Commit 3923d485 authored by Mikulas Patocka's avatar Mikulas Patocka Committed by Mike Snitzer

dm writecache: implement gradual cleanup

If a block is stored in the cache for too long, it will now be
written to the underlying device and cleaned up.

Add a new option "max_age" that specifies the maximum age of a block
in milliseconds.
Signed-off-by: default avatarMikulas Patocka <mpatocka@redhat.com>
Signed-off-by: default avatarMike Snitzer <snitzer@redhat.com>
parent 93de44eb
...@@ -26,6 +26,8 @@ ...@@ -26,6 +26,8 @@
#define AUTOCOMMIT_BLOCKS_SSD 65536 #define AUTOCOMMIT_BLOCKS_SSD 65536
#define AUTOCOMMIT_BLOCKS_PMEM 64 #define AUTOCOMMIT_BLOCKS_PMEM 64
#define AUTOCOMMIT_MSEC 1000 #define AUTOCOMMIT_MSEC 1000
#define MAX_AGE_DIV 16
#define MAX_AGE_UNSPECIFIED -1UL
#define BITMAP_GRANULARITY 65536 #define BITMAP_GRANULARITY 65536
#if BITMAP_GRANULARITY < PAGE_SIZE #if BITMAP_GRANULARITY < PAGE_SIZE
...@@ -88,6 +90,7 @@ struct wc_entry { ...@@ -88,6 +90,7 @@ struct wc_entry {
:47 :47
#endif #endif
; ;
unsigned long age;
#ifdef DM_WRITECACHE_HANDLE_HARDWARE_ERRORS #ifdef DM_WRITECACHE_HANDLE_HARDWARE_ERRORS
uint64_t original_sector; uint64_t original_sector;
uint64_t seq_count; uint64_t seq_count;
...@@ -119,6 +122,7 @@ struct dm_writecache { ...@@ -119,6 +122,7 @@ struct dm_writecache {
size_t writeback_size; size_t writeback_size;
size_t freelist_high_watermark; size_t freelist_high_watermark;
size_t freelist_low_watermark; size_t freelist_low_watermark;
unsigned long max_age;
unsigned uncommitted_blocks; unsigned uncommitted_blocks;
unsigned autocommit_blocks; unsigned autocommit_blocks;
...@@ -130,6 +134,8 @@ struct dm_writecache { ...@@ -130,6 +134,8 @@ struct dm_writecache {
struct timer_list autocommit_timer; struct timer_list autocommit_timer;
struct wait_queue_head freelist_wait; struct wait_queue_head freelist_wait;
struct timer_list max_age_timer;
atomic_t bio_in_progress[2]; atomic_t bio_in_progress[2];
struct wait_queue_head bio_in_progress_wait[2]; struct wait_queue_head bio_in_progress_wait[2];
...@@ -597,6 +603,7 @@ static void writecache_insert_entry(struct dm_writecache *wc, struct wc_entry *i ...@@ -597,6 +603,7 @@ static void writecache_insert_entry(struct dm_writecache *wc, struct wc_entry *i
rb_link_node(&ins->rb_node, parent, node); rb_link_node(&ins->rb_node, parent, node);
rb_insert_color(&ins->rb_node, &wc->tree); rb_insert_color(&ins->rb_node, &wc->tree);
list_add(&ins->lru, &wc->lru); list_add(&ins->lru, &wc->lru);
ins->age = jiffies;
} }
static void writecache_unlink(struct dm_writecache *wc, struct wc_entry *e) static void writecache_unlink(struct dm_writecache *wc, struct wc_entry *e)
...@@ -632,6 +639,16 @@ static inline void writecache_verify_watermark(struct dm_writecache *wc) ...@@ -632,6 +639,16 @@ static inline void writecache_verify_watermark(struct dm_writecache *wc)
queue_work(wc->writeback_wq, &wc->writeback_work); queue_work(wc->writeback_wq, &wc->writeback_work);
} }
static void writecache_max_age_timer(struct timer_list *t)
{
struct dm_writecache *wc = from_timer(wc, t, max_age_timer);
if (!dm_suspended(wc->ti) && !writecache_has_error(wc)) {
queue_work(wc->writeback_wq, &wc->writeback_work);
mod_timer(&wc->max_age_timer, jiffies + wc->max_age / MAX_AGE_DIV);
}
}
static struct wc_entry *writecache_pop_from_freelist(struct dm_writecache *wc, sector_t expected_sector) static struct wc_entry *writecache_pop_from_freelist(struct dm_writecache *wc, sector_t expected_sector)
{ {
struct wc_entry *e; struct wc_entry *e;
...@@ -838,6 +855,7 @@ static void writecache_suspend(struct dm_target *ti) ...@@ -838,6 +855,7 @@ static void writecache_suspend(struct dm_target *ti)
bool flush_on_suspend; bool flush_on_suspend;
del_timer_sync(&wc->autocommit_timer); del_timer_sync(&wc->autocommit_timer);
del_timer_sync(&wc->max_age_timer);
wc_lock(wc); wc_lock(wc);
writecache_flush(wc); writecache_flush(wc);
...@@ -974,6 +992,9 @@ static void writecache_resume(struct dm_target *ti) ...@@ -974,6 +992,9 @@ static void writecache_resume(struct dm_target *ti)
writecache_verify_watermark(wc); writecache_verify_watermark(wc);
if (wc->max_age != MAX_AGE_UNSPECIFIED)
mod_timer(&wc->max_age_timer, jiffies + wc->max_age / MAX_AGE_DIV);
wc_unlock(wc); wc_unlock(wc);
} }
...@@ -1661,7 +1682,9 @@ static void writecache_writeback(struct work_struct *work) ...@@ -1661,7 +1682,9 @@ static void writecache_writeback(struct work_struct *work)
wbl.size = 0; wbl.size = 0;
while (!list_empty(&wc->lru) && while (!list_empty(&wc->lru) &&
(wc->writeback_all || (wc->writeback_all ||
wc->freelist_size + wc->writeback_size <= wc->freelist_low_watermark)) { wc->freelist_size + wc->writeback_size <= wc->freelist_low_watermark ||
(jiffies - container_of(wc->lru.prev, struct wc_entry, lru)->age >=
wc->max_age - wc->max_age / MAX_AGE_DIV))) {
n_walked++; n_walked++;
if (unlikely(n_walked > WRITEBACK_LATENCY) && if (unlikely(n_walked > WRITEBACK_LATENCY) &&
...@@ -1924,9 +1947,11 @@ static int writecache_ctr(struct dm_target *ti, unsigned argc, char **argv) ...@@ -1924,9 +1947,11 @@ static int writecache_ctr(struct dm_target *ti, unsigned argc, char **argv)
wc->ti = ti; wc->ti = ti;
mutex_init(&wc->lock); mutex_init(&wc->lock);
wc->max_age = MAX_AGE_UNSPECIFIED;
writecache_poison_lists(wc); writecache_poison_lists(wc);
init_waitqueue_head(&wc->freelist_wait); init_waitqueue_head(&wc->freelist_wait);
timer_setup(&wc->autocommit_timer, writecache_autocommit_timer, 0); timer_setup(&wc->autocommit_timer, writecache_autocommit_timer, 0);
timer_setup(&wc->max_age_timer, writecache_max_age_timer, 0);
for (i = 0; i < 2; i++) { for (i = 0; i < 2; i++) {
atomic_set(&wc->bio_in_progress[i], 0); atomic_set(&wc->bio_in_progress[i], 0);
...@@ -2100,6 +2125,14 @@ static int writecache_ctr(struct dm_target *ti, unsigned argc, char **argv) ...@@ -2100,6 +2125,14 @@ static int writecache_ctr(struct dm_target *ti, unsigned argc, char **argv)
goto invalid_optional; goto invalid_optional;
wc->autocommit_jiffies = msecs_to_jiffies(autocommit_msecs); wc->autocommit_jiffies = msecs_to_jiffies(autocommit_msecs);
wc->autocommit_time_set = true; wc->autocommit_time_set = true;
} else if (!strcasecmp(string, "max_age") && opt_params >= 1) {
unsigned max_age_msecs;
string = dm_shift_arg(&as), opt_params--;
if (sscanf(string, "%u%c", &max_age_msecs, &dummy) != 1)
goto invalid_optional;
if (max_age_msecs > 86400000)
goto invalid_optional;
wc->max_age = msecs_to_jiffies(max_age_msecs);
} else if (!strcasecmp(string, "cleaner")) { } else if (!strcasecmp(string, "cleaner")) {
wc->cleaner = true; wc->cleaner = true;
} else if (!strcasecmp(string, "fua")) { } else if (!strcasecmp(string, "fua")) {
...@@ -2361,6 +2394,8 @@ static void writecache_status(struct dm_target *ti, status_type_t type, ...@@ -2361,6 +2394,8 @@ static void writecache_status(struct dm_target *ti, status_type_t type,
DMEMIT(" autocommit_blocks %u", wc->autocommit_blocks); DMEMIT(" autocommit_blocks %u", wc->autocommit_blocks);
if (wc->autocommit_time_set) if (wc->autocommit_time_set)
DMEMIT(" autocommit_time %u", jiffies_to_msecs(wc->autocommit_jiffies)); DMEMIT(" autocommit_time %u", jiffies_to_msecs(wc->autocommit_jiffies));
if (wc->max_age != MAX_AGE_UNSPECIFIED)
DMEMIT(" max_age %u", jiffies_to_msecs(wc->max_age));
if (wc->cleaner) if (wc->cleaner)
DMEMIT(" cleaner"); DMEMIT(" cleaner");
if (wc->writeback_fua_set) if (wc->writeback_fua_set)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment