From: Brian Warner Date: Sat, 6 Dec 2008 05:07:10 +0000 (-0700) Subject: MutableFileNode.modify: pass first_time= and servermap= to the modifier callback X-Git-Url: https://git.rkrishnan.org/COPYING.TGPPL.html?a=commitdiff_plain;h=fb9af2c7a00d7bec6a7c1c1dbd7ea74a9c509858;p=tahoe-lafs%2Ftahoe-lafs.git MutableFileNode.modify: pass first_time= and servermap= to the modifier callback --- diff --git a/src/allmydata/dirnode.py b/src/allmydata/dirnode.py index b13a3010..39c377eb 100644 --- a/src/allmydata/dirnode.py +++ b/src/allmydata/dirnode.py @@ -24,7 +24,7 @@ class Deleter: self.node = node self.name = name self.must_exist = True - def modify(self, old_contents): + def modify(self, old_contents, servermap, first_time): children = self.node._unpack_contents(old_contents) if self.name not in children: if self.must_exist: @@ -42,7 +42,7 @@ class MetadataSetter: self.name = name self.metadata = metadata - def modify(self, old_contents): + def modify(self, old_contents, servermap, first_time): children = self.node._unpack_contents(old_contents) if self.name not in children: raise NoSuchChildError(self.name) @@ -62,7 +62,7 @@ class Adder: def set_node(self, name, node, metadata): self.entries.append( [name, node, metadata] ) - def modify(self, old_contents): + def modify(self, old_contents, servermap, first_time): children = self.node._unpack_contents(old_contents) now = time.time() for e in self.entries: diff --git a/src/allmydata/interfaces.py b/src/allmydata/interfaces.py index ce813eca..d0ccffa5 100644 --- a/src/allmydata/interfaces.py +++ b/src/allmydata/interfaces.py @@ -618,12 +618,14 @@ class IMutableFileNode(IFileNode, IMutableFilesystemNode): uploading the new version. I return a Deferred that fires (with a PublishStatus object) when the update is complete. - The modifier callable will be given two arguments: a string (with the - old contents) and a servermap. As with download_best_version(), the - old contents will be from the best recoverable version, but the - modifier can use the servermap to make other decisions (such as - refusing to apply the delta if there are multiple parallel versions, - or if there is evidence of a newer unrecoverable version). + The modifier callable will be given three arguments: a string (with + the old contents), a 'first_time' boolean, and a servermap. As with + download_best_version(), the old contents will be from the best + recoverable version, but the modifier can use the servermap to make + other decisions (such as refusing to apply the delta if there are + multiple parallel versions, or if there is evidence of a newer + unrecoverable version). 'first_time' will be True the first time the + modifier is called, and False on any subsequent calls. The callable should return a string with the new contents. The callable must be prepared to be called multiple times, and must diff --git a/src/allmydata/mutable/node.py b/src/allmydata/mutable/node.py index 98942a66..3e6338e6 100644 --- a/src/allmydata/mutable/node.py +++ b/src/allmydata/mutable/node.py @@ -328,10 +328,12 @@ class MutableFileNode: I implement the following pseudocode:: obtain_mutable_filenode_lock() + first_time = True while True: update_servermap(MODE_WRITE) old = retrieve_best_version() - new = modifier(old, *args, **kwargs) + new = modifier(old, servermap, first_time) + first_time = False if new == old: break try: publish(new) @@ -366,23 +368,23 @@ class MutableFileNode: servermap = ServerMap() if backoffer is None: backoffer = BackoffAgent().delay - return self._modify_and_retry(servermap, modifier, backoffer) - def _modify_and_retry(self, servermap, modifier, backoffer): - d = self._modify_once(servermap, modifier) + return self._modify_and_retry(servermap, modifier, backoffer, True) + def _modify_and_retry(self, servermap, modifier, backoffer, first_time): + d = self._modify_once(servermap, modifier, first_time) def _retry(f): f.trap(UncoordinatedWriteError) d2 = defer.maybeDeferred(backoffer, self, f) d2.addCallback(lambda ignored: self._modify_and_retry(servermap, modifier, - backoffer)) + backoffer, False)) return d2 d.addErrback(_retry) return d - def _modify_once(self, servermap, modifier): + def _modify_once(self, servermap, modifier, first_time): d = self._update_servermap(servermap, MODE_WRITE) d.addCallback(self._once_updated_download_best_version, servermap) def _apply(old_contents): - new_contents = modifier(old_contents) + new_contents = modifier(old_contents, servermap, first_time) if new_contents is None or new_contents == old_contents: # no changes need to be made return diff --git a/src/allmydata/test/common.py b/src/allmydata/test/common.py index a82ca684..f9272a9d 100644 --- a/src/allmydata/test/common.py +++ b/src/allmydata/test/common.py @@ -256,7 +256,7 @@ class FakeMutableFileNode: def _modify(self, modifier): assert not self.is_readonly() old_contents = self.all_contents[self.storage_index] - self.all_contents[self.storage_index] = modifier(old_contents) + self.all_contents[self.storage_index] = modifier(old_contents, None, True) return None def download(self, target): diff --git a/src/allmydata/test/test_mutable.py b/src/allmydata/test/test_mutable.py index f19eeb04..ad368280 100644 --- a/src/allmydata/test/test_mutable.py +++ b/src/allmydata/test/test_mutable.py @@ -382,18 +382,18 @@ class Filenode(unittest.TestCase, testutil.ShouldFailMixin): return d def test_modify(self): - def _modifier(old_contents): + def _modifier(old_contents, servermap, first_time): return old_contents + "line2" - def _non_modifier(old_contents): + def _non_modifier(old_contents, servermap, first_time): return old_contents - def _none_modifier(old_contents): + def _none_modifier(old_contents, servermap, first_time): return None - def _error_modifier(old_contents): + def _error_modifier(old_contents, servermap, first_time): raise ValueError("oops") - def _toobig_modifier(old_contents): + def _toobig_modifier(old_contents, servermap, first_time): return "b" * (Publish.MAX_SEGMENT_SIZE+1) calls = [] - def _ucw_error_modifier(old_contents): + def _ucw_error_modifier(old_contents, servermap, first_time): # simulate an UncoordinatedWriteError once calls.append(1) if len(calls) <= 1: @@ -444,16 +444,16 @@ class Filenode(unittest.TestCase, testutil.ShouldFailMixin): return d def test_modify_backoffer(self): - def _modifier(old_contents): + def _modifier(old_contents, servermap, first_time): return old_contents + "line2" calls = [] - def _ucw_error_modifier(old_contents): + def _ucw_error_modifier(old_contents, servermap, first_time): # simulate an UncoordinatedWriteError once calls.append(1) if len(calls) <= 1: raise UncoordinatedWriteError("simulated") return old_contents + "line3" - def _always_ucw_error_modifier(old_contents): + def _always_ucw_error_modifier(old_contents, servermap, first_time): raise UncoordinatedWriteError("simulated") def _backoff_stopper(node, f): return f @@ -1658,7 +1658,7 @@ class MultipleVersions(unittest.TestCase, PublishMixin, CheckerMixin): target[0] = 3 # seqnum4 self._set_versions(target) - def _modify(oldversion): + def _modify(oldversion, servermap, first_time): return oldversion + " modified" d = self._fn.modify(_modify) d.addCallback(lambda res: self._fn.download_best_version())