Merge lp:~jamesh/storm/zstorm-same-thread into lp:storm

Proposed by James Henstridge
Status: Merged
Approved by: Jamu Kakar
Approved revision: 359
Merged at revision: 359
Proposed branch: lp:~jamesh/storm/zstorm-same-thread
Merge into: lp:storm
Diff against target: 162 lines (+58/-20)
3 files modified
NEWS (+3/-0)
storm/zope/zstorm.py (+21/-9)
tests/zope/zstorm.py (+34/-11)
To merge this branch: bzr merge lp:~jamesh/storm/zstorm-same-thread
Reviewer Review Type Date Requested Status
Jamu Kakar (community) Approve
Thomas Herve (community) Approve
Review via email: mp+18920@code.launchpad.net
To post a comment you must log in.
Revision history for this message
James Henstridge (jamesh) wrote :

Before joining a store to the transaction, make sure that ZStorm knows about the store.

The most common case where this check fails is if a ZStorm managed store is being used in a thread other than the one it was created in, which is a semi-common error that can be difficult to debug.

Revision history for this message
Thomas Herve (therve) wrote :

Looks great. Just one comment:

[1]
133 + except ZStormError:
134 + # Expected: wrong thread.
135 + pass

To make the test more robust, can you add something to failures here, and assert it's present at the end? This way it will still check something even if the thread doesn't run, for example.

review: Approve
lp:~jamesh/storm/zstorm-same-thread updated
359. By James Henstridge

Improve test robustness, as suggested by therve.

Revision history for this message
Jamu Kakar (jkakar) wrote :

[1]

+def register_store_with_transaction(store, zstorm_ref):

Can you please add a docstring to this function.

[2]

+ # Check if the store is known. This could indicate a store being

s/is known/is know/ please

[3]

+ """If a zstorm registered thread crosses over to another thread,
+ it will not be usable."""

Please format this as:

        """
        If a zstorm registered thread crosses over to another thread,
        it will not be usable.
        """

[4]

+ try:
+ store.execute("SELECT 1")
+ # And again, to show that the event handler is still
+ # attached.
+ store.execute("SELECT 1")
+ except ZStormError:

Without thinking hard, it's not obvious which of these statements
causes the ZStormError to be raised. I think it would be helpful to
add something to the failures list after each of these calls to make
the flow of execution more clear.

This is nice, debugging the kinds of problems this logic will
uncover is rather awkward. +1!

review: Approve
Revision history for this message
James Henstridge (jamesh) wrote :

> [4]
>
> + try:
> + store.execute("SELECT 1")
> + # And again, to show that the event handler is still
> + # attached.
> + store.execute("SELECT 1")
> + except ZStormError:
>
> Without thinking hard, it's not obvious which of these statements
> causes the ZStormError to be raised. I think it would be helpful to
> add something to the failures list after each of these calls to make
> the flow of execution more clear.

Looking at the test, this part is obviously wrong. The second statement never gets run. I'll fix this before merging.

lp:~jamesh/storm/zstorm-same-thread updated
360. By James Henstridge

Fix test to properly check repeated calls

361. By James Henstridge

Merge from trunk

362. By James Henstridge

