Ë
    Õ[Ãi­.  ã                   óx   — d Z ddlZddlZddlZddlZddlmZ  G d„ de«      Zedk(  r ej                  «        yy)a  
Tests for greenlet behavior during interpreter shutdown (Py_FinalizeEx).

Prior to the safe finalization fix, active greenlets being deallocated
during interpreter shutdown could trigger SIGSEGV or SIGABRT on Python
< 3.11, because green_dealloc attempted to throw GreenletExit via
g_switch() into a partially-torn-down interpreter.

The fix adds _Py_IsFinalizing() guards (on Python < 3.11 only) that
call murder_in_place() instead of g_switch() when the interpreter is
shutting down, avoiding the crash at the cost of not running cleanup
code inside the greenlet.

These tests verify:
  1. No crashes on ANY Python version (the core safety guarantee).
  2. GreenletExit cleanup code runs correctly during normal thread exit
     (the standard production path, e.g. uWSGI worker threads).
é    N)ÚTestCasec                   óH   — e Zd Zd„ Zd„ Zd„ Zd„ Zd„ Zd„ Zd„ Z	d„ Z
d	„ Zd
„ Zy)ÚTestInterpreterShutdownc                 óÈ   — t        j                  |«      }t        j                  t        j
                  d|gdddd¬«      }|j                  |j                  |j                  fS )z˜
        Run a Python script in a subprocess that exercises greenlet
        during interpreter shutdown. Returns (returncode, stdout, stderr).
        z-cTé   F)Úcapture_outputÚtextÚtimeoutÚcheck)	ÚtextwrapÚdedentÚ
