|
17 | 17 | """ |
18 | 18 | import os |
19 | 19 | import pickle |
| 20 | +import threading |
| 21 | +import time |
20 | 22 | import traceback |
21 | 23 |
|
| 24 | +import redis |
| 25 | + |
22 | 26 | try: |
23 | 27 | from unittest import mock |
24 | 28 | except ImportError: # pragma: NO PY3 COVER |
@@ -346,3 +350,54 @@ def callback(): |
346 | 350 | error = error_info.value |
347 | 351 | message = "".join(traceback.format_exception_only(type(error), error)) |
348 | 352 | assert "hello dad" not in message |
| 353 | + |
| 354 | + |
| 355 | +@pytest.mark.skipif(not USE_REDIS_CACHE, reason="Redis is not configured") |
| 356 | +@pytest.mark.usefixtures("client_context") |
| 357 | +def test_parallel_threads_lookup_w_redis_cache(namespace, dispose_of): |
| 358 | + """Regression test for #496 |
| 359 | +
|
| 360 | + https://github.com/googleapis/python-ndb/issues/496 |
| 361 | + """ |
| 362 | + |
| 363 | + class MonkeyPipeline(redis.client.Pipeline): |
| 364 | + def mset(self, mapping): |
| 365 | + """Force a delay here to expose concurrency error.""" |
| 366 | + time.sleep(0.05) |
| 367 | + return super(MonkeyPipeline, self).mset(mapping) |
| 368 | + |
| 369 | + with mock.patch("redis.client.Pipeline", MonkeyPipeline): |
| 370 | + client = ndb.Client() |
| 371 | + global_cache = ndb.RedisCache.from_environment() |
| 372 | + activity = {"calls": 0} |
| 373 | + |
| 374 | + class SomeKind(ndb.Model): |
| 375 | + foo = ndb.IntegerProperty() |
| 376 | + |
| 377 | + class LookupThread(threading.Thread): |
| 378 | + def __init__(self, id): |
| 379 | + super(LookupThread, self).__init__() |
| 380 | + self.id = id |
| 381 | + |
| 382 | + def run(self): |
| 383 | + context = client.context( |
| 384 | + cache_policy=False, |
| 385 | + global_cache=global_cache, |
| 386 | + namespace=namespace, |
| 387 | + ) |
| 388 | + with context: |
| 389 | + entity = SomeKind.get_by_id(self.id) |
| 390 | + assert entity.foo == 42 |
| 391 | + activity["calls"] += 1 |
| 392 | + |
| 393 | + key = SomeKind(foo=42).put() |
| 394 | + dispose_of(key._key) |
| 395 | + id = key.id() |
| 396 | + |
| 397 | + thread1, thread2 = LookupThread(id), LookupThread(id) |
| 398 | + thread1.start() |
| 399 | + thread2.start() |
| 400 | + thread1.join() |
| 401 | + thread2.join() |
| 402 | + |
| 403 | + assert activity["calls"] == 2 |
0 commit comments