@@ -1011,6 +1011,20 @@ def __init__(
10111011 self .connection_kwargs = connection_kwargs
10121012 self .max_connections = max_connections
10131013
1014+ # We need to preserve the pointer to os.getpid because Valkey class
1015+ # contains a __del__ method that causes the call chain:
1016+ # 1. Valkey.close()
1017+ # 2. ConnectionPool.disconnect()
1018+ # 3. ConnectionPool._checkpid()
1019+ # 4. os.getpid()
1020+ #
1021+ # If os.getpid is garbage collected before Valkey, then the __del__
1022+ # method will raise an AttributeError when trying to call os.getpid.
1023+ # It wasn't an issue in practice until Python REPL was reworked in 3.13
1024+ # to collect all globals at the end of the session, which caused
1025+ # os.getpid to be garbage collected before Valkey.
1026+ self ._getpid = os .getpid
1027+
10141028 # a lock to protect the critical section in _checkpid().
10151029 # this lock is acquired when the process id changes, such as
10161030 # after a fork. during this time, multiple threads in the child
@@ -1080,14 +1094,14 @@ def _checkpid(self) -> None:
10801094 # seconds to acquire _fork_lock. if _fork_lock cannot be acquired in
10811095 # that time it is assumed that the child is deadlocked and a
10821096 # valkey.ChildDeadlockedError error is raised.
1083- if self .pid != os . getpid ():
1097+ if self .pid != self . _getpid ():
10841098 acquired = self ._fork_lock .acquire (timeout = 5 )
10851099 if not acquired :
10861100 raise ChildDeadlockedError
10871101 # reset() the instance for the new process if another thread
10881102 # hasn't already done so
10891103 try :
1090- if self .pid != os . getpid ():
1104+ if self .pid != self . _getpid ():
10911105 self .reset ()
10921106 finally :
10931107 self ._fork_lock .release ()
0 commit comments