mirror of
https://github.com/python/cpython.git
synced 2025-07-13 14:25:18 +00:00

Biased reference counting maintains two refcount fields in each object: `ob_ref_local` and `ob_ref_shared`. The true refcount is the sum of these two fields. In some cases, when refcounting operations are split across threads, the ob_ref_shared field can be negative (although the total refcount must be at least zero). In this case, the thread that decremented the refcount requests that the owning thread give up ownership and merge the refcount fields.
121 lines
4.2 KiB
Python
121 lines
4.2 KiB
Python
import threading
|
|
import time
|
|
import unittest
|
|
import weakref
|
|
from concurrent import futures
|
|
from test import support
|
|
from test.support import Py_GIL_DISABLED
|
|
|
|
|
|
def mul(x, y):
|
|
return x * y
|
|
|
|
def capture(*args, **kwargs):
|
|
return args, kwargs
|
|
|
|
|
|
class MyObject(object):
|
|
def my_method(self):
|
|
pass
|
|
|
|
|
|
def make_dummy_object(_):
|
|
return MyObject()
|
|
|
|
|
|
class ExecutorTest:
|
|
# Executor.shutdown() and context manager usage is tested by
|
|
# ExecutorShutdownTest.
|
|
def test_submit(self):
|
|
future = self.executor.submit(pow, 2, 8)
|
|
self.assertEqual(256, future.result())
|
|
|
|
def test_submit_keyword(self):
|
|
future = self.executor.submit(mul, 2, y=8)
|
|
self.assertEqual(16, future.result())
|
|
future = self.executor.submit(capture, 1, self=2, fn=3)
|
|
self.assertEqual(future.result(), ((1,), {'self': 2, 'fn': 3}))
|
|
with self.assertRaises(TypeError):
|
|
self.executor.submit(fn=capture, arg=1)
|
|
with self.assertRaises(TypeError):
|
|
self.executor.submit(arg=1)
|
|
|
|
def test_map(self):
|
|
self.assertEqual(
|
|
list(self.executor.map(pow, range(10), range(10))),
|
|
list(map(pow, range(10), range(10))))
|
|
|
|
self.assertEqual(
|
|
list(self.executor.map(pow, range(10), range(10), chunksize=3)),
|
|
list(map(pow, range(10), range(10))))
|
|
|
|
def test_map_exception(self):
|
|
i = self.executor.map(divmod, [1, 1, 1, 1], [2, 3, 0, 5])
|
|
self.assertEqual(i.__next__(), (0, 1))
|
|
self.assertEqual(i.__next__(), (0, 1))
|
|
self.assertRaises(ZeroDivisionError, i.__next__)
|
|
|
|
@support.requires_resource('walltime')
|
|
def test_map_timeout(self):
|
|
results = []
|
|
try:
|
|
for i in self.executor.map(time.sleep,
|
|
[0, 0, 6],
|
|
timeout=5):
|
|
results.append(i)
|
|
except futures.TimeoutError:
|
|
pass
|
|
else:
|
|
self.fail('expected TimeoutError')
|
|
|
|
self.assertEqual([None, None], results)
|
|
|
|
def test_shutdown_race_issue12456(self):
|
|
# Issue #12456: race condition at shutdown where trying to post a
|
|
# sentinel in the call queue blocks (the queue is full while processes
|
|
# have exited).
|
|
self.executor.map(str, [2] * (self.worker_count + 1))
|
|
self.executor.shutdown()
|
|
|
|
@support.cpython_only
|
|
def test_no_stale_references(self):
|
|
# Issue #16284: check that the executors don't unnecessarily hang onto
|
|
# references.
|
|
my_object = MyObject()
|
|
my_object_collected = threading.Event()
|
|
my_object_callback = weakref.ref(
|
|
my_object, lambda obj: my_object_collected.set())
|
|
fut = self.executor.submit(my_object.my_method)
|
|
del my_object
|
|
|
|
if Py_GIL_DISABLED:
|
|
# Due to biased reference counting, my_object might only be
|
|
# deallocated while the thread that created it runs -- if the
|
|
# thread is paused waiting on an event, it may not merge the
|
|
# refcount of the queued object. For that reason, we wait for the
|
|
# task to finish (so that it's no longer referenced) and force a
|
|
# GC to ensure that it is collected.
|
|
fut.result() # Wait for the task to finish.
|
|
support.gc_collect()
|
|
else:
|
|
del fut # Deliberately discard the future.
|
|
|
|
collected = my_object_collected.wait(timeout=support.SHORT_TIMEOUT)
|
|
self.assertTrue(collected,
|
|
"Stale reference not collected within timeout.")
|
|
|
|
def test_max_workers_negative(self):
|
|
for number in (0, -1):
|
|
with self.assertRaisesRegex(ValueError,
|
|
"max_workers must be greater "
|
|
"than 0"):
|
|
self.executor_type(max_workers=number)
|
|
|
|
def test_free_reference(self):
|
|
# Issue #14406: Result iterator should not keep an internal
|
|
# reference to result objects.
|
|
for obj in self.executor.map(make_dummy_object, range(10)):
|
|
wr = weakref.ref(obj)
|
|
del obj
|
|
support.gc_collect() # For PyPy or other GCs.
|
|
self.assertIsNone(wr())
|