Attempt at limiting the rate at which memory is dirtied.
[bluesky.git] / bluesky / cache.c
1 /* Blue Sky: File Systems in the Cloud
2  *
3  * Copyright (C) 2009  The Regents of the University of California
4  * Written by Michael Vrable <mvrable@cs.ucsd.edu>
5  *
6  * TODO: Licensing
7  */
8
9 #include <stdint.h>
10 #include <glib.h>
11 #include <string.h>
12 #include <inttypes.h>
13
14 #include "bluesky-private.h"
15
16 #define WRITEBACK_DELAY (20 * 1000000)
17 #define CACHE_DROP_DELAY (20 * 1000000)
18
19 /* Filesystem caching and cache coherency.  There are actually a couple of
20  * different tasks that are performed here:
21  *   - Forcing data to the log if needed to reclaim memory or simply if the
22  *     data has been dirty in memory long enough.
23  *   - Writing batches of data to the cloud.
24  */
25
26 static void flushd_dirty_inode(BlueSkyInode *inode)
27 {
28     BlueSkyFS *fs = inode->fs;
29
30     g_mutex_lock(fs->lock);
31     bluesky_list_unlink(&fs->unlogged_list, inode->unlogged_list);
32     inode->unlogged_list = NULL;
33     g_mutex_unlock(fs->lock);
34
35     /* Inode is clean; nothing to do. */
36     if (inode->change_count == inode->change_commit)
37         return;
38
39     if (bluesky_verbose) {
40         g_log("bluesky/flushd", G_LOG_LEVEL_DEBUG,
41             "Starting flush of inode %"PRIu64, inode->inum);
42     }
43
44     bluesky_inode_start_sync(inode);
45 }
46
47 /* Check whether memory usage may have dropped below critical thresholds for
48  * waking up waiting threads. */
49 void flushd_check_wakeup(BlueSkyFS *fs)
50 {
51     int dirty = g_atomic_int_get(&fs->cache_dirty);
52     dirty += g_atomic_int_get(&fs->cache_log_dirty);
53
54     if (dirty <= bluesky_watermark_high_dirty)
55         g_cond_broadcast(fs->flushd_cond);
56 }
57
58 /* Try to flush dirty data to disk, either due to memory pressure or due to
59  * timeouts. */
60 static void flushd_dirty(BlueSkyFS *fs)
61 {
62     int64_t start_time = bluesky_get_current_time();
63     g_mutex_lock(fs->lock);
64
65     while (1) {
66         BlueSkyInode *inode;
67         if (fs->unlogged_list.prev == NULL)
68             break;
69         inode = fs->unlogged_list.prev->data;
70
71         if (bluesky_verbose) {
72             g_log("bluesky/flushd", G_LOG_LEVEL_DEBUG,
73                   "Considering flushing inode %"PRIu64, inode->inum);
74         }
75
76         /* Stop processing dirty inodes if we both have enough memory available
77          * and the oldest inode is sufficiently new that it need not be flushed
78          * out. */
79         uint64_t elapsed = bluesky_get_current_time() - inode->change_time;
80         if (g_atomic_int_get(&fs->cache_dirty) < bluesky_watermark_low_dirty
81                 && elapsed < WRITEBACK_DELAY)
82             break;
83         if (inode->change_time > start_time)
84             break;
85
86         bluesky_inode_ref(inode);
87
88         g_mutex_unlock(fs->lock);
89
90         g_mutex_lock(inode->lock);
91         flushd_dirty_inode(inode);
92         g_mutex_unlock(inode->lock);
93         bluesky_inode_unref(inode);
94
95         g_mutex_lock(fs->lock);
96         flushd_check_wakeup(fs);
97     }
98
99     g_cond_broadcast(fs->flushd_cond);
100
101     g_mutex_unlock(fs->lock);
102 }
103
104 /* Try to flush dirty data to the cloud.
105  * TODO: Rewrite this to work on cloud log items rather than inodes, so we can
106  * better track which logs are fully synchronized to the cloud and can be
107  * garbage collected if needed? */
108 static void flushd_cloud(BlueSkyFS *fs)
109 {
110     int64_t start_time = bluesky_get_current_time();
111     g_mutex_lock(fs->lock);
112
113     while (1) {
114         BlueSkyInode *inode;
115         if (fs->dirty_list.prev == NULL)
116             break;
117         inode = fs->dirty_list.prev->data;
118
119         if (bluesky_verbose) {
120             g_log("bluesky/flushd", G_LOG_LEVEL_DEBUG,
121                   "Flushing inode %"PRIu64" to cloud", inode->inum);
122         }
123
124         /* Stop processing dirty inodes if we both have enough memory available
125          * and the oldest inode is sufficiently new that it need not be flushed
126          * out. */
127         uint64_t elapsed = bluesky_get_current_time() - inode->change_time;
128         if (g_atomic_int_get(&fs->cache_dirty) < bluesky_watermark_low_dirty
129                 && elapsed < WRITEBACK_DELAY)
130             break;
131         if (inode->change_time > start_time)
132             break;
133
134         bluesky_inode_ref(inode);
135
136         g_mutex_unlock(fs->lock);
137
138         g_mutex_lock(inode->lock);
139         flushd_dirty_inode(inode);
140         g_mutex_lock(fs->lock);
141         bluesky_list_unlink(&fs->dirty_list, inode->dirty_list);
142         inode->dirty_list = NULL;
143         g_mutex_unlock(fs->lock);
144
145         BlueSkyCloudLog *log = inode->committed_item;
146         bluesky_cloudlog_ref(log);
147         g_mutex_unlock(inode->lock);
148
149         if (log != NULL)
150             bluesky_cloudlog_serialize(log, fs);
151         bluesky_inode_unref(inode);
152         bluesky_cloudlog_unref(log);
153
154         g_mutex_lock(fs->lock);
155     }
156
157     g_mutex_unlock(fs->lock);
158     bluesky_cloudlog_flush(fs);
159 }
160
161 /* Drop cached data for a given inode, if it is clean.  inode must be locked. */
162 static void drop_caches(BlueSkyInode *inode)
163 {
164     if (inode->type == BLUESKY_REGULAR)
165         bluesky_file_drop_cached(inode);
166 }
167
168 /* Drop clean data from the cache if needed.  Clean data should generally be
169  * memory-mapped from log file or similar, so the kernel can drop this clean
170  * data from memory for us and hence memory management isn't too important.
171  * Mainly, we'll want to drop references to data that hasn't been accessed in a
172  * while so that it is possible to reclaim log segments on disk. */
173 static void flushd_clean(BlueSkyFS *fs)
174 {
175     g_mutex_lock(fs->lock);
176
177     size_t inode_count = g_hash_table_size(fs->inodes);
178     if (!inode_count)
179         inode_count = 1;
180
181     while (inode_count-- > 0) {
182         BlueSkyInode *inode;
183         if (fs->accessed_list.prev == NULL)
184             break;
185         inode = fs->accessed_list.prev->data;
186
187         uint64_t elapsed = bluesky_get_current_time() - inode->access_time;
188         if (elapsed < CACHE_DROP_DELAY)
189             break;
190
191         if (bluesky_verbose) {
192             g_log("bluesky/flushd", G_LOG_LEVEL_DEBUG,
193                   "Considering dropping cached data for inode %"PRIu64,
194                   inode->inum);
195         }
196
197         bluesky_inode_ref(inode);
198
199         g_mutex_unlock(fs->lock);
200
201         g_mutex_lock(inode->lock);
202
203         g_mutex_lock(fs->lock);
204         bluesky_list_unlink(&fs->accessed_list, inode->accessed_list);
205         inode->accessed_list = bluesky_list_prepend(&fs->accessed_list, inode);
206         g_mutex_unlock(fs->lock);
207
208         drop_caches(inode);
209
210         g_mutex_unlock(inode->lock);
211         bluesky_inode_unref(inode);
212
213         g_mutex_lock(fs->lock);
214     }
215
216     g_mutex_unlock(fs->lock);
217 }
218
219 /* Run the flush daemon for a single iteration, though if it is already
220  * executing returns immediately. */
221 static gpointer flushd_task(BlueSkyFS *fs)
222 {
223     if (!g_mutex_trylock(fs->flushd_lock))
224         return NULL;
225     flushd_dirty(fs);
226     flushd_cloud(fs);
227     flushd_clean(fs);
228     g_mutex_unlock(fs->flushd_lock);
229
230     return NULL;
231 }
232
233 void bluesky_flushd_invoke(BlueSkyFS *fs)
234 {
235     g_thread_create((GThreadFunc)flushd_task, fs, FALSE, NULL);
236 }
237
238 void bluesky_flushd_invoke_conditional(BlueSkyFS *fs)
239 {
240     if (g_atomic_int_get(&fs->cache_dirty) < bluesky_watermark_medium_dirty)
241         return;
242
243     if (bluesky_verbose) {
244         g_log("bluesky/flushd", G_LOG_LEVEL_DEBUG,
245               "Too much data; invoking flushd: dirty=%d",
246               g_atomic_int_get(&fs->cache_dirty));
247     }
248
249     bluesky_flushd_invoke(fs);
250
251     /* If the system is under heavy memory pressure, actually delay execution
252      * so the flush daemon can catch up. */
253     while (g_atomic_int_get(&fs->cache_dirty)
254                 + g_atomic_int_get(&fs->cache_log_dirty)
255            > bluesky_watermark_high_dirty) {
256         g_log("bluesky/flushd", G_LOG_LEVEL_DEBUG,
257               "Waiting due to memory pressure, dirty=%d + %d",
258               g_atomic_int_get(&fs->cache_dirty),
259               g_atomic_int_get(&fs->cache_log_dirty));
260         g_mutex_lock(fs->lock);
261         g_cond_wait(fs->flushd_cond, fs->lock);
262         g_mutex_unlock(fs->lock);
263     }
264 }
265
266 /* Start a perpetually-running thread that flushes the cache occasionally. */
267 static gpointer flushd_thread(BlueSkyFS *fs)
268 {
269     while (TRUE) {
270         bluesky_flushd_invoke(fs);
271         struct timespec delay;
272         delay.tv_sec = 2;
273         delay.tv_nsec = 0;
274         nanosleep(&delay, NULL);
275     }
276
277     return NULL;
278 }
279
280 void bluesky_flushd_thread_launch(BlueSkyFS *fs)
281 {
282     g_thread_create((GThreadFunc)flushd_thread, fs, FALSE, NULL);
283 }