Improve tracking of memory usage in BlueSky.
[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
18 /* Filesystem caching and cache coherency.  There are actually a couple of
19  * different tasks that are performed here:
20  *   - Forcing data to the log if needed to reclaim memory or simply if the
21  *     data has been dirty in memory long enough.
22  *   - Writing batches of data to the cloud.
23  */
24
25 static void flushd_dirty_inode(BlueSkyInode *inode)
26 {
27     BlueSkyFS *fs = inode->fs;
28
29     g_mutex_lock(fs->lock);
30     bluesky_list_unlink(&fs->unlogged_list, inode->unlogged_list);
31     inode->unlogged_list = NULL;
32     g_mutex_unlock(fs->lock);
33
34     /* Inode is clean; nothing to do. */
35     if (inode->change_count == inode->change_commit)
36         return;
37
38     if (bluesky_verbose) {
39         g_log("bluesky/flushd", G_LOG_LEVEL_DEBUG,
40             "Starting flush of inode %"PRIu64, inode->inum);
41     }
42
43     bluesky_inode_start_sync(inode);
44 }
45
46 /* Try to flush dirty data to disk, either due to memory pressure or due to
47  * timeouts. */
48 static void flushd_dirty(BlueSkyFS *fs)
49 {
50     int64_t start_time = bluesky_get_current_time();
51     g_mutex_lock(fs->lock);
52
53     while (1) {
54         BlueSkyInode *inode;
55         if (fs->unlogged_list.prev == NULL)
56             break;
57         inode = fs->unlogged_list.prev->data;
58
59         if (bluesky_verbose) {
60             g_log("bluesky/flushd", G_LOG_LEVEL_DEBUG,
61                   "Considering flushing inode %"PRIu64, inode->inum);
62         }
63
64         /* Stop processing dirty inodes if we both have enough memory available
65          * and the oldest inode is sufficiently new that it need not be flushed
66          * out. */
67         uint64_t elapsed = bluesky_get_current_time() - inode->change_time;
68         if (g_atomic_int_get(&fs->cache_dirty) < bluesky_watermark_low_dirty
69                 && elapsed < WRITEBACK_DELAY)
70             break;
71         if (inode->change_time > start_time)
72             break;
73
74         bluesky_inode_ref(inode);
75
76         g_mutex_unlock(fs->lock);
77
78         g_mutex_lock(inode->lock);
79         flushd_dirty_inode(inode);
80         g_mutex_unlock(inode->lock);
81         bluesky_inode_unref(inode);
82
83         g_mutex_lock(fs->lock);
84     }
85
86     g_mutex_unlock(fs->lock);
87 }
88
89 /* Try to flush dirty data to the cloud. */
90 static void flushd_cloud(BlueSkyFS *fs)
91 {
92     int64_t start_time = bluesky_get_current_time();
93     g_mutex_lock(fs->lock);
94
95     while (1) {
96         BlueSkyInode *inode;
97         if (fs->dirty_list.prev == NULL)
98             break;
99         inode = fs->dirty_list.prev->data;
100
101         if (bluesky_verbose) {
102             g_log("bluesky/flushd", G_LOG_LEVEL_DEBUG,
103                   "Flushing inode %"PRIu64" to cloud", inode->inum);
104         }
105
106         /* Stop processing dirty inodes if we both have enough memory available
107          * and the oldest inode is sufficiently new that it need not be flushed
108          * out. */
109         uint64_t elapsed = bluesky_get_current_time() - inode->change_time;
110         if (g_atomic_int_get(&fs->cache_dirty) < bluesky_watermark_low_dirty
111                 && elapsed < WRITEBACK_DELAY)
112             break;
113         if (inode->change_time > start_time)
114             break;
115
116         bluesky_inode_ref(inode);
117
118         g_mutex_unlock(fs->lock);
119
120         g_mutex_lock(inode->lock);
121         flushd_dirty_inode(inode);
122         g_mutex_lock(fs->lock);
123         bluesky_list_unlink(&fs->dirty_list, inode->dirty_list);
124         inode->dirty_list = NULL;
125         g_mutex_unlock(fs->lock);
126
127         BlueSkyCloudLog *log = inode->committed_item;
128         bluesky_cloudlog_ref(log);
129         g_mutex_unlock(inode->lock);
130
131         if (log != NULL)
132             bluesky_cloudlog_serialize(log, fs);
133         bluesky_inode_unref(inode);
134         bluesky_cloudlog_ref(log);
135
136         g_mutex_lock(fs->lock);
137     }
138
139     g_mutex_unlock(fs->lock);
140     bluesky_cloudlog_flush(fs);
141 }
142
143 /* Drop cached data for a given inode, if it is clean.  inode must be locked. */
144 static void drop_caches(BlueSkyInode *inode)
145 {
146     if (inode->type == BLUESKY_REGULAR)
147         bluesky_file_drop_cached(inode);
148 }
149
150 /* Drop clean data from the cache if needed due to memory pressure. */
151 static void flushd_clean(BlueSkyFS *fs)
152 {
153     g_mutex_lock(fs->lock);
154
155     size_t inode_count = g_hash_table_size(fs->inodes);
156     if (!inode_count)
157         inode_count = 1;
158
159     while (inode_count-- > 0) {
160 #if 0
161         if (g_atomic_int_get(&fs->cache_total) < bluesky_watermark_medium_total)
162             break;
163 #endif
164
165         BlueSkyInode *inode;
166         if (fs->accessed_list.prev == NULL)
167             break;
168         inode = fs->accessed_list.prev->data;
169
170         if (bluesky_verbose) {
171             g_log("bluesky/flushd", G_LOG_LEVEL_DEBUG,
172                   "Considering dropping cached data for inode %"PRIu64,
173                   inode->inum);
174         }
175
176         bluesky_inode_ref(inode);
177
178         g_mutex_unlock(fs->lock);
179
180         g_mutex_lock(inode->lock);
181
182         g_mutex_lock(fs->lock);
183         bluesky_list_unlink(&fs->accessed_list, inode->accessed_list);
184         inode->accessed_list = bluesky_list_prepend(&fs->accessed_list, inode);
185         g_mutex_unlock(fs->lock);
186
187         drop_caches(inode);
188
189         g_mutex_unlock(inode->lock);
190         bluesky_inode_unref(inode);
191
192         g_mutex_lock(fs->lock);
193     }
194
195     g_mutex_unlock(fs->lock);
196 }
197
198 /* Run the flush daemon for a single iteration, though if it is already
199  * executing returns immediately. */
200 static gpointer flushd_task(BlueSkyFS *fs)
201 {
202     if (!g_mutex_trylock(fs->flushd_lock))
203         return NULL;
204     flushd_dirty(fs);
205     flushd_cloud(fs);
206     flushd_clean(fs);
207     g_mutex_unlock(fs->flushd_lock);
208
209     return NULL;
210 }
211
212 void bluesky_flushd_invoke(BlueSkyFS *fs)
213 {
214     g_thread_create((GThreadFunc)flushd_task, fs, FALSE, NULL);
215 }
216
217 void bluesky_flushd_invoke_conditional(BlueSkyFS *fs)
218 {
219     if (g_atomic_int_get(&fs->cache_dirty) < bluesky_watermark_high_dirty
220         /*&& g_atomic_int_get(&fs->cache_total) < bluesky_watermark_high_total*/)
221         return;
222
223     if (bluesky_verbose) {
224         g_log("bluesky/flushd", G_LOG_LEVEL_DEBUG,
225               "Too much data; invoking flushd: dirty=%d",
226               g_atomic_int_get(&fs->cache_dirty));
227     }
228
229     bluesky_flushd_invoke(fs);
230 }
231
232 /* Start a perpetually-running thread that flushes the cache occasionally. */
233 static gpointer flushd_thread(BlueSkyFS *fs)
234 {
235     while (TRUE) {
236         bluesky_flushd_invoke(fs);
237         struct timespec delay;
238         delay.tv_sec = 2;
239         delay.tv_nsec = 0;
240         nanosleep(&delay, NULL);
241     }
242
243     return NULL;
244 }
245
246 void bluesky_flushd_thread_launch(BlueSkyFS *fs)
247 {
248     g_thread_create((GThreadFunc)flushd_thread, fs, FALSE, NULL);
249 }