summaryrefslogtreecommitdiff
path: root/venv/lib/python3.11/site-packages/greenlet/tests/test_greenlet_trash.py
diff options
context:
space:
mode:
authorcyfraeviolae <cyfraeviolae>2024-04-03 03:17:55 -0400
committercyfraeviolae <cyfraeviolae>2024-04-03 03:17:55 -0400
commit12cf076118570eebbff08c6b3090e0d4798447a1 (patch)
tree3ba25e17e3c3a5e82316558ba3864b955919ff72 /venv/lib/python3.11/site-packages/greenlet/tests/test_greenlet_trash.py
parentc45662ff3923b34614ddcc8feb9195541166dcc5 (diff)
no venv
Diffstat (limited to 'venv/lib/python3.11/site-packages/greenlet/tests/test_greenlet_trash.py')
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/tests/test_greenlet_trash.py178
1 files changed, 0 insertions, 178 deletions
diff --git a/venv/lib/python3.11/site-packages/greenlet/tests/test_greenlet_trash.py b/venv/lib/python3.11/site-packages/greenlet/tests/test_greenlet_trash.py
deleted file mode 100644
index 8d9716e..0000000
--- a/venv/lib/python3.11/site-packages/greenlet/tests/test_greenlet_trash.py
+++ /dev/null
@@ -1,178 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-Tests for greenlets interacting with the CPython trash can API.
-
-The CPython trash can API is not designed to be re-entered from a
-single thread. But this can happen using greenlets, if something
-during the object deallocation process switches greenlets, and this second
-greenlet then causes the trash can to get entered again. Here, we do this
-very explicitly, but in other cases (like gevent) it could be arbitrarily more
-complicated: for example, a weakref callback might try to acquire a lock that's
-already held by another greenlet; that would allow a greenlet switch to occur.
-
-See https://github.com/gevent/gevent/issues/1909
-
-This test is fragile and relies on details of the CPython
-implementation (like most of the rest of this package):
-
- - We enter the trashcan and deferred deallocation after
- ``_PyTrash_UNWIND_LEVEL`` calls. This constant, defined in
- CPython's object.c, is generally 50. That's basically how many objects are required to
- get us into the deferred deallocation situation.
-
- - The test fails by hitting an ``assert()`` in object.c; if the
- build didn't enable assert, then we don't catch this.
-
- - If the test fails in that way, the interpreter crashes.
-"""
-from __future__ import print_function, absolute_import, division
-
-import unittest
-
-class TestTrashCanReEnter(unittest.TestCase):
-
- def test_it(self):
- # Try several times to trigger it, because it isn't 100%
- # reliable.
- for _ in range(10):
- self.check_it()
-
- def check_it(self): # pylint:disable=too-many-statements
- import greenlet
- from greenlet._greenlet import get_tstate_trash_delete_nesting # pylint:disable=no-name-in-module
-
- main = greenlet.getcurrent()
-
- assert get_tstate_trash_delete_nesting() == 0
-
- # We expect to be in deferred deallocation after this many
- # deallocations have occurred. TODO: I wish we had a better way to do
- # this --- that was before get_tstate_trash_delete_nesting; perhaps
- # we can use that API to do better?
- TRASH_UNWIND_LEVEL = 50
- # How many objects to put in a container; it's the container that
- # queues objects for deferred deallocation.
- OBJECTS_PER_CONTAINER = 500
-
- class Dealloc: # define the class here because we alter class variables each time we run.
- """
- An object with a ``__del__`` method. When it starts getting deallocated
- from a deferred trash can run, it switches greenlets, allocates more objects
- which then also go in the trash can. If we don't save state appropriately,
- nesting gets out of order and we can crash the interpreter.
- """
-
- #: Has our deallocation actually run and switched greenlets?
- #: When it does, this will be set to the current greenlet. This should
- #: be happening in the main greenlet, so we check that down below.
- SPAWNED = False
-
- #: Has the background greenlet run?
- BG_RAN = False
-
- BG_GLET = None
-
- #: How many of these things have ever been allocated.
- CREATED = 0
-
- #: How many of these things have ever been deallocated.
- DESTROYED = 0
-
- #: How many were destroyed not in the main greenlet. There should always
- #: be some.
- #: If the test is broken or things change in the trashcan implementation,
- #: this may not be correct.
- DESTROYED_BG = 0
-
- def __init__(self, sequence_number):
- """
- :param sequence_number: The ordinal of this object during
- one particular creation run. This is used to detect (guess, really)
- when we have entered the trash can's deferred deallocation.
- """
- self.i = sequence_number
- Dealloc.CREATED += 1
-
- def __del__(self):
- if self.i == TRASH_UNWIND_LEVEL and not self.SPAWNED:
- Dealloc.SPAWNED = greenlet.getcurrent()
- other = Dealloc.BG_GLET = greenlet.greenlet(background_greenlet)
- x = other.switch()
- assert x == 42
- # It's important that we don't switch back to the greenlet,
- # we leave it hanging there in an incomplete state. But we don't let it
- # get collected, either. If we complete it now, while we're still
- # in the scope of the initial trash can, things work out and we
- # don't see the problem. We need this greenlet to complete
- # at some point in the future, after we've exited this trash can invocation.
- del other
- elif self.i == 40 and greenlet.getcurrent() is not main:
- Dealloc.BG_RAN = True
- try:
- main.switch(42)
- except greenlet.GreenletExit as ex:
- # We expect this; all references to us go away
- # while we're still running, and we need to finish deleting
- # ourself.
- Dealloc.BG_RAN = type(ex)
- del ex
-
- # Record the fact that we're dead last of all. This ensures that
- # we actually get returned too.
- Dealloc.DESTROYED += 1
- if greenlet.getcurrent() is not main:
- Dealloc.DESTROYED_BG += 1
-
-
- def background_greenlet():
- # We direct through a second function, instead of
- # directly calling ``make_some()``, so that we have complete
- # control over when these objects are destroyed: we need them
- # to be destroyed in the context of the background greenlet
- t = make_some()
- del t # Triggere deletion.
-
- def make_some():
- t = ()
- i = OBJECTS_PER_CONTAINER
- while i:
- # Nest the tuples; it's the recursion that gets us
- # into trash.
- t = (Dealloc(i), t)
- i -= 1
- return t
-
-
- some = make_some()
- self.assertEqual(Dealloc.CREATED, OBJECTS_PER_CONTAINER)
- self.assertEqual(Dealloc.DESTROYED, 0)
-
- # If we're going to crash, it should be on the following line.
- # We only crash if ``assert()`` is enabled, of course.
- del some
-
- # For non-debug builds of CPython, we won't crash. The best we can do is check
- # the nesting level explicitly.
- self.assertEqual(0, get_tstate_trash_delete_nesting())
-
- # Discard this, raising GreenletExit into where it is waiting.
- Dealloc.BG_GLET = None
- # The same nesting level maintains.
- self.assertEqual(0, get_tstate_trash_delete_nesting())
-
- # We definitely cleaned some up in the background
- self.assertGreater(Dealloc.DESTROYED_BG, 0)
-
- # Make sure all the cleanups happened.
- self.assertIs(Dealloc.SPAWNED, main)
- self.assertTrue(Dealloc.BG_RAN)
- self.assertEqual(Dealloc.BG_RAN, greenlet.GreenletExit)
- self.assertEqual(Dealloc.CREATED, Dealloc.DESTROYED )
- self.assertEqual(Dealloc.CREATED, OBJECTS_PER_CONTAINER * 2)
-
- import gc
- gc.collect()
-
-
-if __name__ == '__main__':
- unittest.main()