[tor-commits] [sbws/master] Stop deleting the latest.v3bw symlink. Instead, do an atomic rename.

pastly at torproject.org pastly at torproject.org
Thu Aug 9 14:21:19 UTC 2018


commit 2b111356fb813379ab0b4a0dc705706766788ad3
Author: teor <teor2345 at gmail.com>
Date:   Wed Jul 18 11:12:34 2018 +1000

    Stop deleting the latest.v3bw symlink. Instead, do an atomic rename.
    
    Also:
    * document the location of latest.v3bw
    * document the atomic creation of latest.v3bw
    * explain how to atomically create latest.v3bw when transferring it to
      another host
    
    Closes tor trac 26740.
---
 CHANGELOG.md            |  6 +++++-
 sbws/config.default.ini |  2 ++
 sbws/core/generate.py   | 13 +++++++------
 sbws/lib/v3bwfile.py    | 19 +++++++++++++------
 4 files changed, 27 insertions(+), 13 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 604811e..48a7d37 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,9 +12,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
 - Log line on start up with sbws version, platform info, and library versions
 (trac#26751)
 
+### Fixed
+
+- Stop deleting the latest.v3bw symlink. Instead, do an atomic rename.
+  (#26740)
+
 ### Changed
 
-- Document at which times should v3bw files be generated (#26740)
 - Remove test data v3bw file and generate it from the same test. (#26736)
 
 ### Fixed
diff --git a/sbws/config.default.ini b/sbws/config.default.ini
index b21ba0b..09053a3 100644
--- a/sbws/config.default.ini
+++ b/sbws/config.default.ini
@@ -1,6 +1,8 @@
 [paths]
 datadir = ${sbws_home}/datadir
 v3bw_dname = ${sbws_home}/v3bw
+# The latest bandwidth file is atomically symlinked to
+# V3BandwidthsFile ${v3bw_dname}/latest.v3bw
 v3bw_fname = ${v3bw_dname}/{}.v3bw
 started_filepath = ${sbws_home}/started_at
 log_dname = ${sbws_home}/log
diff --git a/sbws/core/generate.py b/sbws/core/generate.py
index f952fbc..481529c 100644
--- a/sbws/core/generate.py
+++ b/sbws/core/generate.py
@@ -12,12 +12,13 @@ log = logging.getLogger(__name__)
 def gen_parser(sub):
     d = 'Generate a v3bw file based on recent results. A v3bw file is the '\
         'file Tor directory authorities want to read and base their '\
-        'bandwidth votes on.' \
-        'This file should be generated every hour at any minute, except ' \
-        'between 45 and 55 minutes past the hour because Tor read this file '\
-        ' at minute 50 and except ' \
-        'between 15 and 25 minutes past the hour because Tor read this file '\
-        ' at minute 20 during a consensus failure.'
+        'bandwidth votes on. '\
+        'To avoid inconsistent reads, configure tor with '\
+        '"V3BandwidthsFile /path/to/latest.v3bw". '\
+        '(latest.v3bw is an atomically created symlink in the same '\
+        'directory as output.) '\
+        'If the file is transferred to another host, it should be written to '\
+        'a temporary path, then renamed to the V3BandwidthsFile path.'
     p = sub.add_parser('generate', description=d,
                        formatter_class=ArgumentDefaultsHelpFormatter)
     p.add_argument('--output', default=None, type=str,
diff --git a/sbws/lib/v3bwfile.py b/sbws/lib/v3bwfile.py
index c1dc890..1899e3b 100644
--- a/sbws/lib/v3bwfile.py
+++ b/sbws/lib/v3bwfile.py
@@ -413,17 +413,24 @@ class V3BWFile(object):
             log.info("Writing to stdout is not supported.")
             return
         log.info('Writing v3bw file to %s', output)
+        # To avoid inconsistent reads, the bandwidth data is written to an
+        # archive path, then atomically symlinked to 'latest.v3bw'
         out_dir = os.path.dirname(output)
         out_link = os.path.join(out_dir, 'latest.v3bw')
-        if os.path.exists(out_link):
-            log.debug('Deleting existing symlink before creating a new one.')
-            os.remove(out_link)
+        out_link_tmp = out_link + '.tmp'
         with DirectoryLock(out_dir):
             with open(output, 'wt') as fd:
                 fd.write(str(self.header))
                 for line in self.bw_lines:
                     fd.write(str(line))
             output_basename = os.path.basename(output)
-            log.debug('Creating symlink from {} to {}.'
-                      .format(output_basename, out_link))
-            os.symlink(output_basename, out_link)
+            # To atomically symlink a file, we need to create a temporary link,
+            # then rename it to the final link name. (POSIX guarantees that
+            # rename is atomic.)
+            log.debug('Creating symlink {} -> {}.'
+                      .format(out_link_tmp, output_basename))
+            os.symlink(output_basename, out_link_tmp)
+            log.debug('Renaming symlink {} -> {} to {} -> {}.'
+                      .format(out_link_tmp, output_basename,
+                              out_link, output_basename))
+            os.rename(out_link_tmp, out_link)





More information about the tor-commits mailing list