|
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