GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — `os.fork()` child can hang inside `PyOS_AfterFork_Child()` on the warnings lock (3.14 regression) · Issue #158973 · python/cpython Closed Labels 3.14bugs and security fixesbugs and security fixes3.15bugs and security fixesbugs and security fixes3.16new features, bugs and security fixesnew features, bugs and security fixesextension-modulesC modules in the Modules dirC modules in the Modules dirtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error DescriptionBug reportSince 3.14, the warnings module protects its state with a lock in the interpreter, added in #128386 for #128384. In the reproducer, one thread keeps an unclosed socket in import os, socket, sys, threading, time, warnings # Every warning is shown, so warn() runs showwarning(), which is Python code, # while it holds the warnings lock. warnings.simplefilter("always") warnings.showwarning = lambda *args, **kwargs: None local = threading.local() def warn_forever(): local.sock = socket.socket() # never closed: freeing it issues a ResourceWarning while True: warnings.warn("from another thread") threading.Thread(target=warn_forever, daemon=True).start() time.sleep(0.5) hung = 0 for _ in range(20): pid = os.fork() if pid == 0: os._exit(0) # the child runs no other code time.sleep(0.5) if os.waitpid(pid, os.WNOHANG) == (0, 0): hung += 1 os.kill(pid, 9) os.waitpid(pid, 0) print(f"{sys.version.split()[0]}: {hung} of 20 children hung", flush=True) os._exit(0) # skip interpreter shutdown, which can hang too (see below) Results on Linux x86_64 with glibc 2.43:
gdb on a hung child (3.14.4, Ubuntu build without debug symbols; frames without a name are cut) shows a finalizer that runs in The lock can be held while other threads run. Lines 1136 to 1139 in 1610807
The lock is not in Lines 782 to 799 in 1610807
Holding the lock across the fork avoids the hang. With these two lines added to the reproducer, 0 of 20 children hang on 3.14.4, 3.14.8 and 3.15.0rc3: import _warnings os.register_at_fork(before=_warnings._acquire_lock, after_in_parent=_warnings._release_lock, after_in_child=_warnings._release_lock) A fix could take the lock in This hangs JupyterLab terminals. terminado starts them with ptyprocess on the server's event loop thread, and ptyprocess waits for I know this is grey territory. The
The same lock can also hang interpreter shutdown, with no fork. At shutdown, the daemon thread is frozen while it holds the lock, and the I used Opus 5.5 for assistance with investigation and the writeup. CPython versions tested on:3.12, 3.13, 3.14, 3.15 Operating systems tested on:Linux Linked PRs
Activity
MetadataMetadataAssigneesLabels3.14bugs and security fixesbugs and security fixes3.15bugs and security fixesbugs and security fixes3.16new features, bugs and security fixesnew features, bugs and security fixesextension-modulesC modules in the Modules dirC modules in the Modules dirtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error ProjectsMilestoneRelationshipsDevelopmentIssue actions | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| You are visitor 01402520 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |