-
Kirill Smelkov authored
Cleanup.__del__() calls transaction.abort(). transaction.abort() implicitly uses thread-local transaction manager. __del__ method is called when the last reference to an object is dropped. If there is no object cycles this can be more or less controlled. However if an object with __del__ method is held by some other objects forming a cycle, the __del__ method will be called only eventually whenever that cycle is garbage-collected by GC. The garbage collection can be ran from *whatever* thread which caused the GC. If we leave cleanup local variable, a reference to it will be held in frame's .f_locals, and if e.g. an exception is raised and even caught a cycle will be created: exc_traceback | ^ | | v .f_localsplus frame This is perfectly the situation which was outlined above - a cycle of objects which will be freed only by GC and one of those objects (.f_localsplus) reference `cleanup`. This way Cleanup.__del__ will be called not strictly when REQUEST goes away but maybe a bit later if the cycle stays alive more (and thus potentially aborting different next transaction) and/or from another thread (and thus aborting unrelated transaction). The case was caught via analyzing strange messages in Zope logs on a Wendelin system: Exception KeyError: (140574709077840,) in <bound method Cleanup.__del__ of <App.ZApplication.Cleanup instance at 0x7fd9f22f6830>> ignored This turned out to be Wendelin.core's _ZBigFileH.on_connection_close() trying to unregister itself from transaction manager it previously registered to on ZODB connection open: https://lab.nexedi.com/nexedi/wendelin.core/blob/87bf4908/bigfile/file_zodb.py#L621 https://lab.nexedi.com/nexedi/wendelin.core/blob/87bf4908/bigfile/file_zodb.py#L608 the transaction_manager being ThreadTransactionManager: https://github.com/zopefoundation/transaction/blob/bd26c334/transaction/__init__.py#L23 https://github.com/zopefoundation/transaction/blob/bd26c334/transaction/_manager.py#L122 which implicitly keeps its instance variables in thread-local storage (i.e. self.var is different for every thread), and so transaction_manager.unregisterSynch() was removing _ZBigFileH instance from a set different to where it was originally registered: https://github.com/zopefoundation/transaction/blob/bd26c334/transaction/_manager.py#L79 thus the error message, not to mention the further inevitable desynchronization in between wendelin.core transaction manager https://lab.nexedi.com/nexedi/wendelin.core/blob/87bf4908/bigfile/file_zodb.py#L548 and ZODB transaction manager which can lead to all kind of corruptions. ---- So do not let `cleanup` variable to stay in the frame. This is simple but not optimal fix. The good solution would avoid using __del__ at all and track request lifetime explicitly via regular programming logic.
9ca50f15