|
1 | 1 | import copy
|
2 |
| -import os |
3 | 2 | import socket
|
4 | 3 | import ssl
|
5 | 4 | import sys
|
6 | 5 | import threading
|
7 | 6 | import weakref
|
8 | 7 | from abc import abstractmethod
|
9 | 8 | 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 |
10 | 24 | from queue import Empty, Full, LifoQueue
|
11 | 25 | from time import time
|
12 | 26 | from typing import Any, Callable, List, Optional, Sequence, Type, Union
|
@@ -178,7 +192,7 @@ def __init__(
|
178 | 192 | "1. 'password' and (optional) 'username'\n"
|
179 | 193 | "2. 'credential_provider'"
|
180 | 194 | )
|
181 |
| - self.pid = os.getpid() |
| 195 | + self.pid = getpid() |
182 | 196 | self.db = db
|
183 | 197 | self.client_name = client_name
|
184 | 198 | self.lib_name = lib_name
|
@@ -445,7 +459,7 @@ def disconnect(self, *args):
|
445 | 459 | if conn_sock is None:
|
446 | 460 | return
|
447 | 461 |
|
448 |
| - if os.getpid() == self.pid: |
| 462 | + if getpid() == self.pid: |
449 | 463 | try:
|
450 | 464 | conn_sock.shutdown(socket.SHUT_RDWR)
|
451 | 465 | except (OSError, TypeError):
|
@@ -1011,20 +1025,6 @@ def __init__(
|
1011 | 1025 | self.connection_kwargs = connection_kwargs
|
1012 | 1026 | self.max_connections = max_connections
|
1013 | 1027 |
|
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 |
| - |
1028 | 1028 | # a lock to protect the critical section in _checkpid().
|
1029 | 1029 | # this lock is acquired when the process id changes, such as
|
1030 | 1030 | # after a fork. during this time, multiple threads in the child
|
@@ -1057,7 +1057,7 @@ def reset(self) -> None:
|
1057 | 1057 | # release _fork_lock. when each of these threads eventually acquire
|
1058 | 1058 | # _fork_lock, they will notice that another thread already called
|
1059 | 1059 | # reset() and they will immediately release _fork_lock and continue on.
|
1060 |
| - self.pid = os.getpid() |
| 1060 | + self.pid = getpid() |
1061 | 1061 |
|
1062 | 1062 | def _checkpid(self) -> None:
|
1063 | 1063 | # _checkpid() attempts to keep ConnectionPool fork-safe on modern
|
@@ -1094,14 +1094,14 @@ def _checkpid(self) -> None:
|
1094 | 1094 | # seconds to acquire _fork_lock. if _fork_lock cannot be acquired in
|
1095 | 1095 | # that time it is assumed that the child is deadlocked and a
|
1096 | 1096 | # valkey.ChildDeadlockedError error is raised.
|
1097 |
| - if self.pid != self._getpid(): |
| 1097 | + if self.pid != getpid(): |
1098 | 1098 | acquired = self._fork_lock.acquire(timeout=5)
|
1099 | 1099 | if not acquired:
|
1100 | 1100 | raise ChildDeadlockedError
|
1101 | 1101 | # reset() the instance for the new process if another thread
|
1102 | 1102 | # hasn't already done so
|
1103 | 1103 | try:
|
1104 |
| - if self.pid != self._getpid(): |
| 1104 | + if self.pid != getpid(): |
1105 | 1105 | self.reset()
|
1106 | 1106 | finally:
|
1107 | 1107 | self._fork_lock.release()
|
@@ -1307,7 +1307,7 @@ def reset(self):
|
1307 | 1307 | # release _fork_lock. when each of these threads eventually acquire
|
1308 | 1308 | # _fork_lock, they will notice that another thread already called
|
1309 | 1309 | # reset() and they will immediately release _fork_lock and continue on.
|
1310 |
| - self.pid = os.getpid() |
| 1310 | + self.pid = getpid() |
1311 | 1311 |
|
1312 | 1312 | def make_connection(self):
|
1313 | 1313 | "Make a fresh connection."
|
|
0 commit comments