Skip to content

Commit e0151c1

Browse files
authored
Merge pull request #166 from mkmkme/mkmkme/getpid-attempt-two
connection: fix getpid() call on disconnect
2 parents ef40c56 + a2c27a9 commit e0151c1

File tree

1 file changed

+21
-21
lines changed

1 file changed

+21
-21
lines changed

valkey/connection.py

+21-21
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
11
import copy
2-
import os
32
import socket
43
import ssl
54
import sys
65
import threading
76
import weakref
87
from abc import abstractmethod
98
from itertools import chain
9+
10+
# We need to explicitly import `getpid` from `os` instead of importing `os`. The
11+
# reason for that is that Valkey class contains a __del__ method that causes the
12+
# call chain:
13+
# 1. Valkey.close()
14+
# 2. ConnectionPool.disconnect()
15+
# 3. ConnectionPool._checkpid()
16+
# 4. os.getpid()
17+
#
18+
# If os.getpid is garbage collected before Valkey, then the __del__
19+
# method will raise an AttributeError when trying to call os.getpid.
20+
# It wasn't an issue in practice until Python REPL was reworked in 3.13
21+
# to collect all globals at the end of the session, which caused
22+
# os.getpid to be garbage collected before Valkey.
23+
from os import getpid
1024
from queue import Empty, Full, LifoQueue
1125
from time import time
1226
from typing import Any, Callable, List, Optional, Sequence, Type, Union
@@ -178,7 +192,7 @@ def __init__(
178192
"1. 'password' and (optional) 'username'\n"
179193
"2. 'credential_provider'"
180194
)
181-
self.pid = os.getpid()
195+
self.pid = getpid()
182196
self.db = db
183197
self.client_name = client_name
184198
self.lib_name = lib_name
@@ -445,7 +459,7 @@ def disconnect(self, *args):
445459
if conn_sock is None:
446460
return
447461

448-
if os.getpid() == self.pid:
462+
if getpid() == self.pid:
449463
try:
450464
conn_sock.shutdown(socket.SHUT_RDWR)
451465
except (OSError, TypeError):
@@ -1011,20 +1025,6 @@ def __init__(
10111025
self.connection_kwargs = connection_kwargs
10121026
self.max_connections = max_connections
10131027

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-
10281028
# a lock to protect the critical section in _checkpid().
10291029
# this lock is acquired when the process id changes, such as
10301030
# after a fork. during this time, multiple threads in the child
@@ -1057,7 +1057,7 @@ def reset(self) -> None:
10571057
# release _fork_lock. when each of these threads eventually acquire
10581058
# _fork_lock, they will notice that another thread already called
10591059
# reset() and they will immediately release _fork_lock and continue on.
1060-
self.pid = os.getpid()
1060+
self.pid = getpid()
10611061

10621062
def _checkpid(self) -> None:
10631063
# _checkpid() attempts to keep ConnectionPool fork-safe on modern
@@ -1094,14 +1094,14 @@ def _checkpid(self) -> None:
10941094
# seconds to acquire _fork_lock. if _fork_lock cannot be acquired in
10951095
# that time it is assumed that the child is deadlocked and a
10961096
# valkey.ChildDeadlockedError error is raised.
1097-
if self.pid != self._getpid():
1097+
if self.pid != getpid():
10981098
acquired = self._fork_lock.acquire(timeout=5)
10991099
if not acquired:
11001100
raise ChildDeadlockedError
11011101
# reset() the instance for the new process if another thread
11021102
# hasn't already done so
11031103
try:
1104-
if self.pid != self._getpid():
1104+
if self.pid != getpid():
11051105
self.reset()
11061106
finally:
11071107
self._fork_lock.release()
@@ -1307,7 +1307,7 @@ def reset(self):
13071307
# release _fork_lock. when each of these threads eventually acquire
13081308
# _fork_lock, they will notice that another thread already called
13091309
# reset() and they will immediately release _fork_lock and continue on.
1310-
self.pid = os.getpid()
1310+
self.pid = getpid()
13111311

13121312
def make_connection(self):
13131313
"Make a fresh connection."

0 commit comments

Comments
 (0)