Merge lp:~mbp/bzr/rm-del-methods into lp:bzr

Proposed by Martin Pool
Status: Merged
Approved by: Martin Pool
Approved revision: no longer in the source branch.
Merged at revision: 6002
Proposed branch: lp:~mbp/bzr/rm-del-methods
Merge into: lp:bzr
Diff against target: 258 lines (+33/-78)
7 files modified
bzrlib/atomicfile.py (+0/-4)
bzrlib/lock.py (+0/-6)
bzrlib/lockable_files.py (+17/-46)
bzrlib/tests/test_fifo_cache.py (+5/-5)
bzrlib/transport/memory.py (+0/-6)
bzrlib/transport/sftp.py (+0/-6)
doc/developers/code-style.txt (+11/-5)
To merge this branch: bzr merge lp:~mbp/bzr/rm-del-methods
Reviewer Review Type Date Requested Status
John A Meinel Needs Information
Bazaar Developers Pending
Andrew Bennetts Pending
Review via email: mp+64466@code.launchpad.net

Commit message

remove most __del__ methods

Description of the change

Per bug 791612 and recent discussion about bad gc behavior of __del__ methods, this just removes them.

To post a comment you must log in.
Revision history for this message
Martin Pool (mbp) wrote :

https://bugs.launchpad.net/launchpad/+bug/721166 is another related case, where a warning is intermittently breaking lp's test suite, but without actually telling us anything useful.

Revision history for this message
Martin Pool (mbp) wrote :

