From 4be24a89dfbad5cadfa4d1bae4105c352995577a Mon Sep 17 00:00:00 2001 From: david-sarah Date: Sat, 29 May 2010 19:55:44 -0700 Subject: [PATCH] SFTP: avoid blocking close on a heisenfile that has been abandoned or never changed. Also, improve the logging to help track down a case where OpenOffice hangs on opening a file with FXF_READ|FXF_WRITE. --- src/allmydata/frontends/sftpd.py | 78 ++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/src/allmydata/frontends/sftpd.py b/src/allmydata/frontends/sftpd.py index e44270d8..0d459f6e 100644 --- a/src/allmydata/frontends/sftpd.py +++ b/src/allmydata/frontends/sftpd.py @@ -526,10 +526,11 @@ class OverwriteableFileConsumer(PrefixingLogMixin): def close(self): self.is_closed = True self.finish() - try: - self.f.close() - except EnvironmentError as e: - self.log("suppressed %r from close of temporary file %r" % (e, self.f), level=WEIRD) + if not self.is_closed: + try: + self.f.close() + except BaseException as e: + self.log("suppressed %r from close of temporary file %r" % (e, self.f), level=WEIRD) def unregisterProducer(self): if self.producer: @@ -798,37 +799,48 @@ class GeneralSFTPFile(PrefixingLogMixin): parent = self.parent childname = self.childname - def _close(ign): - d2 = defer.succeed(None) - if self.has_changed and not abandoned: - d2.addCallback(lambda ign: self.consumer.when_done()) - if self.filenode and self.filenode.is_mutable(): - self.log("update mutable file %r childname=%r" % (self.filenode, self.childname,), level=OPERATIONAL) - d2.addCallback(lambda ign: self.consumer.get_current_size()) - d2.addCallback(lambda size: self.consumer.read(0, size)) - d2.addCallback(lambda new_contents: self.filenode.overwrite(new_contents)) - else: - def _add_file(ign): - self.log("_add_file childname=%r" % (childname,), level=OPERATIONAL) - u = FileHandle(self.consumer.get_file(), self.convergence) - return parent.add_file(childname, u) - d2.addCallback(_add_file) + # has_changed is set when writeChunk is called, not when the write occurs, so + # it is correct to optimize out the commit if it is False at the close call. + has_changed = self.has_changed - def _committed(res): - if noisy: self.log("_committed(%r)" % (res,), level=NOISY) + def _committed(res): + if noisy: self.log("_committed(%r)" % (res,), level=NOISY) - self.consumer.close() + self.consumer.close() + + # We must close_notify before re-firing self.async. + if self.close_notify: + self.close_notify(self.userpath, self.parent, self.childname, self) + return res + + def _close(ign): + d2 = self.consumer.when_done() + if self.filenode and self.filenode.is_mutable(): + self.log("update mutable file %r childname=%r" % (self.filenode, self.childname,), level=OPERATIONAL) + d2.addCallback(lambda ign: self.consumer.get_current_size()) + d2.addCallback(lambda size: self.consumer.read(0, size)) + d2.addCallback(lambda new_contents: self.filenode.overwrite(new_contents)) + else: + def _add_file(ign): + self.log("_add_file childname=%r" % (childname,), level=OPERATIONAL) + u = FileHandle(self.consumer.get_file(), self.convergence) + return parent.add_file(childname, u) + d2.addCallback(_add_file) - # We must close_notify before re-firing self.async. - if self.close_notify: - self.close_notify(self.userpath, self.parent, self.childname, self) - return res d2.addBoth(_committed) return d2 - self.async.addCallback(_close) - d = defer.Deferred() + + # If the file has been abandoned, we don't want the close operation to get "stuck", + # even if self.async fails to re-fire. Doing the close independently of self.async + # in that case ensures that dropping an ssh connection is sufficient to abandon + # any heisenfiles that were not explicitly closed in that connection. + if abandoned or not has_changed: + d.addCallback(_committed) + else: + self.async.addCallback(_close) + self.async.addCallbacks(eventually_callback(d), eventually_errback(d)) d.addBoth(_convert_error, request) return d @@ -1064,7 +1076,8 @@ class SFTPUserHandler(ConchUser, PrefixingLogMixin): return d def _sync_heisenfiles(self, userpath, direntry, ignore=None): - if noisy: self.log("._sync_heisenfiles(%r, %r)" % (userpath, direntry), level=NOISY) + request = "._sync_heisenfiles(%r, %r, ignore=%r)" % (userpath, direntry, ignore) + self.log(request, level=OPERATIONAL) files = [] if direntry in all_heisenfiles: @@ -1075,7 +1088,12 @@ class SFTPUserHandler(ConchUser, PrefixingLogMixin): d = defer.succeed(None) for f in files: if f is not ignore: - d.addCallback(lambda ign: f.sync()) + d.addBoth(lambda ign: f.sync()) + + def _done(ign): + self.log("done %r" % (request,), level=OPERATIONAL) + return None + d.addBoth(_done) return d def _remove_heisenfile(self, userpath, parent, childname, file_to_remove): -- 2.45.2