X-Git-Url: http://git.vrable.net/?a=blobdiff_plain;f=bluesky%2Fcache.c;h=d94c18e9f62631de7350100ee603823b9b76e0dc;hb=a82b60b3b683840a7074110831bcbaa16a40f0eb;hp=58b7cd6f1ac79c6b67af07fc4ece5979f872312d;hpb=e49fed25d000a51b316b480c1bfb1b00c1f5d345;p=bluesky.git diff --git a/bluesky/cache.c b/bluesky/cache.c index 58b7cd6..d94c18e 100644 --- a/bluesky/cache.c +++ b/bluesky/cache.c @@ -14,6 +14,7 @@ #include "bluesky-private.h" #define WRITEBACK_DELAY (20 * 1000000) +#define CACHE_DROP_DELAY (20 * 1000000) /* Filesystem caching and cache coherency. There are actually a couple of * different tasks that are performed here: @@ -22,40 +23,13 @@ * - Writing batches of data to the cloud. */ -#if 0 -static void writeback_complete(gpointer a, gpointer i) -{ - BlueSkyInode *inode = (BlueSkyInode *)i; - - if (bluesky_verbose) { - g_log("bluesky/flushd", G_LOG_LEVEL_DEBUG, - "Writeback for inode %"PRIu64" complete", inode->inum); - } - - g_mutex_lock(inode->lock); - - inode->change_commit = inode->change_pending; - inode->change_pending = 0; - if (inode->change_count == inode->change_commit) { - /* If inode is no longer dirty... */ - inode->change_time = 0; - g_mutex_lock(inode->fs->lock); - bluesky_list_unlink(&inode->fs->dirty_list, inode->dirty_list); - inode->dirty_list = NULL; - g_mutex_unlock(inode->fs->lock); - } - - g_mutex_unlock(inode->lock); -} -#endif - static void flushd_dirty_inode(BlueSkyInode *inode) { BlueSkyFS *fs = inode->fs; g_mutex_lock(fs->lock); - bluesky_list_unlink(&fs->dirty_list, inode->dirty_list); - inode->dirty_list = NULL; + bluesky_list_unlink(&fs->unlogged_list, inode->unlogged_list); + inode->unlogged_list = NULL; g_mutex_unlock(fs->lock); /* Inode is clean; nothing to do. */ @@ -70,6 +44,17 @@ static void flushd_dirty_inode(BlueSkyInode *inode) bluesky_inode_start_sync(inode); } +/* Check whether memory usage may have dropped below critical thresholds for + * waking up waiting threads. */ +void flushd_check_wakeup(BlueSkyFS *fs) +{ + int dirty = g_atomic_int_get(&fs->cache_dirty); + dirty += g_atomic_int_get(&fs->cache_log_dirty); + + if (dirty <= bluesky_watermark_high_dirty) + g_cond_broadcast(fs->flushd_cond); +} + /* Try to flush dirty data to disk, either due to memory pressure or due to * timeouts. */ static void flushd_dirty(BlueSkyFS *fs) @@ -79,9 +64,9 @@ static void flushd_dirty(BlueSkyFS *fs) while (1) { BlueSkyInode *inode; - if (fs->dirty_list.prev == NULL) + if (fs->unlogged_list.prev == NULL) break; - inode = fs->dirty_list.prev->data; + inode = fs->unlogged_list.prev->data; if (bluesky_verbose) { g_log("bluesky/flushd", G_LOG_LEVEL_DEBUG, @@ -107,10 +92,90 @@ static void flushd_dirty(BlueSkyFS *fs) g_mutex_unlock(inode->lock); bluesky_inode_unref(inode); + g_mutex_lock(fs->lock); + flushd_check_wakeup(fs); + } + + g_cond_broadcast(fs->flushd_cond); + + g_mutex_unlock(fs->lock); +} + +/* Try to flush dirty data to the cloud. This will take a snapshot of the + * entire filesystem (though only point-in-time consistent for isolated inodes + * and not the filesystem as a whole) and ensure all data is written to the + * cloud. When the write completes, we will allow old journal segments (those + * that were fully written _before_ the snapshot process started) to be garbage + * collected. Newer journal segments can't be collected yet since they may + * still contain data which has not been written persistently to the cloud. */ +static void flushd_cloud(BlueSkyFS *fs) +{ + g_mutex_lock(fs->lock); + + /* TODO: Locking? Since we're reading a single variable this is probably + * atomic but a lock could be safer. */ + int journal_seq_start = fs->log->seq_num; + + while (1) { + BlueSkyInode *inode; + if (fs->dirty_list.prev == NULL) + break; + inode = fs->dirty_list.prev->data; + + if (bluesky_verbose) { + g_log("bluesky/flushd", G_LOG_LEVEL_DEBUG, + "Flushing inode %"PRIu64" to cloud", inode->inum); + } + + bluesky_inode_ref(inode); + + g_mutex_unlock(fs->lock); + + g_mutex_lock(inode->lock); + g_assert(inode->change_cloud == inode->change_commit); + g_mutex_lock(fs->lock); + bluesky_list_unlink(&fs->dirty_list, inode->dirty_list); + inode->dirty_list = NULL; + g_mutex_unlock(fs->lock); + + BlueSkyCloudLog *log = inode->committed_item; + inode->committed_item = NULL; + g_mutex_unlock(inode->lock); + + if (log != NULL) + bluesky_cloudlog_serialize(log, fs); + bluesky_inode_unref(inode); + bluesky_cloudlog_unref(log); + g_mutex_lock(fs->lock); } g_mutex_unlock(fs->lock); + bluesky_cloudlog_flush(fs); + + /* Wait until all segments have been written to the cloud, so that it + * becomes safe to free up journal segments. */ + while (fs->log_state->pending_segments != NULL) { + SerializedRecord *segment + = (SerializedRecord *)fs->log_state->pending_segments->data; + g_mutex_lock(segment->lock); + while (!segment->complete) + g_cond_wait(segment->cond, segment->lock); + g_mutex_unlock(segment->lock); + + g_mutex_free(segment->lock); + g_cond_free(segment->cond); + g_free(segment); + + fs->log_state->pending_segments + = g_list_delete_link(fs->log_state->pending_segments, + fs->log_state->pending_segments); + } + + g_print("All segments have been flushed, journal < %d is clean\n", + journal_seq_start); + + fs->log->journal_watermark = journal_seq_start; } /* Drop cached data for a given inode, if it is clean. inode must be locked. */ @@ -118,9 +183,31 @@ static void drop_caches(BlueSkyInode *inode) { if (inode->type == BLUESKY_REGULAR) bluesky_file_drop_cached(inode); + + BlueSkyCloudLog *log = inode->committed_item; + if (log != NULL) { + g_mutex_lock(log->lock); + if (log->data != NULL + && g_atomic_int_get(&log->data_lock_count) == 0 + && (log->location_flags != 0)) + { + bluesky_cloudlog_stats_update(log, -1); + bluesky_string_unref(log->data); + log->data = NULL; + bluesky_cloudlog_stats_update(log, 1); + } + if (log->location_flags & CLOUDLOG_CLOUD) { + log->location_flags &= ~CLOUDLOG_JOURNAL; + } + g_mutex_unlock(log->lock); + } } -/* Drop clean data from the cache if needed due to memory pressure. */ +/* Drop clean data from the cache if needed. Clean data should generally be + * memory-mapped from log file or similar, so the kernel can drop this clean + * data from memory for us and hence memory management isn't too important. + * Mainly, we'll want to drop references to data that hasn't been accessed in a + * while so that it is possible to reclaim log segments on disk. */ static void flushd_clean(BlueSkyFS *fs) { g_mutex_lock(fs->lock); @@ -130,16 +217,15 @@ static void flushd_clean(BlueSkyFS *fs) inode_count = 1; while (inode_count-- > 0) { -#if 0 - if (g_atomic_int_get(&fs->cache_total) < bluesky_watermark_medium_total) - break; -#endif - BlueSkyInode *inode; if (fs->accessed_list.prev == NULL) break; inode = fs->accessed_list.prev->data; + uint64_t elapsed = bluesky_get_current_time() - inode->access_time; + if (elapsed < CACHE_DROP_DELAY) + break; + if (bluesky_verbose) { g_log("bluesky/flushd", G_LOG_LEVEL_DEBUG, "Considering dropping cached data for inode %"PRIu64, @@ -175,8 +261,9 @@ static gpointer flushd_task(BlueSkyFS *fs) if (!g_mutex_trylock(fs->flushd_lock)) return NULL; flushd_dirty(fs); - bluesky_cloudlog_write_log(fs); + flushd_cloud(fs); flushd_clean(fs); + bluesky_cachefile_gc(fs); g_mutex_unlock(fs->flushd_lock); return NULL; @@ -189,18 +276,30 @@ void bluesky_flushd_invoke(BlueSkyFS *fs) void bluesky_flushd_invoke_conditional(BlueSkyFS *fs) { - if (g_atomic_int_get(&fs->cache_dirty) < bluesky_watermark_high_dirty - && g_atomic_int_get(&fs->cache_total) < bluesky_watermark_high_total) + if (g_atomic_int_get(&fs->cache_dirty) < bluesky_watermark_medium_dirty) return; if (bluesky_verbose) { g_log("bluesky/flushd", G_LOG_LEVEL_DEBUG, - "Too much data; invoking flushd: dirty=%d total=%d", - g_atomic_int_get(&fs->cache_dirty), - g_atomic_int_get(&fs->cache_total)); + "Too much data; invoking flushd: dirty=%d", + g_atomic_int_get(&fs->cache_dirty)); } bluesky_flushd_invoke(fs); + + /* If the system is under heavy memory pressure, actually delay execution + * so the flush daemon can catch up. */ + while (g_atomic_int_get(&fs->cache_dirty) + + g_atomic_int_get(&fs->cache_log_dirty) + > bluesky_watermark_high_dirty) { + g_log("bluesky/flushd", G_LOG_LEVEL_DEBUG, + "Waiting due to memory pressure, dirty=%d + %d", + g_atomic_int_get(&fs->cache_dirty), + g_atomic_int_get(&fs->cache_log_dirty)); + g_mutex_lock(fs->lock); + g_cond_wait(fs->flushd_cond, fs->lock); + g_mutex_unlock(fs->lock); + } } /* Start a perpetually-running thread that flushes the cache occasionally. */