I'm thinking of also banning 'def __del__(' the same way we ban assert, to avoid later disappointment.

Revision history for this message
Martin Pool (mbp) wrote :

... but perhaps we can wait til it's an actual problem, or until we're sure there's really zero use for it.

Revision history for this message
Martin Pool (mbp) wrote :

also bug 391024 showing these methods generate noise without helping very much in removing leaks

Revision history for this message
John A Meinel (jameinel) wrote :

I'm fine with all of them but the SmartStreamMedium. That one is potentially doing real work in __del__

review: Needs Information
Revision history for this message
Martin Pool (mbp) wrote :

On 14 June 2011 02:58, John A Meinel <email address hidden> wrote:
> Review: Needs Information
> I'm fine with all of them but the SmartStreamMedium. That one is potentially doing real work in __del__

It is, but I think not in a way that's going to close very safely or
reliably. There is no real guarantee things will still be
sufficiently open when this fires that this can actually disconnect.

I can't find any specific bugs from this but it looks a lot like the
kind of thing that gives spurious errors.

Possibly I should audit for places where this is constructed and check
they're explicitly closed.

I might wait on this for comments from spiv.

Revision history for this message
Andrew Bennetts (spiv) wrote :

I'd be pretty hesitant about removing the smart medium __del__ too. I'm worried we'd see a recurrence of bug 659590 or similar if we did, because I don't think we typically do explicitly .disconnect() Transports (and thus any associated medium) very often at all.

In principle we ought to be explicitly releasing those resources, so in principle removing the __del__ as a fallback is fine, but I think reality doesn't yet match that principle.

Revision history for this message
Martin Pool (mbp) wrote :

I'll put back the ssh gc and file a separate bug for deterministically closing them.

Revision history for this message
Martin Pool (mbp) wrote :

https://bugs.launchpad.net/bzr/+bug/803187

and this has now been overwritten with a version that leaves the del method in place for the smart medium.

Revision history for this message
Martin Pool (mbp) wrote :

another review, please?

Revision history for this message
Martin Pool (mbp) wrote :

based on previous comments and offline discussion, I'm going to send this in

Revision history for this message
Martin Pool (mbp) wrote :

sent to pqm by email

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bzrlib/atomicfile.py'
2--- bzrlib/atomicfile.py 2011-05-20 14:46:02 +0000
3+++ bzrlib/atomicfile.py 2011-06-28 21:53:58 +0000
4@@ -118,7 +118,3 @@
5 """Discard the file unless already committed."""
6 if self._fd is not None:
7 self.abort()
8-
9- def __del__(self):
10- if self._fd is not None:
11- warnings.warn("%r leaked" % self)
12
13=== modified file 'bzrlib/lock.py'
14--- bzrlib/lock.py 2011-03-30 11:45:54 +0000
15+++ bzrlib/lock.py 2011-06-28 21:53:58 +0000
16@@ -171,12 +171,6 @@
17 self.f.close()
18 self.f = None
19
20- def __del__(self):
21- if self.f:
22- from warnings import warn
23- warn("lock on %r not released" % self.f)
24- self.unlock()
25-
26 def unlock(self):
27 raise NotImplementedError()
28
29
30=== modified file 'bzrlib/lockable_files.py'
31--- bzrlib/lockable_files.py 2011-04-07 10:36:24 +0000
32+++ bzrlib/lockable_files.py 2011-06-28 21:53:58 +0000
33@@ -33,27 +33,6 @@
34 )
35
36
37-# XXX: The tracking here of lock counts and whether the lock is held is
38-# somewhat redundant with what's done in LockDir; the main difference is that
39-# LockableFiles permits reentrancy.
40-
41-class _LockWarner(object):
42- """Hold a counter for a lock and warn if GCed while the count is >= 1.
43-
44- This is separate from LockableFiles because putting a __del__ on
45- LockableFiles can result in uncollectable cycles.
46- """
47-
48- def __init__(self, repr):
49- self.lock_count = 0
50- self.repr = repr
51-
52- def __del__(self):
53- if self.lock_count >= 1:
54- # There should have been a try/finally to unlock this.
55- warnings.warn("%r was gc'd while locked" % self.repr)
56-
57-
58 class LockableFiles(object):
59 """Object representing a set of related files locked within the same scope.
60
61@@ -68,17 +47,14 @@
62 This class is now deprecated; code should move to using the Transport
63 directly for file operations and using the lock or CountedLock for
64 locking.
65-
66+
67 :ivar _lock: The real underlying lock (e.g. a LockDir)
68- :ivar _counted_lock: A lock decorated with a semaphore, so that it
69- can be re-entered.
70+ :ivar _lock_count: If _lock_mode is true, a positive count of the number
71+ of times the lock has been taken (and not yet released) *by this
72+ process*, through this particular object instance.
73+ :ivar _lock_mode: None, or 'r' or 'w'
74 """
75
76- # _lock_mode: None, or 'r' or 'w'
77-
78- # _lock_count: If _lock_mode is true, a positive count of the number of
79- # times the lock has been taken *by this process*.
80-
81 def __init__(self, transport, lock_name, lock_class):
82 """Create a LockableFiles group
83
84@@ -92,7 +68,7 @@
85 self.lock_name = lock_name
86 self._transaction = None
87 self._lock_mode = None
88- self._lock_warner = _LockWarner(repr(self))
89+ self._lock_count = 0
90 self._find_modes()
91 esc_name = self._escape(lock_name)
92 self._lock = lock_class(transport, esc_name,
93@@ -111,6 +87,7 @@
94 def __repr__(self):
95 return '%s(%r)' % (self.__class__.__name__,
96 self._transport)
97+
98 def __str__(self):
99 return 'LockableFiles(%s, %s)' % (self.lock_name, self._transport.base)
100
101@@ -174,19 +151,18 @@
102 some other way, and need to synchronise this object's state with that
103 fact.
104 """
105- # TODO: Upgrade locking to support using a Transport,
106- # and potentially a remote locking protocol
107 if self._lock_mode:
108- if self._lock_mode != 'w' or not self.get_transaction().writeable():
109+ if (self._lock_mode != 'w'
110+ or not self.get_transaction().writeable()):
111 raise errors.ReadOnlyError(self)
112 self._lock.validate_token(token)
113- self._lock_warner.lock_count += 1
114+ self._lock_count += 1
115 return self._token_from_lock
116 else:
117 token_from_lock = self._lock.lock_write(token=token)
118 #traceback.print_stack()
119 self._lock_mode = 'w'
120- self._lock_warner.lock_count = 1
121+ self._lock_count = 1
122 self._set_write_transaction()
123 self._token_from_lock = token_from_lock
124 return token_from_lock
125@@ -195,12 +171,12 @@
126 if self._lock_mode:
127 if self._lock_mode not in ('r', 'w'):
128 raise ValueError("invalid lock mode %r" % (self._lock_mode,))
129- self._lock_warner.lock_count += 1
130+ self._lock_count += 1
131 else:
132 self._lock.lock_read()
133 #traceback.print_stack()
134 self._lock_mode = 'r'
135- self._lock_warner.lock_count = 1
136+ self._lock_count = 1
137 self._set_read_transaction()
138
139 def _set_read_transaction(self):
140@@ -217,23 +193,19 @@
141 def unlock(self):
142 if not self._lock_mode:
143 return lock.cant_unlock_not_held(self)
144- if self._lock_warner.lock_count > 1:
145- self._lock_warner.lock_count -= 1
146+ if self._lock_count > 1:
147+ self._lock_count -= 1
148 else:
149 #traceback.print_stack()
150 self._finish_transaction()
151 try:
152 self._lock.unlock()
153 finally:
154- self._lock_mode = self._lock_warner.lock_count = None
155-
156- @property
157- def _lock_count(self):
158- return self._lock_warner.lock_count
159+ self._lock_mode = self._lock_count = None
160
161 def is_locked(self):
162 """Return true if this LockableFiles group is locked"""
163- return self._lock_warner.lock_count >= 1
164+ return self._lock_count >= 1
165
166 def get_physical_lock_status(self):
167 """Return physical lock status.
168@@ -325,4 +297,3 @@
169 def validate_token(self, token):
170 if token is not None:
171 raise errors.TokenLockingNotSupported(self)
172-
173
174=== modified file 'bzrlib/tests/test_fifo_cache.py'
175--- bzrlib/tests/test_fifo_cache.py 2009-03-23 14:59:43 +0000
176+++ bzrlib/tests/test_fifo_cache.py 2011-06-28 21:53:58 +0000
177@@ -229,11 +229,11 @@
178 c = fifo_cache.FIFOCache()
179 c.add(1, 2, cleanup=logging_cleanup)
180 del c
181- # TODO: We currently don't support calling the cleanup() funcs during
182- # __del__. We might want to consider implementing this.
183- self.expectFailure("we don't call cleanups during __del__",
184- self.assertEqual, [(1, 2)], log)
185- self.assertEqual([(1, 2)], log)
186+ # As a matter of design, bzr does not (can not) count on anything
187+ # being run from Python __del__ methods, because they may not run for
188+ # a long time, and because in cPython merely having them defined
189+ # interferes with garbage collection.
190+ self.assertEqual([], log)
191
192
193 class TestFIFOSizeCache(tests.TestCase):
194
195=== modified file 'bzrlib/transport/memory.py'
196--- bzrlib/transport/memory.py 2010-02-23 07:43:11 +0000
197+++ bzrlib/transport/memory.py 2011-06-28 21:53:58 +0000
198@@ -289,12 +289,6 @@
199 raise LockError('File %r already locked' % (self.path,))
200 self.transport._locks[self.path] = self
201
202- def __del__(self):
203- # Should this warn, or actually try to cleanup?
204- if self.transport:
205- warnings.warn("MemoryLock %r not explicitly unlocked" % (self.path,))
206- self.unlock()
207-
208 def unlock(self):
209 del self.transport._locks[self.path]
210 self.transport = None
211
212=== modified file 'bzrlib/transport/sftp.py'
213--- bzrlib/transport/sftp.py 2011-04-20 14:27:19 +0000
214+++ bzrlib/transport/sftp.py 2011-06-28 21:53:58 +0000
215@@ -114,12 +114,6 @@
216 except FileExists:
217 raise LockError('File %r already locked' % (self.path,))
218
219- def __del__(self):
220- """Should this warn, or actually try to cleanup?"""
221- if self.lock_file:
222- warning("SFTPLock %r not explicitly unlocked" % (self.path,))
223- self.unlock()
224-
225 def unlock(self):
226 if not self.lock_file:
227 return
228
229=== modified file 'doc/developers/code-style.txt'
230--- doc/developers/code-style.txt 2010-12-14 23:39:41 +0000
231+++ doc/developers/code-style.txt 2011-06-28 21:53:58 +0000
232@@ -189,15 +189,21 @@
233 why in a comment.
234
235 1. Never rely on a ``__del__`` method running. If there is code that
236- must run, do it from a ``finally`` block instead.
237+ must run, instead have a ``finally`` block or an ``addCleanup`` call an
238+ explicit ``close`` method.
239
240 2. Never ``import`` from inside a ``__del__`` method, or you may crash the
241 interpreter!!
242
243-3. In some places we raise a warning from the destructor if the object
244- has not been cleaned up or closed. This is considered OK: the warning
245- may not catch every case but it's still useful sometimes.
246-
247+3. Prior to bzr 2.4, we sometimes used to raise warnings from del methods
248+ that the object was not cleaned up or closed. We no longer do this:
249+ failure to close the object doesn't cause a test failure; the warning
250+ appears an arbitrary long time after the problem occurred (the object
251+ being leaked); merely having a del method inhibits Python gc; the
252+ warnings appear to users and upset them; they can also break tests that
253+ are checking what appears on stderr.
254+
255+In short, just don't use ``__del__``.
256
257 Cleanup methods
258 ===============