]> arthur.barton.de Git - bup.git/blobdiff - lib/bup/client.py
Check that all context managed objects are properly closed
[bup.git] / lib / bup / client.py
index 9372d609c4464196cad6777a70a3d2b162b551d0..7c284c4af4aa613db536cf5db637382988e1db02 100644 (file)
@@ -3,14 +3,14 @@ from __future__ import print_function
 
 from __future__ import absolute_import
 from binascii import hexlify, unhexlify
-import errno, os, re, struct, time, zlib
+import os, re, struct, time, zlib
 import socket
 
 from bup import git, ssh, vfs
-from bup.compat import environ, range, reraise
+from bup.compat import environ, pending_raise, range, reraise
 from bup.helpers import (Conn, atomically_replaced_file, chunkyreader, debug1,
                          debug2, linereader, lines_until_sentinel,
-                         mkdirp, progress, qprogress, DemuxConn)
+                         mkdirp, nullcontext_if_not, progress, qprogress, DemuxConn)
 from bup.io import path_msg
 from bup.vint import write_bvec
 
@@ -69,6 +69,7 @@ def parse_remote(remote):
 
 class Client:
     def __init__(self, remote, create=False):
+        self.closed = False
         self._busy = self.conn = None
         self.sock = self.p = self.pout = self.pin = None
         is_reverse = environ.get(b'BUP_SERVER_REVERSE')
@@ -117,16 +118,8 @@ class Client:
             self.check_ok()
         self.sync_indexes()
 
-    def __del__(self):
-        try:
-            self.close()
-        except IOError as e:
-            if e.errno == errno.EPIPE:
-                pass
-            else:
-                raise
-
     def close(self):
+        self.closed = True
         if self.conn and not self._busy:
             self.conn.write(b'quit\n')
         if self.pin:
@@ -148,6 +141,16 @@ class Client:
         self.conn = None
         self.sock = self.p = self.pin = self.pout = None
 
+    def __del__(self):
+        assert self.closed
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, type, value, traceback):
+        with pending_raise(value, rethrow=False):
+            self.close()
+
     def check_ok(self):
         if self.p:
             rv = self.p.poll()
@@ -158,15 +161,17 @@ class Client:
             return self.conn.check_ok()
         except Exception as e:
             reraise(ClientError(e))
+            # reraise doesn't return
+            return None
 
     def check_busy(self):
         if self._busy:
             raise ClientError('already busy with command %r' % self._busy)
-        
+
     def ensure_busy(self):
         if not self._busy:
             raise ClientError('expected to be busy, but not busy?!')
-        
+
     def _not_busy(self):
         self._busy = None
 
@@ -480,7 +485,9 @@ class Client:
         return result
 
 
+# FIXME: disentangle this (stop inheriting) from PackWriter
 class PackWriter_Remote(git.PackWriter):
+
     def __init__(self, conn, objcache_maker, suggest_packs,
                  onopen, onclose,
                  ensure_busy,
@@ -492,6 +499,7 @@ class PackWriter_Remote(git.PackWriter):
                                 compression_level=compression_level,
                                 max_pack_size=max_pack_size,
                                 max_pack_objects=max_pack_objects)
+        self.remote_closed = False
         self.file = conn
         self.filename = b'remote socket'
         self.suggest_packs = suggest_packs
@@ -502,25 +510,39 @@ class PackWriter_Remote(git.PackWriter):
         self._bwcount = 0
         self._bwtime = time.time()
 
+    # __enter__ and __exit__ are inherited
+
     def _open(self):
         if not self._packopen:
             self.onopen()
             self._packopen = True
 
     def _end(self, run_midx=True):
+        # Called by other PackWriter methods like breakpoint().
+        # Must not close the connection (self.file)
         assert(run_midx)  # We don't support this via remote yet
-        if self._packopen and self.file:
+        self.objcache, objcache = None, self.objcache
+        with nullcontext_if_not(objcache):
+            if not (self._packopen and self.file):
+                return None
             self.file.write(b'\0\0\0\0')
             self._packopen = False
             self.onclose() # Unbusy
-            self.objcache = None
+            if objcache is not None:
+                objcache.close()
             return self.suggest_packs() # Returns last idx received
 
     def close(self):
+        # Called by inherited __exit__
+        self.remote_closed = True
         id = self._end()
         self.file = None
         return id
 
+    def __del__(self):
+        assert self.remote_closed
+        super(PackWriter_Remote, self).__del__()
+
     def abort(self):
         raise ClientError("don't know how to abort remote pack writing")