From 5e97b34daa27f6e0ca1c233e31d00ab8620284f0 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 29 Aug 2017 13:02:02 -0400 Subject: [PATCH 1/4] On windows, don't force-unlink active conscache objects. Part of a fix for bug 22752: We can't unlink these because Windows doesn't allow you to unlink an in-use file. --- src/or/conscache.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/or/conscache.c b/src/or/conscache.c index 5ffa129bb..c30d1998c 100644 --- a/src/or/conscache.c +++ b/src/or/conscache.c @@ -9,6 +9,11 @@ #define CCE_MAGIC 0x17162253 +#ifdef _WIN32 +/* On Windows, unlink won't work if there's an active mmap. */ +#define MUST_UNMAP_TO_UNLINK +#endif + /** * A consensus_cache_entry_t is a reference-counted handle to an * item in a consensus_cache_t. It can be mmapped into RAM, or not, @@ -406,23 +411,36 @@ int consensus_cache_get_n_filenames_available(consensus_cache_t *cache) { tor_assert(cache); - int max = storage_dir_get_max_files(cache->dir); + int max = cache->max_entries; int used = smartlist_len(storage_dir_list(cache->dir)); +#ifdef MUST_UNMAP_TO_UNLINK + if (used > max) + return 0; +#else tor_assert_nonfatal(max >= used); +#endif return max - used; } /** * Delete every element of cache has been marked with - * consensus_cache_entry_mark_for_removal. If force is false, - * retain those entries which are not in use except by the cache. + * consensus_cache_entry_mark_for_removal. If force is false, + * retain those entries which are in use by something other than the cache. */ void consensus_cache_delete_pending(consensus_cache_t *cache, int force) { SMARTLIST_FOREACH_BEGIN(cache->entries, consensus_cache_entry_t *, ent) { tor_assert_nonfatal(ent->in_cache == cache); - if (! force) { + int force_ent = force; +#ifdef MUST_UNMAP_TO_UNLINK + /* We cannot delete anything with an active mmap on win32, so no + * force-deletion. */ + if (ent->map) { + force_ent = 0; + } +#endif + if (! force_ent) { if (ent->refcnt > 1 || BUG(ent->in_cache == NULL)) { /* Somebody is using this entry right now */ continue; From da159c45e2447c729601a1393e93de908ba27c16 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 29 Aug 2017 13:03:36 -0400 Subject: [PATCH 2/4] On windows, allow many entries in conscache directories Since we can't be sure that we can unlink enough files on windows here, let's let the number of permitted entries grow huge if it really must. We do this by letting the storagedir hold lots of entries, but still trying to keep the number of entries under the configured limit. We also have to tell consdiffmgr not to freak out if it can't actually remove enough entries. Part of a fix for bug 22752 --- src/or/conscache.c | 27 +++++++++++++++++++++++++++ src/or/conscache.h | 1 + src/or/consdiffmgr.c | 10 +++++++++- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/or/conscache.c b/src/or/conscache.c index c30d1998c..350a05b0f 100644 --- a/src/or/conscache.c +++ b/src/or/conscache.c @@ -54,6 +54,11 @@ struct consensus_cache_t { storage_dir_t *dir; /** List of all the entries in the directory. */ smartlist_t *entries; + + /** The maximum number of entries that we'd like to allow in this cache. + * This is the same as the storagedir limit when MUST_UNMAP_TO_UNLINK is + * not defined. */ + unsigned max_entries; }; static void consensus_cache_clear(consensus_cache_t *cache); @@ -71,6 +76,10 @@ consensus_cache_open(const char *subdir, int max_entries) { consensus_cache_t *cache = tor_malloc_zero(sizeof(consensus_cache_t)); char *directory = get_datadir_fname(subdir); + cache->max_entries = max_entries; +#ifdef MUST_UNMAP_TO_UNLINK + max_entries = 1000000; +#endif cache->dir = storage_dir_new(directory, max_entries); tor_free(directory); if (!cache->dir) { @@ -82,6 +91,19 @@ consensus_cache_open(const char *subdir, int max_entries) return cache; } +/** Return true if it's okay to put more entries in this cache than + * its official file limit. */ +int +consensus_cache_may_overallocate(consensus_cache_t *cache) +{ + (void) cache; +#ifdef MUST_UNMAP_TO_UNLINK + return 1; +#else + return 0; +#endif +} + /** * Tell the sandbox (if any) configured by cfg to allow the * operations that cache will need. @@ -90,6 +112,11 @@ int consensus_cache_register_with_sandbox(consensus_cache_t *cache, struct sandbox_cfg_elem **cfg) { +#ifdef MUST_UNMAP_TO_UNLINK + /* Our sandbox doesn't support huge limits like we use here. + */ + tor_assert_nonfatal_unreached(); +#endif return storage_dir_register_with_sandbox(cache->dir, cfg); } diff --git a/src/or/conscache.h b/src/or/conscache.h index aef54201f..a0d74c4e0 100644 --- a/src/or/conscache.h +++ b/src/or/conscache.h @@ -14,6 +14,7 @@ HANDLE_DECL(consensus_cache_entry, consensus_cache_entry_t, ) consensus_cache_t *consensus_cache_open(const char *subdir, int max_entries); void consensus_cache_free(consensus_cache_t *cache); struct sandbox_cfg_elem; +int consensus_cache_may_overallocate(consensus_cache_t *cache); int consensus_cache_register_with_sandbox(consensus_cache_t *cache, struct sandbox_cfg_elem **cfg); void consensus_cache_unmap_lazy(consensus_cache_t *cache, time_t cutoff); diff --git a/src/or/consdiffmgr.c b/src/or/consdiffmgr.c index 928fc26f5..831d5d45c 100644 --- a/src/or/consdiffmgr.c +++ b/src/or/consdiffmgr.c @@ -1136,7 +1136,7 @@ consdiffmgr_ensure_space_for_files(int n) return 0; } // Let's get more assertive: clean out unused stuff, and force-remove - // the files. + // the files that we can. consdiffmgr_cleanup(); consensus_cache_delete_pending(cache, 1); const int n_to_remove = n - consensus_cache_get_n_filenames_available(cache); @@ -1159,6 +1159,14 @@ consdiffmgr_ensure_space_for_files(int n) smartlist_free(objects); consensus_cache_delete_pending(cache, 1); + + if (consensus_cache_may_overallocate(cache)) { + /* If we're allowed to throw extra files into the cache, let's do so + * rather getting upset. + */ + return 0; + } + if (BUG(n_marked < n_to_remove)) return -1; else From a58a41c15a53edda1e560221bd17e46c8df6a661 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 29 Aug 2017 13:09:39 -0400 Subject: [PATCH 3/4] Changes file for bug22752 (simple version) --- changes/bug22752_simple | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 changes/bug22752_simple diff --git a/changes/bug22752_simple b/changes/bug22752_simple new file mode 100644 index 000000000..7e6035705 --- /dev/null +++ b/changes/bug22752_simple @@ -0,0 +1,6 @@ + o Major bugfixes (windows, directory cache): + - On windows, do not try to delete cached consensus documents and + diffs, until they unmapped from memory. Allow the diff storage + directory to grow larger in order to handle files that might + need to stay around longer. Fixes bug 22752; bugfix on + 0.3.1.1-alpha. From 948be49ce06f744ca456d20f48bfb6d2f5cfb7d2 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 4 Sep 2017 11:54:49 -0400 Subject: [PATCH 4/4] 22752: Improve comments to explain why we're doing this fix. Based on questions and comments from dgoulet, I've tried to fill in the reasoning about why these functions work in the way that they do, so that it will be easier for future programmers to understand why this code exists and works the way it does. --- src/or/conscache.c | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/or/conscache.c b/src/or/conscache.c index 350a05b0f..9e13ce8e4 100644 --- a/src/or/conscache.c +++ b/src/or/conscache.c @@ -10,7 +10,10 @@ #define CCE_MAGIC 0x17162253 #ifdef _WIN32 -/* On Windows, unlink won't work if there's an active mmap. */ +/* On Windows, unlink won't work on a file if the file is actively mmap()ed. + * That forces us to be less aggressive about unlinking files, and causes other + * changes throughout our logic. + */ #define MUST_UNMAP_TO_UNLINK #endif @@ -74,13 +77,26 @@ static void consensus_cache_entry_unmap(consensus_cache_entry_t *ent); consensus_cache_t * consensus_cache_open(const char *subdir, int max_entries) { + int storagedir_max_entries; consensus_cache_t *cache = tor_malloc_zero(sizeof(consensus_cache_t)); char *directory = get_datadir_fname(subdir); cache->max_entries = max_entries; + #ifdef MUST_UNMAP_TO_UNLINK - max_entries = 1000000; + /* If we can't unlink the files that we're still using, then we need to + * tell the storagedir backend to allow far more files than this consensus + * cache actually wants, so that it can hold files which, from this cache's + * perspective, have become useless. + */ +#define VERY_LARGE_STORAGEDIR_LIMIT (1000*1000) + storagedir_max_entries = VERY_LARGE_STORAGEDIR_LIMIT; +#else + /* Otherwise, we can just tell the storagedir to use the same limits + * as this cache. */ + storagedir_max_entries = max_entries; #endif - cache->dir = storage_dir_new(directory, max_entries); + + cache->dir = storage_dir_new(directory, storagedir_max_entries); tor_free(directory); if (!cache->dir) { tor_free(cache); @@ -92,7 +108,12 @@ consensus_cache_open(const char *subdir, int max_entries) } /** Return true if it's okay to put more entries in this cache than - * its official file limit. */ + * its official file limit. + * + * (We need this method on Windows, where we can't unlink files that are still + * in use, and therefore might need to temporarily exceed the file limit until + * the no-longer-wanted files are deletable.) + */ int consensus_cache_may_overallocate(consensus_cache_t *cache) { @@ -113,7 +134,15 @@ consensus_cache_register_with_sandbox(consensus_cache_t *cache, struct sandbox_cfg_elem **cfg) { #ifdef MUST_UNMAP_TO_UNLINK - /* Our sandbox doesn't support huge limits like we use here. + /* Our Linux sandbox doesn't support huge file lists like the one that would + * be generated by using VERY_LARGE_STORAGEDIR_LIMIT above in + * consensus_cache_open(). Since the Linux sandbox is the only one we have + * right now, we just assert that we never reach this point when we've had + * to use VERY_LARGE_STORAGEDIR_LIMIT. + * + * If at some point in the future we have a different sandbox mechanism that + * can handle huge file lists, we can remove this assertion or make it + * conditional. */ tor_assert_nonfatal_unreached(); #endif