subprocessÚrunÚsysÚ
executableÚ
returncodeÚstdoutÚstderr)ÚselfÚscript_bodyÚfull_scriptÚresults       ú`/var/www/timesheet/venv/lib/python3.12/site-packages/greenlet/tests/test_interpreter_shutdown.pyÚ_run_shutdown_scriptz,TestInterpreterShutdown._run_shutdown_script   sW   € ô
 —o‘o kÓ2ˆÜ—‘Ü�^‰^˜T ;Ð/ØØØØô
ˆð × Ñ  &§-¡-°·±Ð>Ð>ó    c           	      óˆ   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       y)a8  
        An active (suspended) greenlet that is deallocated during
        interpreter shutdown should not crash the process.

        Before the fix, this would SIGSEGV on Python < 3.11 because
        _green_dealloc_kill_started_non_main_greenlet tried to call
        g_switch() during Py_FinalizeEx.
        aT              import greenlet

            def worker():
                greenlet.getcurrent().parent.switch("from worker")
                return "done"

            g = greenlet.greenlet(worker)
            result = g.switch()
            assert result == "from worker", result
            print("OK: exiting with active greenlet")
        r   úProcess crashed (rc=ú):
z OK: exiting with active greenletN©r   ÚassertEqualÚassertIn©r   Úrcr   r   s       r   Ú)test_active_greenlet_at_shutdown_no_crashzATestInterpreterShutdown.test_active_greenlet_at_shutdown_no_crash1   sU   € ð "×6Ñ6ð 8ó ÑˆˆF�Fð 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð8¸&ÕAr   c           	      óˆ   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       y)zm
        Multiple suspended greenlets at shutdown should all be cleaned
        up without crashing.
        a­              import greenlet

            def worker(name):
                greenlet.getcurrent().parent.switch(f"hello from {name}")
                return "done"

            greenlets = []
            for i in range(10):
                g = greenlet.greenlet(worker)
                result = g.switch(f"g{i}")
                greenlets.append(g)

            print(f"OK: {len(greenlets)} active greenlets at shutdown")
        r   r   r   z#OK: 10 active greenlets at shutdownNr   r"   s       r   Ú*test_multiple_active_greenlets_at_shutdownzBTestInterpreterShutdown.test_multiple_active_greenlets_at_shutdownI   sU   € ð
 "×6Ñ6ð 8ó ÑˆˆF�Fð 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð;¸VÕDr   c           	      óˆ   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       y)zQ
        Nested (chained parent) greenlets at shutdown should not crash.
        aâ              import greenlet

            def inner():
                greenlet.getcurrent().parent.switch("inner done")

            def outer():
                g_inner = greenlet.greenlet(inner)
                g_inner.switch()
                greenlet.getcurrent().parent.switch("outer done")

            g = greenlet.greenlet(outer)
            result = g.switch()
            assert result == "outer done", result
            print("OK: nested greenlets at shutdown")
        r   r   r   z OK: nested greenlets at shutdownNr   r"   s       r   Ú!test_nested_greenlets_at_shutdownz9TestInterpreterShutdown.test_nested_greenlets_at_shutdown`   sU   € ð "×6Ñ6ð 8ó ÑˆˆF�Fð  	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð8¸&ÕAr   c           	      óˆ   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       y)zm
        Greenlets in worker threads that are still referenced at
        shutdown should not crash.
        aÅ              import greenlet
            import threading

            results = []

            def thread_worker():
                def greenlet_func():
                    greenlet.getcurrent().parent.switch("from thread greenlet")
                    return "done"

                g = greenlet.greenlet(greenlet_func)
                val = g.switch()
                results.append((g, val))

            threads = []
            for _ in range(3):
                t = threading.Thread(target=thread_worker)
                t.start()
                threads.append(t)

            for t in threads:
                t.join()

            print(f"OK: {len(results)} threaded greenlets at shutdown")
        r   r   r   z$OK: 3 threaded greenlets at shutdownNr   r"   s       r   Ú#test_threaded_greenlets_at_shutdownz;TestInterpreterShutdown.test_threaded_greenlets_at_shutdownw   sU   € ð
 "×6Ñ6ð 8ó ÑˆˆF�Fð4 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð<¸fÕEr   c           	      ó¬   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       | j                  d|«       y)zû
        When a thread exits normally while holding active greenlets,
        GreenletExit IS thrown and cleanup code runs.  This is the
        standard cleanup path used in production (e.g. uWSGI worker
        threads finishing a request).
        a4              import os
            import threading
            import greenlet

            _write = os.write

            def thread_func():
                def worker(_w=_write,
                           _GreenletExit=greenlet.GreenletExit):
                    try:
                        greenlet.getcurrent().parent.switch("suspended")
                    except _GreenletExit:
                        _w(1, b"CLEANUP: GreenletExit caught\n")
                        raise

                g = greenlet.greenlet(worker)
                g.switch()
                # Thread exits with active greenlet -> thread-state
                # cleanup triggers GreenletExit

            t = threading.Thread(target=thread_func)
            t.start()
            t.join()
            print("OK: thread cleanup done")
        r   r   r   úOK: thread cleanup donezCLEANUP: GreenletExit caughtNr   r"   s       r   Ú(test_greenlet_cleanup_during_thread_exitz@TestInterpreterShutdown.test_greenlet_cleanup_during_thread_exit­   sd   € ð "×6Ñ6ð 8ó ÑˆˆF�Fð4 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð/°Ô8Ø�‰Ð4°fÕ=r   c           	      ó¬   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       | j                  d|«       y)zl
        try/finally blocks in active greenlets run correctly when the
        owning thread exits.
        aR              import os
            import threading
            import greenlet

            _write = os.write

            def thread_func():
                def worker(_w=_write):
                    try:
                        greenlet.getcurrent().parent.switch("suspended")
                    finally:
                        _w(1, b"FINALLY: cleanup executed\n")

                g = greenlet.greenlet(worker)
                g.switch()

            t = threading.Thread(target=thread_func)
            t.start()
            t.join()
            print("OK: thread cleanup done")
        r   r   r   r,   zFINALLY: cleanup executedNr   r"   s       r   Ú%test_finally_block_during_thread_exitz=TestInterpreterShutdown.test_finally_block_during_thread_exitÒ   sd   € ð
 "×6Ñ6ð 8ó ÑˆˆF�Fð, 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð/°Ô8Ø�‰Ð1°6Õ:r   c           	      óˆ   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       y)z�
        Stress test: many active greenlets with cleanup code at shutdown.
        Ensures no crashes regardless of deallocation order.
        aï              import sys
            import greenlet

            cleanup_count = 0

            def worker(idx):
                global cleanup_count
                try:
                    greenlet.getcurrent().parent.switch(f"ready-{idx}")
                except greenlet.GreenletExit:
                    cleanup_count += 1
                    raise

            greenlets = []
            for i in range(50):
                g = greenlet.greenlet(worker)
                result = g.switch(i)
                greenlets.append(g)

            print(f"OK: {len(greenlets)} greenlets about to shut down")
            # Note: we can't easily print cleanup_count during shutdown
            # since it happens after the main module's code runs.
        r   r   r   z#OK: 50 greenlets about to shut downNr   r"   s       r   Ú,test_many_greenlets_with_cleanup_at_shutdownzDTestInterpreterShutdown.test_many_greenlets_with_cleanup_at_shutdownñ   sU   € ð
 "×6Ñ6ð 8ó ÑˆˆF�Fð0 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð;¸VÕDr   c           	      óˆ   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       y)zƒ
        Deeply nested greenlet parent chains at shutdown.
        Tests that the deallocation order doesn't cause issues.
        a¨              import greenlet

            def level(depth, max_depth):
                if depth < max_depth:
                    g = greenlet.greenlet(level)
                    g.switch(depth + 1, max_depth)
                greenlet.getcurrent().parent.switch(f"depth-{depth}")

            g = greenlet.greenlet(level)
            result = g.switch(0, 10)
            print(f"OK: nested to depth 10, got {result}")
        r   r   r   zOK: nested to depth 10Nr   r"   s       r   Ú(test_deeply_nested_greenlets_at_shutdownz@TestInterpreterShutdown.test_deeply_nested_greenlets_at_shutdown  sU   € ð
 "×6Ñ6ð 8ó ÑˆˆF�Fð 	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰Ð.°Õ7r   c           	      óˆ   — | j                  d«      \  }}}| j                  |dd|› d|› |› �«       | j                  d|«       y)z‡
        A greenlet that has an active exception context when it's
        suspended should not crash during shutdown cleanup.
        a              import greenlet

            def worker():
                try:
                    raise ValueError("test error")
                except ValueError:
                    # Suspend while an exception is active on the stack
                    greenlet.getcurrent().parent.switch("suspended with exc")
                return "done"

            g = greenlet.greenlet(worker)
            result = g.switch()
            assert result == "suspended with exc"
            print("OK: greenlet with active exception at shutdown")
        r   r   r   z.OK: greenlet with active exception at shutdownNr   r"   s       r   Ú(test_greenlet_with_traceback_at_shutdownz@TestInterpreterShutdown.test_greenlet_with_traceback_at_shutdown&  sU   € ð
 "×6Ñ6ð 8ó ÑˆˆF�Fð  	×Ñ˜˜QÐ"6°r°d¸$¸v¸hÀvÀhÐ OÔPØ�‰ÐFÈÕOr   N)Ú__name__Ú
__module__Ú__qualname__r   r$   r&   r(   r*   r-   r/   r1   r3   r5   © r   r   r   r      s=   „ ò?ò&Bò0Eò.Bò. Fòl#>òJ;ò>Eò@8ó*Pr   r   Ú__main__)
Ú__doc__r   r   Úunittestr   Úgreenlet.testsr   r   r6   Úmainr9   r   r   ú<module>r?      sF   ðñó$ Û Û Û å #ô`P˜hô `PðF	 ˆzÒØ€H‡M�M…Oð r   