8000 bpo-16575: Add checks for unions passed by value to functions. by vsajip · Pull Request #16430 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

bpo-16575: Add checks for unions passed by value to functions. #16430

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 149 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
149 commits
Select commit Hold shift + click to select a range
3796a4c
bpo-16575: Add checks for unions passed by value to functions.
vsajip Sep 26, 2019
f6f496a
Updated the logic for propagation of flags.
vsajip Sep 26, 2019
dc31b8f
Corrected typos in test.
vsajip Sep 26, 2019
b92f55a
Removed debugging code.
vsajip Sep 26, 2019
01ba08e
Improved tests.
vsajip Sep 26, 2019
0bcbfa4
bpo-28009: Fix uuid.uuid1() and uuid.get_node() on AIX (GH-8672)
aixtools Sep 26, 2019
6ce03ec
cleanup ababstractproperty in typing.py (GH-16432)
hongweipeng Sep 27, 2019
5faff97
bpo-38206: Clarify tp_dealloc requirements for heap allocated types. …
ammaraskar Sep 27, 2019
9055815
bpo-38270: More fixes for strict crypto policy (GH-16418)
tiran Sep 27, 2019
6693f73
bpo-38187: Fix a refleak in Tools/c-analyzer. (gh-16304)
ericsnowcurrently Sep 27, 2019
5d6f5b6
bpo-32820: Simplify __format__ implementation for ipaddress. (GH-16378)
serhiy-storchaka Sep 27, 2019
dd6117c
Fix typo in the "Porting to Python 3.8" section. (GH-16435)
bariod Sep 27, 2019
e8650a4
bpo-38243, xmlrpc.server: Escape the server_title (GH-16373)
corona10 Sep 27, 2019
52d1b86
bpo-38301: In Solaris family, we must be sure to use '-D_REENTRANT' (…
jcea Sep 28, 2019
441b10c
bpo-38304: Add PyConfig.struct_size (GH-16451)
vstinner Sep 28, 2019
7774d78
bpo-38216, bpo-36274: Allow subclasses to separately override validat…
jaraco Sep 28, 2019
c816503
bpo-38115: Deal with invalid bytecode offsets in lnotab (GH-16079)
Yhg1s Sep 28, 2019
c5a7e0c
bpo-37408: Precise that Tarfile "format" argument only concerns writi…
pakal Sep 28, 2019
f185a73
bpo-38310: Predict BUILD_MAP_UNPACK_WITH_CALL -> CALL_FUNCTION_EX. (G…
brandtbucher Sep 29, 2019
9a7d951
bpo-38108: Makes mock objects inherit from Base (GH-16060)
lisroach Sep 29, 2019
58498bc
bpo-38019: correctly handle pause/resume reading of closed asyncio un…
asvetlov Sep 29, 2019
fb4ae15
bpo-38317: Fix PyConfig.warnoptions priority (GH-16478)
vstinner Sep 29, 2019
25e115e
bpo-38161: Removes _AwaitEvent from AsyncMock. (GH-16443)
lisroach Sep 30, 2019
5bcc6d8
bpo-37096: Add large-file tests for modules using sendfile(2) (GH-13676)
giampaolo Sep 30, 2019
3667e1e
bpo-38163: Child mocks detect their type as sync or async (GH-16471)
lisroach Sep 30, 2019
6758e6e
bpo-38242: Revert "bpo-36889: Merge asyncio streams (GH-13251)" (#16482)
1st1 Sep 30, 2019
fc4a044
bpo-30773: Fix ag_running; prohibit running athrow/asend/aclose in pa…
1st1 Sep 30, 2019
89f8177
bpo-38304: Fix PyConfig usage in python_uwp.cpp (GH-16487)
vstinner Sep 30, 2019
dec3971
bpo-38322: Fix gotlandmark() of PC/getpathp.c (GH-16489)
vstinner Sep 30, 2019
efe74b6
bpo-38321: Fix _asynciomodule.c compiler warning (GH-16493)
vstinner Sep 30, 2019
c9a413e
bpo-38321: Fix PyCStructUnionType_update_stgdict() warning (GH-16492)
vstinner Sep 30, 2019
bcda460
Clear weakrefs in garbage found by the GC (#16495)
nascheme Sep 30, 2019
aca8c40
bpo-38321: Fix _testcapimodule.c warning (GH-16494)
vstinner Sep 30, 2019
e407013
Fix and improve `asyncio.run()` docs (GH-16403)
aeros Oct 1, 2019
cf57cab
bpo-32689: Updates shutil.move to allow for Path objects to be used a…
maxwellmckinnon Oct 1, 2019
94e1650
bpo-38319: Fix shutil._fastcopy_sendfile(): set sendfile() max block …
giampaolo Oct 1, 2019
3c30a76
bpo-38304: Remove PyConfig.struct_size (GH-16500) (GH-16508)
vstinner Oct 1, 2019
8462a49
bpo-38304: PyConfig_InitPythonConfig() cannot fail anymore (GH-16509)
vstinner Oct 1, 2019
982bfa4
bpo-36670: Multiple regrtest bugfixes (GH-16511)
vstinner Oct 1, 2019
2f90261
bpo-38321: Fix compiler warning in _randommodule.c (GH-16512)
vstinner Oct 1, 2019
5e0ea75
bpo-37474: Don't call fedisableexcept() on FreeBSD (GH-16515)
vstinner Oct 1, 2019
6314abc
bpo-37802: Fix a compiler warning in longobject.c (GH-16517)
vstinner Oct 1, 2019
b9a8b82
bpo-38343: Fixes version handling for nuget packages (GH-16527)
zooba Oct 1, 2019
2ea71a0
bpo-36670: regrtest bug fixes (GH-16537)
vstinner Oct 2, 2019
b3e7045
bpo-38338, test.pythoninfo: add more ssl infos (GH-16539)
vstinner Oct 2, 2019
61691d8
bpo-38353: Cleanup includes in the internal C API (GH-16548)
vstinner Oct 2, 2019
3e04cd2
bpo-36670, regrtest: Fix WindowsLoadTracker() for partial line (GH-16…
vstinner Oct 2, 2019
c65119d
bpo-36670: Enhance regrtest WindowsLoadTracker (GH-16553)
vstinner Oct 3, 2019
098e256
bpo-36670: Enhance regrtest (GH-16556)
vstinner Oct 3, 2019
a0e3d27
bpo-38355: Fix ntpath.realpath failing on sys.executable (GH-16551)
zooba Oct 3, 2019
353fb1e
bpo-38359: Ensures pyw.exe launcher reads correct registry key (GH-16…
zooba Oct 3, 2019
b23a842
bpo-34344 Fix AbstractEventLoopPolicy.get_event_loop docstring (GH-16…
idomic Oct 3, 2019
86ec5c6
bpo-38353: Fix calculate_argv0_path() for symlinks (GH-16549)
vstinner Oct 3, 2019
c515b57
bpo-38353: Fix compiler warning in pycore_initconfig.h (GH-16570)
vstinner Oct 3, 2019
e982d8b
bpo-38353: Fix compiler warning in internal headers (GH-16573)
vstinner Oct 4, 2019
03a8a56
bpo-38353: Add subfunctions to getpath.c (GH-16572)
vstinner Oct 4, 2019
f0c8579
bpo-38353: Rework ismodule() in getpath.c (GH-16574)
vstinner Oct 4, 2019
3142c66
bpo-38235: Correct some arguments names in logging documentation (GH-…
AWhetter Oct 4, 2019
2290b23
Updated CODEOWNERS to indicate ownership of some modules. (GH-16578)
vsajip Oct 4, 2019
06cb94b
bpo-13153: Use OS native encoding for converting between Python and T…
serhiy-storchaka Oct 4, 2019
8855e47
bpo-38266: Revert bpo-37878: Make PyThreadState_DeleteCurrent() Inter…
nanjekyejoannah Oct 4, 2019
abd7cd8
bpo-38353: getpath.c uses dynamically allocated strings (GH-16582)
vstinner Oct 4, 2019
c02b41b
bpo-38353: getpath.c: allocates strings on the heap (GH-16585)
vstinner Oct 4, 2019
3faf826
bpo-38341: Add SMTPNotSupportedError in the exports of smtplib (#16525)
rockwelln Oct 5, 2019
65dcc8a
bpo-38332: Catch KeyError from unknown cte in encoded-word. (GH-16503)
aft90 Oct 5, 2019
c38e725
bpo-38210: Fix intersection operation with dict view and iterator. (G…
corona10 Oct 6, 2019
24ddd9c
bpo-38383: Fix possible integer overflow in startswith() of bytes and…
shihai1991 Oct 6, 2019
9e71917
bpo-26510: Add versionchanged for required arg of add_subparsers (GH…
adamjstewart Oct 7, 2019
ef092fe
bpo-25988: Do not expose abstract collection classes in the collectio…
serhiy-storchaka Oct 7, 2019
ed8efd8
Fix a compile warning in dictobject.c (GH-16610)
shihai1991 Oct 7, 2019
d97f1ce
bpo-38210: Fix compiler warning in dictobject.c (GH-16611)
vstinner Oct 7, 2019
b96145a
bpo-38353: Simplify calculate_pybuilddir() (GH-16614)
vstinner Oct 7, 2019
038503e
bpo-38391: Fixing a typo for Py_DECREF (GH-16616)
kmoza Oct 7, 2019
303475e
Fix a compile warning in selectmodule.c. (GH-16617)
zhangyangyu Oct 7, 2019
321def8
bpo-36356: Fix memory leak in _asynciomodule.c (GH-16598)
btharper Oct 7, 2019
6876257
bpo-36389: _PyObject_CheckConsistency() available in release mode (GH…
vstinner Oct 7, 2019
60ec6ef
bpo-36389: Fix _PyBytesWriter in release mode (GH-16624)
vstinner Oct 7, 2019
15ae75d
bpo-38294: Add list of no-longer-escaped chars to re.escape documenta…
rbanffy Oct 7, 2019
e310af9
bpo-38344: Fix syntax in activate.bat (GH-16533)
jamesabel Oct 7, 2019
7775349
bpo-36389: Add newline to _PyObject_AssertFailed() (GH-16629)
vstinner Oct 7, 2019
1b18455
bpo-38392: PyObject_GC_Track() validates object in debug mode (GH-16615)
vstinner Oct 7, 2019
36e33c3
bpo-38400 Don't check for NULL linked list pointers in _PyObject_IsFr…
pablogsal Oct 7, 2019
4d5f94b
bpo-38070: Enhance visit_decref() debug trace (GH-16631)
vstinner Oct 8, 2019
0d3fe8a
closes bpo-38402: Check error of primitive crypt/crypt_r. (GH-16599)
chibby0ne Oct 8, 2019
5dfbb4d
Fix typo in _warnings.warn_explicit() docstring (GH-16625)
hansrajdas Oct 8, 2019
d7c3873
bpo-33714: Output an exception raised in module's m_clear(). (GH-16592)
serhiy-storchaka Oct 8, 2019
d05b000
bpo-38371: Tkinter: deprecate the split() method. (GH-16584)
serhiy-storchaka Oct 8, 2019
b690a27
bpo-36698: IDLE no longer fails when write non-encodable characters t…
serhiy-storchaka Oct 8, 2019
13abda4
bpo-38405: Make nested subclasses of typing.NamedTuple pickleable. (G…
serhiy-storchaka Oct 8, 2019
8252c52
bpo-38407: Add docstrings for typing.SupportsXXX classes. (GH-16644)
serhiy-storchaka Oct 8, 2019
03ab6b4
bpo-38118: Ignore Valgrind false alarm in PyUnicode_Decode() (GH-16651)
vstinner Oct 8, 2019
10cd00a
bpo-38395: Fix ownership in weakref.proxy methods (GH-16632)
pablogsal Oct 8, 2019
e53c580
test_dictviews: Add testcase for dictviews_sub (GH-16660)
corona10 Oct 8, 2019
0ec618a
bpo-37531: regrtest ignores output on timeout (GH-16659)
vstinner Oct 8, 2019
e8bedbd
bpo-38368: Added fix for ctypes crash when handling arrays in structs…
vsajip Oct 8, 2019
594e2ed
closes bpo-36161: Use thread-safe ttyname_r instead of ttyname. (GH-1…
chibby0ne Oct 9, 2019
01171eb
Typo fix: "empy" should be "empty". (GH-16666)
hansrajdas Oct 9, 2019
ecbf35f
bpo-38379: don't claim objects are collected when they aren't (#16658)
tim-one Oct 9, 2019
09895c2
bpo-38409: Fix grammar in str.strip() docstring (GH-16682)
zware Oct 9, 2019
a544773
bpo-38392: Only declare visit_validate() if Py_DEBUG is defined (GH-1…
vstinner Oct 10, 2019
7bb1431
bpo-38109: Add missing constants to Lib/stat.py (GH-16665)
rlamy Oct 10, 2019
a05fcd3
bpo-38425: Fix ‘res’ may be used uninitialized warning (GH-16688)
corona10 Oct 10, 2019
d47f0dd
bpo-32996: Documentation fix-up. (GH-16646)
eirrgang Oct 10, 2019
b6e0fc7
bpo-38353: Fix typos in calculate_argv0_path_framework() (GH-16695)
vstinner Oct 10, 2019
1dbe537
Re-enable the OverflowError test for test_truediv on test_complex (GH…
corona10 Oct 10, 2019
d565fb9
bpo-38282: Rewrite getsockaddrarg() helper function (GH-16698)
vstinner Oct 10, 2019
72bbd2a
Remove AppVeyor badge now that we don't use it in the CI anymore (GH-…
pablogsal Oct 10, 2019
320dd50
bpo-38437: Activate GC_DEBUG when PY_DEBUG is set (GH-16707)
pablogsal Oct 10, 2019
f900064
docs: Add asyncio source code links (GH-16640)
aeros Oct 10, 2019
a8e0d31
Typo fix: "throuhgh" should be "through". (GH-16704)
hansrajdas Oct 11, 2019
c39d1dd
Fix strict-aliasing rules errors on gcc 4.8.5. (GH-16714)
corona10 Oct 11, 2019
cbb5481
bpo-38442: Remove an execution bit from Doc/whatsnew/3.8.rst. (GH-16715)
serhiy-storchaka Oct 11, 2019
2b7dc40
bpo-38347: find pathfix for Python scripts whose name contain a '-' (…
rpluem Oct 11, 2019
19a3d87
bpo-38449: Revert "bpo-22347: Update mimetypes.guess_type to allow op…
maxking Oct 12, 2019
822922a
bpo-35800: Deprecate smtpd.MailmanProxy (GH-11675)
samuelcolvin Oct 12, 2019
e634da2
Announce the change in the CancelledError inheritance (GH-16730)
pgjones Oct 12, 2019
547c60c
Fix minor typos in Whatsnew
hugovk Oct 12, 2019
8177404
bpo-37731: Reorder includes in xmltok.c to avoid redefinition of _POS…
pablogsal Oct 12, 2019
f3751ef
bpo-38417: Add umask support to subprocess (GH-16726)
gpshead Oct 12, 2019
67b93f8
bpo-38456: Use /bin/true in test_subprocess (GH-16736)
gpshead Oct 12, 2019
27b33fb
bpo-38282: Correctly manage the Bluetooth L2CAP socket structure in F…
pablogsal Oct 13, 2019
46113e0
bpo-38456: Handle the case when there is no 'true' command (GH-16739)
pablogsal Oct 13, 2019
140a7d1
bpo-38378: Rename parameters "out" and "in" of os.sendfile(). (GH-16742)
serhiy-storchaka Oct 13, 2019
793cb85
bpo-38431: Fix __repr__ method of InitVar to work with typing objects…
samuelcolvin Oct 13, 2019
b16e382
bpo-38202: Fix a crash in dict_view & non-itearble. (GH-16241)
ZackerySpytz Oct 13, 2019
e3babbd
Correct signature of __build_class__ (GH-16735)
pablogsal Oct 13, 2019
466326d
bpo-38379: Don't block collection of unreachable objects when some ob…
pablogsal Oct 13, 2019
bb78f6c
Rebased version of what's new PR (#16745)
akuchling Oct 13, 2019
8144095
bpo-28556: Remove another mention of metaclass of Generic in typing d…
ilevkivskyi Oct 13, 2019
fdfe283
bpo-38467: Fix argument name of typing functions (GH-16753)
srittau Oct 13, 2019
95bfc8a
Misc gc code & comment cleanups. (GH-16752)
tim-one Oct 13, 2019
8a6cbf8
bpo-38464: Document parameter for NormalDist.quantiles() (GH-16757)
rhettinger Oct 14, 2019
fd5c414
bpo-38469: Handle named expression scope with global/nonlocal keyword…
pablogsal Oct 14, 2019
61a6db5
bpo-38461 and bpo-38463: Minor fixes to Whatsnew 3.8 (GH-16761)
rhettinger Oct 14, 2019
a329153
bpo-37759: Add examples for the new typing features (GH-16763)
rhettinger Oct 14, 2019
9cb51f4
Update macOS installer display files for 3.9.0a1 (GH-16765)
ned-deily Oct 14, 2019
aad2ee0
bpo-32498: urllib.parse.unquote also accepts bytes (GH-7768)
stein-k Oct 14, 2019
19d6842
Update build docs for macOS (GH-16771)
ned-deily Oct 14, 2019
298439c
bpo-37759: Polish What's New in Python 3.8. (#16769)
serhiy-storchaka Oct 14, 2019
d83fc27
bpo-38453: Resolve test directories before chdir to them (GH-16723)
zooba Oct 14, 2019
274bd01
Remove draft status. Add asyncio REPL example (GH-16785)
rhettinger Oct 14, 2019
4504b45
Doc: 3.8 is now stable. (GH-16790)
JulienPalard Oct 14, 2019
4d20228
bpo-38133: Update docs to reflect fixes to py.exe launcher (GH-16791)
zooba Oct 14, 2019
0b60f64
bpo-11410: Standardize and use symbol visibility attributes across PO…
vsajip Oct 15, 2019
0d44e8c
bpo-16575: Add checks for unions passed by value to functions.
vsajip Sep 26, 2019
ed270bf
Updated the logic for propagation of flags.
vsajip Sep 26, 2019
b798ff6
Corrected typos in test.
vsajip Sep 26, 2019
2f21671
Improved tests.
vsajip Sep 26, 2019
e3afbbe
Rebased with master and resolved conflicts.
vsajip Oct 15, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
bpo-38379: Don't block collection of unreachable objects when some ob…
…jects resurrect (GH-16687)

Currently if any finalizer invoked during garbage collection resurrects any object, the gc gives up and aborts the collection. Although finalizers are assured to only run once per object, this behaviour of the gc can lead to an ever-increasing memory situation if new resurrecting objects are allocated in every new gc collection.

To avoid this, recompute what objects among the unreachable set need to be resurrected and what objects can be safely collected. In this way, resurrecting objects will not block the collection of other objects in the unreachable set.
  • Loading branch information
pablogsal authored Oct 13, 2019
commit 466326dcdf038b948d94302c315be407c73e60d1
98 changes: 82 additions & 16 deletions Lib/test/test_gc.py
Original file line number Diff line number Diff line change
Expand Up @@ -822,7 +822,78 @@ def test_get_objects_arguments(self):
self.assertRaises(TypeError, gc.get_objects, "1")
self.assertRaises(TypeError, gc.get_objects, 1.234)

def test_38379(self):
def test_resurrection_only_happens_once_per_object(self):
class A: # simple self-loop
def __init__(self):
self.me = self

class Lazarus(A):
resurrected = 0
resurrected_instances = []

def __del__(self):
Lazarus.resurrected += 1
Lazarus.resurrected_instances.append(self)

gc.collect()
gc.disable()

# We start with 0 resurrections
laz = Lazarus()
self.assertEqual(Lazarus.resurrected, 0)

# Deleting the instance and triggering a collection
# resurrects the object
del laz
gc.collect()
self.assertEqual(Lazarus.resurrected, 1)
self.assertEqual(len(Lazarus.resurrected_instances), 1)

# Clearing the references and forcing a collection
# should not resurrect the object again.
Lazarus.resurrected_instances.clear()
self.assertEqual(Lazarus.resurrected, 1)
gc.collect()
se 8000 lf.assertEqual(Lazarus.resurrected, 1)

gc.enable()

def test_resurrection_is_transitive(self):
class Cargo:
def __init__(self):
self.me = self

class Lazarus:
resurrected_instances = []

def __del__(self):
Lazarus.resurrected_instances.append(self)

gc.collect()
gc.disable()

laz = Lazarus()
cargo = Cargo()
cargo_id = id(cargo)

# Create a cycle between cargo and laz
laz.cargo = cargo
cargo.laz = laz

# Drop the references, force a collection and check that
# everything was resurrected.
del laz, cargo
gc.collect()
self.assertEqual(len(Lazarus.resurrected_instances), 1)
instance = Lazarus.resurrected_instances.pop()
self.assertTrue(hasattr(instance, "cargo"))
self.assertEqual(id(instance.cargo), cargo_id)

gc.collect()
gc.enable()

def test_resurrection_does_not_block_cleanup_of_other_objects(self):

# When a finalizer resurrects objects, stats were reporting them as
# having been collected. This affected both collect()'s return
# value and the dicts returned by get_stats().
Expand Down Expand Up @@ -861,34 +932,29 @@ def getstats():
# Nothing is collected - Z() is merely resurrected.
t = gc.collect()
c, nc = getstats()
#self.assertEqual(t, 2) # before
self.assertEqual(t, 0) # after
#self.assertEqual(c - oldc, 2) # before
self.assertEqual(c - oldc, 0) # after
self.assertEqual(t, 0)
self.assertEqual(c - oldc, 0)
self.assertEqual(nc - oldnc, 0)

# Unfortunately, a Z() prevents _anything_ from being collected.
# It should be possible to collect the A instances anyway, but
# that will require non-trivial code changes.
# Z() should not prevent anything else from being collected.
oldc, oldnc = c, nc
for i in range(N):
A()
Z()
# Z() prevents anything from being collected.
t = gc.collect()
c, nc = getstats()
#self.assertEqual(t, 2*N + 2) # before
self.assertEqual(t, 0) # after
#self.assertEqual(c - oldc, 2*N + 2) # before
self.assertEqual(c - oldc, 0) # after
self.assertEqual(t, 2*N)
self.assertEqual(c - oldc, 2*N)
self.assertEqual(nc - oldnc, 0)

# But the A() trash is reclaimed on the next run.
# The A() trash should have been reclaimed already but the
# 2 copies of Z are still in zs (and the associated dicts).
oldc, oldnc = c, nc
zs.clear()
t = gc.collect()
c, nc = getstats()
self.assertEqual(t, 2*N)
self.assertEqual(c - oldc, 2*N)
self.assertEqual(t, 4)
self.assertEqual(c - oldc, 4)
self.assertEqual(nc - oldnc, 0)

gc.enable()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
When the garbage collector makes a collection in which some objects
resurrect (they are reachable from outside the isolated cycles after the
finalizers have been executed), do not block the collection of all objects
that are still unreachable. Patch by Pablo Galindo and Tim Peters.
182 changes: 122 additions & 60 deletions Modules/gcmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -301,8 +301,18 @@ gc_list_size(PyGC_Head *list)
return n;
}

/* Walk the list and mark all objects as non-collecting */
static inline void
gc_list_clear_collecting(PyGC_Head *collectable)
{
PyGC_Head *gc;
for (gc = GC_NEXT(collectable); gc != collectable; gc = GC_NEXT(gc)) {
gc_clear_collecting(gc);
}
}

/* Append objects in a GC list to a Python list.
* Return 0 if all OK, < 0 if error (out of memory for list).
* Return 0 if all OK, < 0 if error (out of memory for list)
*/
static int
append_objects(PyObject *py_list, PyGC_Head *gc_list)
Expand Down Expand Up @@ -613,6 +623,22 @@ move_legacy_finalizers(PyGC_Head *unreachable, PyGC_Head *finalizers)
}
}

static inline void
clear_unreachable_mask(PyGC_Head *unreachable)
{
/* Check that the list head does not have the unreachable bit set */
assert(((uintptr_t)unreachable & NEXT_MASK_UNREACHABLE) == 0);

PyGC_Head *gc, *next;
unreachable->_gc_next &= ~NEXT_MASK_UNREACHABLE;
for (gc = GC_NEXT(unreachable); gc != unreachable; gc = next) {
_PyObject_ASSERT((PyObject*)FROM_GC(gc), gc->_gc_next & NEXT_MASK_UNREACHABLE);
gc->_gc_next &= ~NEXT_MASK_UNREACHABLE;
next = (PyGC_Head*)gc->_gc_next;
}
validate_list(unreachable, PREV_MASK_COLLECTING);
}

/* A traversal callback for move_legacy_finalizer_reachable. */
static int
visit_move(PyObject *op, PyGC_Head *tolist)
Expand Down Expand Up @@ -891,36 +917,6 @@ finalize_garbage(PyGC_Head *collectable)
gc_list_merge(&seen, collectable);
}

/* Walk the collectable list and check that they are really unreachable
from the outside (some objects could have been resurrected by a
finalizer). */
static int
check_garbage(PyGC_Head *collectable)
{
int ret = 0;
PyGC_Head *gc;
for (gc = GC_NEXT(collectable); gc != collectable; gc = GC_NEXT(gc)) {
// Use gc_refs and break gc_prev again.
gc_set_refs(gc, Py_REFCNT(FROM_GC(gc)));
_PyObject_ASSERT(FROM_GC(gc), gc_get_refs(gc) != 0);
}
subtract_refs(collectable);
PyGC_Head *prev = collectable;
for (gc = GC_NEXT(collectable); gc != collectable; gc = GC_NEXT(gc)) {
_PyObject_ASSERT_WITH_MSG(FROM_GC(gc),
gc_get_refs(gc) >= 0,
"refcount is too small");
if (gc_get_refs(gc) != 0) {
ret = -1;
}
// Restore gc_prev here.
_PyGCHead_SET_PREV(gc, prev);
gc_clear_collecting(gc);
prev = gc;
}
return ret;
}

/* Break reference cycles by clearing the containers involved. This is
* tricky business as the lists can be changing and we don't know which
* objects may be freed. It is possible I screwed something up here.
Expand Down Expand Up @@ -958,6 +954,7 @@ delete_garbage(struct _gc_runtime_state *state,
}
if (GC_NEXT(collectable) == gc) {
/* object is still alive, move it, it may die later */
gc_clear_collecting(gc);
gc_list_move(gc, old);
}
}
Expand Down Expand Up @@ -1003,6 +1000,87 @@ show_stats_each_generations(struct _gc_runtime_state *state)
buf, gc_list_size(&state->permanent_generation.head));
}

/* Deduce wich objects among "base" are unreachable from outside the list
and move them to 'unreachable'. The process consist in the following steps:

1. Copy all reference counts to a different field (gc_prev is used to hold
this copy to save memory).
2. Traverse all objects in "base" and visit all referred objects using
"tp_traverse" and for every visited object, substract 1 to the reference
count (the one that we copied in the previous step). After this step, all
objects that can be reached directly from outside must have strictly positive
reference count, while all unreachable objects must have a count of exactly 0.
3. Indentify all unreachable objects (the ones with 0 reference count) and move
them to the "unreachable" list. This step also needs to move back to "base" all
objects that were initially marked as unreachable but are referred transitively
by the reachable objects (the ones with strictly positive reference count).

Contracts:

* The "base" has to be a valid list with no mask set.

* The "unreachable" list must be uninitialized (this function calls
gc_list_init over 'unreachable').

IMPORTANT: This function leaves 'unreachable' with the NEXT_MASK_UNREACHABLE
flag set but it does not clear it to skip unnecessary iteration. Before the
flag is cleared (for example, by using 'clear_unreachable_mask' function or
by a call to 'move_legacy_finalizers'), the 'unreachable' list is not a normal
list and we can not use most gc_list_* functions for it. */
static inline void
deduce_unreachable(PyGC_Head *base, PyGC_Head *unreachable) {
validate_list(base, 0);
/* Using ob_refcnt and gc_refs, calculate which objects in the
* container set are reachable from outside the set (i.e., have a
* refcount greater than 0 when all the references within the
* set are taken into account).
*/
update_refs(base); // gc_prev is used for gc_refs
subtract_refs(base);

/* Leave everything reachable from outside base in base, and move
* everything else (in base) to unreachable.
* NOTE: This used to move the reachable objects into a reachable
* set instead. But most things usually turn out to be reachable,
* so it's more efficient to move the unreachable things.
*/
gc_list_init(unreachable);
move_unreachable(base, unreachable); // gc_prev is pointer again
validate_list(base, 0);
}

/* Handle objects that may have resurrected after a call to 'finalize_garbage', moving
them to 'old_generation' and placing the rest on 'still_unreachable'.

Contracts:
* After this function 'unreachable' must not be used anymore and 'still_unreachable'
will contain the objects that did not resurrect.

* The "still_unreachable" list must be uninitialized (this function calls
gc_list_init over 'still_unreachable').

IMPORTANT: After a call to this function, the 'still_unreachable' set will have the
PREV_MARK_COLLECTING set, but the objects in this set are going to be removed so
we can skip the expense of clearing the flag to avoid extra iteration. */
static inline void
handle_resurrected_objects(PyGC_Head *unreachable, PyGC_Head* still_unreachable,
PyGC_Head *old_generation)
{
// Remove the PREV_MASK_COLLECTING from unreachable
// to prepare it for a new call to 'deduce_unreachable'
gc_list_clear_collecting(unreachable);

// After the call to deduce_unreachable, the 'still_unreachable' set will
// have the PREV_MARK_COLLECTING set, but the objects are going to be
// removed so we can skip the expense of clearing the flag.
PyGC_Head* resurrected = unreachable;
deduce_unreachable(resurrected, still_unreachable);
clear_unreachable_mask(still_unreachable);

// Move the resurrected objects to the old generation for future collection.
gc_list_merge(resurrected, old_generation);
}

/* This is the main function. Read this to understand how the
* collection process works. */
static Py_ssize_t
Expand Down Expand Up @@ -1045,26 +1123,9 @@ collect(struct _gc_runtime_state *state, int generation,
old = GEN_HEAD(state, generation+1);
else
old = young;

validate_list(young, 0);
validate_list(old, 0);
/* Using ob_refcnt and gc_refs, calculate which objects in the
* container set are reachable from outside the set (i.e., have a
* refcount greater than 0 when all the references within the
* set are taken into account).
*/
update_refs(young); // gc_prev is used for gc_refs
subtract_refs(young);

/* Leave everything reachable from outside young in young, and move
* everything else (in young) to unreachable.
* NOTE: This used to move the reachable objects into a reachable
* set instead. But most things usually turn out to be reachable,
* so it's more efficient to move the unreachable things.
*/
gc_list_init(&unreachable);
move_unreachable(young, &unreachable); // gc_prev is pointer again
validate_list(young, 0);
deduce_unreachable(young, &unreachable);

untrack_tuples(young);
/* Move reachable objects to next generation. */
Expand Down Expand Up @@ -1114,17 +1175,18 @@ collect(struct _gc_runtime_state *state, int generation,
/* Call tp_finalize on objects which have one. */
finalize_garbage(&unreachable);

if (check_garbage(&unreachable)) { // clear PREV_MASK_COLLECTING here
gc_list_merge(&unreachable, old);
}
else {
/* Call tp_clear on objects in the unreachable set. This will cause
* the reference cycles to be broken. It may also cause some objects
* in finalizers to be freed.
*/
m += gc_list_size(&unreachable);
delete_garbage(state, &unreachable, old);
}
/* Handle any objects that may have resurrected after the call
* to 'finalize_garbage' and continue the collection with the
* objects that are still unreachable */
PyGC_Head final_unreachable;
handle_resurrected_objects(&unreachable, &final_unreachable, old);

/* Call tp_clear on objects in the final_unreachable set. This will cause
* the reference cycles to be broken. It may also cause some objects
* in finalizers to be freed.
*/
m += gc_list_size(&final_unreachable);
delete_garbage(state, &final_unreachable, old);

/* Collect statistics on uncollectable objects found and print
* debugging information. */
Expand Down
0