Make dropping of inodes from cache more resistant to races.
[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 (5 * 1000000)
17 #define CACHE_CLEAN_DELAY (30 * 1000000)
18
19 /* Filesystem caching and cache coherency. */
20
21 static void writeback_complete(gpointer a, gpointer i)
22 {
23     BlueSkyInode *inode = (BlueSkyInode *)i;
24
25     g_log("bluesky/flushd", G_LOG_LEVEL_DEBUG,
26           "Writeback for inode %"PRIu64" complete", inode->inum);
27
28     g_mutex_lock(inode->lock);
29
30     inode->change_commit = inode->change_pending;
31     if (inode->change_count == inode->change_commit) {
32         /* If inode is no longer dirty... */
33         inode->change_time = 0;
34         inode->change_pending = 0;
35     }
36
37     g_mutex_unlock(inode->lock);
38 }
39
40 /* Drop cached data for a given inode, if it is clean.  inode must be locked. */
41 static void drop_caches(BlueSkyInode *inode)
42 {
43     if (inode->type == BLUESKY_REGULAR)
44         bluesky_file_drop_cached(inode);
45 }
46
47 static void flushd_inode(gpointer value, gpointer user_data)
48 {
49     BlueSkyFS *fs = (BlueSkyFS *)user_data;
50
51     BlueSkyInode *inode = (BlueSkyInode *)value;
52
53     g_mutex_lock(inode->lock);
54
55     if (inode->change_count == inode->change_commit) {
56         uint64_t delay = bluesky_get_current_time() - inode->access_time;
57         if (delay >= CACHE_CLEAN_DELAY) {
58             drop_caches(inode);
59
60             /* If the only references are the one we hold and the one in the
61              * filesystem inum->inode hash table...  First check the refcount
62              * without the lock for speed, but if the check looks good verify
63              * it after taking the filesystem lock. */
64             if (inode->refcount == 2) {
65                 g_mutex_lock(fs->lock);
66                 if (inode->refcount == 2) {
67                     g_log("bluesky/flushd", G_LOG_LEVEL_DEBUG,
68                           "Trying to drop inode %"PRIu64" from cache",
69                           inode->inum);
70                     if (g_hash_table_remove(fs->inodes, &inode->inum))
71                         bluesky_inode_unref(inode);
72                 }
73                 g_mutex_unlock(fs->lock);
74             }
75         }
76
77         g_mutex_unlock(inode->lock);
78         bluesky_inode_unref(inode);
79         return;
80     }
81
82     if (inode->change_pending) {
83         /* Waiting for an earlier writeback to finish, so don't start a new
84          * writeback yet. */
85         g_mutex_unlock(inode->lock);
86         bluesky_inode_unref(inode);
87         return;
88     }
89
90     uint64_t elapsed = bluesky_get_current_time() - inode->change_time;
91     if (elapsed < WRITEBACK_DELAY) {
92         /* Give a bit more time before starting writeback. */
93         g_mutex_unlock(inode->lock);
94         bluesky_inode_unref(inode);
95         return;
96     }
97
98     inode->change_pending = inode->change_count;
99
100     g_log("bluesky/flushd", G_LOG_LEVEL_DEBUG,
101           "Starting flush of inode %"PRIu64, inode->inum);
102
103     /* Create a store barrier.  All operations part of the writeback will be
104      * added to this barrier, so when the barrier completes we know that the
105      * writeback is finished. */
106     BlueSkyStoreAsync *barrier = bluesky_store_async_new(fs->store);
107     barrier->op = STORE_OP_BARRIER;
108
109     bluesky_inode_start_sync(inode, barrier);
110
111     bluesky_store_async_add_notifier(barrier, writeback_complete, inode);
112     bluesky_store_async_submit(barrier);
113     bluesky_store_async_unref(barrier);
114
115     g_mutex_unlock(inode->lock);
116     bluesky_inode_unref(inode);
117 }
118
119 /* Scan through the cache for dirty data and start flushing it to stable
120  * storage.  This does not guarantee that data is committed when it returns.
121  * Instead, this can be called occasionally to ensure that dirty data is
122  * gradually flushed.
123  *
124  * We do not want to hold the filesystem lock while flushing individual inodes,
125  * a that could lead to deadlock.  So first scan through the inode table to get
126  * a reference to all inodes, then process that queue of inodes after dropping
127  * the filesystem lock. */
128 static void gather_inodes(gpointer key, gpointer value, gpointer user_data)
129 {
130     GSList **list = (GSList **)user_data;
131     *list = g_slist_prepend(*list, value);
132     bluesky_inode_ref((BlueSkyInode *)value);
133 }
134
135 void bluesky_flushd_invoke(BlueSkyFS *fs)
136 {
137     GSList *list = NULL;
138
139     g_mutex_lock(fs->lock);
140     g_hash_table_foreach(fs->inodes, gather_inodes, &list);
141     g_mutex_unlock(fs->lock);
142
143     list = g_slist_reverse(list);
144     g_slist_foreach(list, flushd_inode, fs);
145
146     g_slist_free(list);
147 }