Skip to content

Commit 70ea839

Browse files
author
Chris Rossi
authored
fix: use fresh context cache for each transaction (#409)
In order to enforce transactional integrity, the context cache can't be shared across transactions. Fixes #394 (again)
1 parent ce9945b commit 70ea839

File tree

4 files changed

+89
-2
lines changed

4 files changed

+89
-2
lines changed

packages/google-cloud-ndb/google/cloud/ndb/_transaction.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,9 @@ def _transaction_async(context, callback, read_only=False):
128128

129129
on_commit_callbacks = []
130130
tx_context = context.new(
131-
transaction=transaction_id, on_commit_callbacks=on_commit_callbacks
131+
transaction=transaction_id,
132+
on_commit_callbacks=on_commit_callbacks,
133+
cache=None, # Use new, empty cache for transaction
132134
)
133135
with tx_context.use():
134136
try:

packages/google-cloud-ndb/tests/system/test_crud.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,6 +1173,7 @@ class SomeKind(ndb.Model):
11731173
assert retreived.foo == ["", ""]
11741174

11751175

1176+
@pytest.mark.skipif(not USE_REDIS_CACHE, reason="Redis is not configured")
11761177
@pytest.mark.usefixtures("redis_context")
11771178
def test_multi_get_weirdness_with_redis(dispose_of):
11781179
"""Regression test for issue #294.

packages/google-cloud-ndb/tests/system/test_misc.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@
1515
"""
1616
Difficult to classify regression tests.
1717
"""
18+
import os
1819
import pickle
1920

2021
import pytest
2122

2223
from google.cloud import ndb
2324

25+
USE_REDIS_CACHE = bool(os.environ.get("REDIS_CACHE_URL"))
26+
2427

2528
# Pickle can only pickle/unpickle global classes
2629
class PickleOtherKind(ndb.Model):
@@ -150,3 +153,81 @@ def concurrent_tasks(id):
150153

151154
entity = SomeKind.get_by_id(id)
152155
assert entity.foo == 242
156+
157+
158+
def test_parallel_transactions_w_context_cache(client_context, dispose_of):
159+
"""Regression test for Issue #394
160+
161+
https://github.com/googleapis/python-ndb/issues/394
162+
"""
163+
164+
class SomeKind(ndb.Model):
165+
foo = ndb.IntegerProperty()
166+
167+
@ndb.transactional_tasklet()
168+
def update(id, add, delay=0):
169+
entity = yield SomeKind.get_by_id_async(id)
170+
foo = entity.foo
171+
foo += add
172+
173+
yield ndb.sleep(delay)
174+
entity.foo = foo
175+
176+
yield entity.put_async()
177+
178+
@ndb.tasklet
179+
def concurrent_tasks(id):
180+
yield [
181+
update(id, 100),
182+
update(id, 100, 0.01),
183+
]
184+
185+
with client_context.new(cache_policy=None).use():
186+
key = SomeKind(foo=42).put()
187+
dispose_of(key._key)
188+
id = key.id()
189+
190+
concurrent_tasks(id).get_result()
191+
192+
entity = SomeKind.get_by_id(id)
193+
assert entity.foo == 242
194+
195+
196+
@pytest.mark.skipif(not USE_REDIS_CACHE, reason="Redis is not configured")
197+
@pytest.mark.usefixtures("redis_context")
198+
def test_parallel_transactions_w_redis_cache(dispose_of):
199+
"""Regression test for Issue #394
200+
201+
https://github.com/googleapis/python-ndb/issues/394
202+
"""
203+
204+
class SomeKind(ndb.Model):
205+
foo = ndb.IntegerProperty()
206+
207+
@ndb.transactional_tasklet()
208+
def update(id, add, delay=0):
209+
entity = yield SomeKind.get_by_id_async(id)
210+
foo = entity.foo
211+
foo += add
212+
213+
yield ndb.sleep(delay)
214+
entity.foo = foo
215+
216+
yield entity.put_async()
217+
218+
@ndb.tasklet
219+
def concurrent_tasks(id):
220+
yield [
221+
update(id, 100),
222+
update(id, 100, 0.01),
223+
]
224+
225+
key = SomeKind(foo=42).put()
226+
dispose_of(key._key)
227+
id = key.id()
228+
229+
SomeKind.get_by_id(id)
230+
concurrent_tasks(id).get_result()
231+
232+
entity = SomeKind.get_by_id(id)
233+
assert entity.foo == 242

packages/google-cloud-ndb/tests/unit/test__transaction.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,13 @@ class Test_transaction_async:
8484
@pytest.mark.usefixtures("in_context")
8585
@mock.patch("google.cloud.ndb._datastore_api")
8686
def test_success(_datastore_api):
87+
context_module.get_context().cache["foo"] = "bar"
8788
on_commit_callback = mock.Mock()
8889

8990
def callback():
90-
context_module.get_context().call_on_commit(on_commit_callback)
91+
context = context_module.get_context()
92+
assert not context.cache
93+
context.call_on_commit(on_commit_callback)
9194
return "I tried, momma."
9295

9396
begin_future = tasklets.Future("begin transaction")

0 commit comments

Comments
 (0)