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