Add news item

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2010-06-04 15:23:13 +0000
3+++ NEWS 2010-07-01 17:53:23 +0000
4@@ -11,6 +11,9 @@
5 unblock_access() methods that can be used to block access to the
6 database connection. This can be used to ensure that an
7 application doesn't access the database at unexpected times.
8+ - When using stores managed by ZStorm, a ZStormError will be raised
9+ on attempts to use a per-thread store from the wrong thread (bug
10+ #348815).
11
12 Bug fixes
13 ---------
14
15=== modified file 'storm/zope/zstorm.py'
16--- storm/zope/zstorm.py 2009-06-26 08:59:26 +0000
17+++ storm/zope/zstorm.py 2010-07-01 17:53:23 +0000
18@@ -134,7 +134,7 @@
19 store._register_for_txn = True
20 store._event.hook(
21 "register-transaction", register_store_with_transaction,
22- self.transaction_manager)
23+ weakref.ref(self))
24
25 self._stores[id(store)] = store
26 if name is not None:
27@@ -180,7 +180,7 @@
28 store._register_for_txn = False
29 store._event.unhook(
30 "register-transaction", register_store_with_transaction,
31- self.transaction_manager)
32+ weakref.ref(self))
33
34 def iterstores(self):
35 """Iterate C{name, store} 2-tuples."""
36@@ -203,9 +203,20 @@
37 return self._default_uris.copy()
38
39
40-def register_store_with_transaction(store, transaction_manager):
41- data_manager = StoreDataManager(store, transaction_manager)
42- transaction_manager.get().join(data_manager)
43+def register_store_with_transaction(store, zstorm_ref):
44+ zstorm = zstorm_ref()
45+ if zstorm is None:
46+ # zstorm object does not exist any more.
47+ return False
48+
49+ # Check if the store is known. This could indicate a store being
50+ # used outside of its thread.
51+ if id(store) not in zstorm._stores:
52+ raise ZStormError("Store not registered with ZStorm, or registered "
53+ "with another thread.")
54+
55+ data_manager = StoreDataManager(store, zstorm)
56+ zstorm.transaction_manager.get().join(data_manager)
57
58 # Unhook the event handler. It will be rehooked for the next transaction.
59 return False
60@@ -216,9 +227,10 @@
61
62 implements(IDataManager)
63
64- def __init__(self, store, transaction_manager):
65+ def __init__(self, store, zstorm):
66 self._store = store
67- self.transaction_manager = transaction_manager
68+ self._zstorm = zstorm
69+ self.transaction_manager = zstorm.transaction_manager
70
71 def abort(self, txn):
72 try:
73@@ -227,7 +239,7 @@
74 if self._store._register_for_txn:
75 self._store._event.hook(
76 "register-transaction", register_store_with_transaction,
77- self.transaction_manager)
78+ weakref.ref(self._zstorm))
79
80 def tpc_begin(self, txn):
81 # Zope's transaction system will call tpc_begin() on all
82@@ -245,7 +257,7 @@
83 if self._store._register_for_txn:
84 self._store._event.hook(
85 "register-transaction", register_store_with_transaction,
86- self.transaction_manager)
87+ weakref.ref(self._zstorm))
88
89 def tpc_vote(self, txn):
90 pass
91
92=== modified file 'tests/zope/zstorm.py'
93--- tests/zope/zstorm.py 2009-03-05 21:53:12 +0000
94+++ tests/zope/zstorm.py 2010-07-01 17:53:23 +0000
95@@ -18,7 +18,9 @@
96 # You should have received a copy of the GNU Lesser General Public License
97 # along with this program. If not, see <http://www.gnu.org/licenses/>.
98 #
99-import thread, weakref, gc
100+import threading
101+import weakref
102+import gc
103
104 from tests.helper import TestHelper
105 from tests.zope import has_transaction, has_zope_component
106@@ -77,18 +79,14 @@
107
108 raised = []
109
110- lock = thread.allocate_lock()
111- lock.acquire()
112 def f():
113 try:
114- try:
115- self.zstorm.get("name")
116- except ZStormError:
117- raised.append(True)
118- finally:
119- lock.release()
120- thread.start_new_thread(f, ())
121- lock.acquire()
122+ self.zstorm.get("name")
123+ except ZStormError:
124+ raised.append(True)
125+ thread = threading.Thread(target=f)
126+ thread.start()
127+ thread.join()
128
129 self.assertTrue(raised)
130
131@@ -218,6 +216,31 @@
132 store.execute("SELECT 1")
133 self.assertNotInTransaction(store)
134
135+ def test_wb_cross_thread_store_does_not_join_transaction(self):
136+ """If a zstorm registered thread crosses over to another thread,
137+ it will not be usable."""
138+ store = self.zstorm.get("name", "sqlite:")
139+
140+ failures = []
141+ def f():
142+ # We perform this twice to show that ZStormError is raised
143+ # consistently (i.e. not just the first time).
144+ for i in range(2):
145+ try:
146+ store.execute("SELECT 1")
147+ except ZStormError:
148+ failures.append("ZStormError raised")
149+ except Exception, exc:
150+ failures.append("Expected ZStormError, got %r" % exc)
151+ else:
152+ failures.append("Expected ZStormError, nothing raised")
153+ if self._isInTransaction(store):
154+ failures.append("store was joined to transaction")
155+ thread = threading.Thread(target=f)
156+ thread.start()
157+ thread.join()
158+ self.assertEqual(failures, ["ZStormError raised"] * 2)
159+
160 def test_wb_reset(self):
161 """_reset is used to reset the zstorm utility between zope test runs.
162 """

Subscribers

People subscribed via source and target branches

to status/vote changes: