omlish 0.0.0.dev226__py3-none-any.whl → 0.0.0.dev228__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omlish/__about__.py +3 -3
- omlish/diag/lslocks.py +4 -4
- omlish/diag/lsof.py +3 -4
- omlish/diag/ps.py +9 -0
- omlish/lite/timeouts.py +1 -1
- omlish/marshal/__init__.py +39 -24
- omlish/marshal/composite/__init__.py +0 -0
- omlish/marshal/{iterables.py → composite/iterables.py} +10 -10
- omlish/marshal/{literals.py → composite/literals.py} +9 -9
- omlish/marshal/{mappings.py → composite/mappings.py} +10 -10
- omlish/marshal/{maybes.py → composite/maybes.py} +11 -11
- omlish/marshal/{newtypes.py → composite/newtypes.py} +8 -8
- omlish/marshal/{optionals.py → composite/optionals.py} +9 -9
- omlish/marshal/objects/__init__.py +7 -0
- omlish/marshal/{dataclasses.py → objects/dataclasses.py} +24 -24
- omlish/marshal/{helpers.py → objects/helpers.py} +6 -3
- omlish/marshal/objects/marshal.py +108 -0
- omlish/marshal/objects/metadata.py +124 -0
- omlish/marshal/{namedtuples.py → objects/namedtuples.py} +16 -16
- omlish/marshal/objects/unmarshal.py +141 -0
- omlish/marshal/polymorphism/__init__.py +7 -0
- omlish/marshal/polymorphism/marshal.py +66 -0
- omlish/marshal/polymorphism/metadata.py +140 -0
- omlish/marshal/{unions.py → polymorphism/unions.py} +18 -18
- omlish/marshal/polymorphism/unmarshal.py +73 -0
- omlish/marshal/singular/__init__.py +0 -0
- omlish/marshal/{any.py → singular/any.py} +8 -8
- omlish/marshal/{base64.py → singular/base64.py} +9 -9
- omlish/marshal/{datetimes.py → singular/datetimes.py} +9 -9
- omlish/marshal/{enums.py → singular/enums.py} +9 -9
- omlish/marshal/{numbers.py → singular/numbers.py} +8 -8
- omlish/marshal/{primitives.py → singular/primitives.py} +8 -8
- omlish/marshal/{uuids.py → singular/uuids.py} +8 -8
- omlish/marshal/standard.py +32 -32
- omlish/os/death.py +70 -4
- omlish/os/fcntl.py +11 -12
- omlish/os/files.py +18 -3
- omlish/os/forkhooks.py +215 -0
- omlish/os/pidfiles/manager.py +4 -1
- omlish/os/pidfiles/pidfile.py +31 -11
- omlish/os/pidfiles/pinning.py +250 -0
- omlish/sockets/bind.py +4 -4
- {omlish-0.0.0.dev226.dist-info → omlish-0.0.0.dev228.dist-info}/METADATA +3 -3
- {omlish-0.0.0.dev226.dist-info → omlish-0.0.0.dev228.dist-info}/RECORD +48 -38
- omlish/marshal/objects.py +0 -317
- omlish/marshal/polymorphism.py +0 -267
- {omlish-0.0.0.dev226.dist-info → omlish-0.0.0.dev228.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev226.dist-info → omlish-0.0.0.dev228.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev226.dist-info → omlish-0.0.0.dev228.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev226.dist-info → omlish-0.0.0.dev228.dist-info}/top_level.txt +0 -0
@@ -2,15 +2,15 @@ import dataclasses as dc
|
|
2
2
|
import datetime
|
3
3
|
import typing as ta
|
4
4
|
|
5
|
-
from
|
6
|
-
from
|
7
|
-
from
|
8
|
-
from
|
9
|
-
from
|
10
|
-
from
|
11
|
-
from
|
12
|
-
from
|
13
|
-
from
|
5
|
+
from ... import check
|
6
|
+
from ... import datetimes as dts
|
7
|
+
from ..base import MarshalContext
|
8
|
+
from ..base import Marshaler
|
9
|
+
from ..base import TypeMapMarshalerFactory
|
10
|
+
from ..base import TypeMapUnmarshalerFactory
|
11
|
+
from ..base import UnmarshalContext
|
12
|
+
from ..base import Unmarshaler
|
13
|
+
from ..values import Value
|
14
14
|
|
15
15
|
|
16
16
|
DatetimeLikeT = ta.TypeVar('DatetimeLikeT', bound=datetime.datetime | datetime.date | datetime.time)
|
@@ -2,15 +2,15 @@ import dataclasses as dc
|
|
2
2
|
import enum
|
3
3
|
import typing as ta
|
4
4
|
|
5
|
-
from
|
6
|
-
from
|
7
|
-
from
|
8
|
-
from
|
9
|
-
from
|
10
|
-
from
|
11
|
-
from
|
12
|
-
from
|
13
|
-
from
|
5
|
+
from ... import check
|
6
|
+
from ... import reflect as rfl
|
7
|
+
from ..base import MarshalContext
|
8
|
+
from ..base import Marshaler
|
9
|
+
from ..base import MarshalerFactory
|
10
|
+
from ..base import UnmarshalContext
|
11
|
+
from ..base import Unmarshaler
|
12
|
+
from ..base import UnmarshalerFactory
|
13
|
+
from ..values import Value
|
14
14
|
|
15
15
|
|
16
16
|
@dc.dataclass(frozen=True)
|
@@ -2,14 +2,14 @@ import decimal
|
|
2
2
|
import fractions
|
3
3
|
import typing as ta
|
4
4
|
|
5
|
-
from
|
6
|
-
from
|
7
|
-
from
|
8
|
-
from
|
9
|
-
from
|
10
|
-
from
|
11
|
-
from
|
12
|
-
from
|
5
|
+
from ... import check
|
6
|
+
from ..base import MarshalContext
|
7
|
+
from ..base import Marshaler
|
8
|
+
from ..base import TypeMapMarshalerFactory
|
9
|
+
from ..base import TypeMapUnmarshalerFactory
|
10
|
+
from ..base import UnmarshalContext
|
11
|
+
from ..base import Unmarshaler
|
12
|
+
from ..values import Value
|
13
13
|
|
14
14
|
|
15
15
|
class ComplexMarshalerUnmarshaler(Marshaler, Unmarshaler):
|
@@ -4,14 +4,14 @@ TODO:
|
|
4
4
|
"""
|
5
5
|
import typing as ta
|
6
6
|
|
7
|
-
from
|
8
|
-
from
|
9
|
-
from
|
10
|
-
from
|
11
|
-
from
|
12
|
-
from
|
13
|
-
from
|
14
|
-
from
|
7
|
+
from ... import dataclasses as dc
|
8
|
+
from ..base import MarshalContext
|
9
|
+
from ..base import Marshaler
|
10
|
+
from ..base import TypeMapMarshalerFactory
|
11
|
+
from ..base import TypeMapUnmarshalerFactory
|
12
|
+
from ..base import UnmarshalContext
|
13
|
+
from ..base import Unmarshaler
|
14
|
+
from ..values import Value
|
15
15
|
|
16
16
|
|
17
17
|
##
|
@@ -1,14 +1,14 @@
|
|
1
1
|
import re
|
2
2
|
import uuid
|
3
3
|
|
4
|
-
from
|
5
|
-
from
|
6
|
-
from
|
7
|
-
from
|
8
|
-
from
|
9
|
-
from
|
10
|
-
from
|
11
|
-
from
|
4
|
+
from ... import check
|
5
|
+
from ..base import MarshalContext
|
6
|
+
from ..base import Marshaler
|
7
|
+
from ..base import TypeMapMarshalerFactory
|
8
|
+
from ..base import TypeMapUnmarshalerFactory
|
9
|
+
from ..base import UnmarshalContext
|
10
|
+
from ..base import Unmarshaler
|
11
|
+
from ..values import Value
|
12
12
|
|
13
13
|
|
14
14
|
PATTERN = re.compile(r'([0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12})|([0-9A-Fa-f]{32})')
|
omlish/marshal/standard.py
CHANGED
@@ -1,42 +1,42 @@
|
|
1
1
|
from ..funcs import match as mfs
|
2
|
-
from .any import ANY_MARSHALER_FACTORY
|
3
|
-
from .any import ANY_UNMARSHALER_FACTORY
|
4
2
|
from .base import MarshalerFactory
|
5
3
|
from .base import RecursiveMarshalerFactory
|
6
4
|
from .base import RecursiveUnmarshalerFactory
|
7
5
|
from .base import TypeCacheMarshalerFactory
|
8
6
|
from .base import TypeCacheUnmarshalerFactory
|
9
7
|
from .base import UnmarshalerFactory
|
10
|
-
from .
|
11
|
-
from .
|
12
|
-
from .
|
13
|
-
from .
|
14
|
-
from .
|
15
|
-
from .
|
16
|
-
from .
|
17
|
-
from .
|
18
|
-
from .
|
19
|
-
from .
|
20
|
-
from .
|
21
|
-
from .
|
22
|
-
from .
|
23
|
-
from .
|
24
|
-
from .
|
25
|
-
from .
|
26
|
-
from .
|
27
|
-
from .
|
28
|
-
from .
|
29
|
-
from .
|
30
|
-
from .
|
31
|
-
from .
|
32
|
-
from .
|
33
|
-
from .
|
34
|
-
from .
|
35
|
-
from .
|
36
|
-
from .
|
37
|
-
from .
|
38
|
-
from .
|
39
|
-
from .
|
8
|
+
from .composite.iterables import IterableMarshalerFactory
|
9
|
+
from .composite.iterables import IterableUnmarshalerFactory
|
10
|
+
from .composite.literals import LiteralMarshalerFactory
|
11
|
+
from .composite.literals import LiteralUnmarshalerFactory
|
12
|
+
from .composite.mappings import MappingMarshalerFactory
|
13
|
+
from .composite.mappings import MappingUnmarshalerFactory
|
14
|
+
from .composite.maybes import MaybeMarshalerFactory
|
15
|
+
from .composite.maybes import MaybeUnmarshalerFactory
|
16
|
+
from .composite.newtypes import NewtypeMarshalerFactory
|
17
|
+
from .composite.newtypes import NewtypeUnmarshalerFactory
|
18
|
+
from .composite.optionals import OptionalMarshalerFactory
|
19
|
+
from .composite.optionals import OptionalUnmarshalerFactory
|
20
|
+
from .objects.dataclasses import DataclassMarshalerFactory
|
21
|
+
from .objects.dataclasses import DataclassUnmarshalerFactory
|
22
|
+
from .objects.namedtuples import NamedtupleMarshalerFactory
|
23
|
+
from .objects.namedtuples import NamedtupleUnmarshalerFactory
|
24
|
+
from .polymorphism.unions import PrimitiveUnionMarshalerFactory
|
25
|
+
from .polymorphism.unions import PrimitiveUnionUnmarshalerFactory
|
26
|
+
from .singular.any import ANY_MARSHALER_FACTORY
|
27
|
+
from .singular.any import ANY_UNMARSHALER_FACTORY
|
28
|
+
from .singular.base64 import BASE64_MARSHALER_FACTORY
|
29
|
+
from .singular.base64 import BASE64_UNMARSHALER_FACTORY
|
30
|
+
from .singular.datetimes import DATETIME_MARSHALER_FACTORY
|
31
|
+
from .singular.datetimes import DATETIME_UNMARSHALER_FACTORY
|
32
|
+
from .singular.enums import EnumMarshalerFactory
|
33
|
+
from .singular.enums import EnumUnmarshalerFactory
|
34
|
+
from .singular.numbers import NUMBERS_MARSHALER_FACTORY
|
35
|
+
from .singular.numbers import NUMBERS_UNMARSHALER_FACTORY
|
36
|
+
from .singular.primitives import PRIMITIVE_MARSHALER_FACTORY
|
37
|
+
from .singular.primitives import PRIMITIVE_UNMARSHALER_FACTORY
|
38
|
+
from .singular.uuids import UUID_MARSHALER_FACTORY
|
39
|
+
from .singular.uuids import UUID_UNMARSHALER_FACTORY
|
40
40
|
|
41
41
|
|
42
42
|
##
|
omlish/os/death.py
CHANGED
@@ -4,8 +4,11 @@ import signal
|
|
4
4
|
import sys
|
5
5
|
import time
|
6
6
|
import typing as ta
|
7
|
+
import weakref
|
7
8
|
|
8
9
|
from .. import check
|
10
|
+
from .forkhooks import ForkHook
|
11
|
+
from .forkhooks import get_fork_depth
|
9
12
|
|
10
13
|
|
11
14
|
##
|
@@ -82,12 +85,24 @@ class BaseDeathpact(Deathpact, abc.ABC):
|
|
82
85
|
|
83
86
|
|
84
87
|
class PipeDeathpact(BaseDeathpact):
|
88
|
+
"""
|
89
|
+
NOTE: Closes write side in children lazily on poll - does not proactively close write sides on fork. This means
|
90
|
+
parents which fork children into codepaths unaware of live PipeDeathpacts will leave write sides open in those
|
91
|
+
children, potentially leading to zombies (if those children outlast the parent). Use ForkAwarePipeDeathpact to
|
92
|
+
handle such cases.
|
93
|
+
"""
|
94
|
+
|
95
|
+
_COOKIE: ta.ClassVar[bytes] = os.urandom(16)
|
96
|
+
|
85
97
|
def __init__(self, **kwargs: ta.Any) -> None:
|
86
98
|
super().__init__(**kwargs)
|
87
99
|
|
88
100
|
self._rfd: int | None = None
|
89
101
|
self._wfd: int | None = None
|
90
102
|
|
103
|
+
self._cookie: bytes | None = self._COOKIE
|
104
|
+
self._fork_depth: int | None = get_fork_depth()
|
105
|
+
|
91
106
|
def __repr__(self) -> str:
|
92
107
|
return f'{self.__class__.__name__}(rfd={self._rfd}, wfd={self._wfd})'
|
93
108
|
|
@@ -95,26 +110,52 @@ class PipeDeathpact(BaseDeathpact):
|
|
95
110
|
def pass_fd(self) -> int:
|
96
111
|
return check.not_none(self._rfd)
|
97
112
|
|
113
|
+
def is_parent(self) -> bool:
|
114
|
+
return (self._COOKIE, get_fork_depth()) == (self._cookie, self._fork_depth)
|
115
|
+
|
116
|
+
#
|
117
|
+
|
98
118
|
def __enter__(self) -> ta.Self:
|
99
119
|
check.none(self._rfd)
|
100
120
|
check.none(self._wfd)
|
101
121
|
|
102
122
|
self._rfd, self._wfd = os.pipe()
|
103
123
|
|
104
|
-
os.set_inheritable(self._rfd, True)
|
105
124
|
os.set_blocking(self._rfd, False)
|
106
125
|
|
107
126
|
return self
|
108
127
|
|
109
|
-
def
|
110
|
-
if self.
|
111
|
-
|
128
|
+
def _close_wfd_if_not_parent(self) -> None:
|
129
|
+
if self._wfd is not None:
|
130
|
+
if not self.is_parent():
|
131
|
+
os.close(check.not_none(self._wfd))
|
112
132
|
self._wfd = None
|
113
133
|
|
134
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
135
|
+
if self._rfd is not None:
|
114
136
|
os.close(self._rfd)
|
115
137
|
self._rfd = None
|
116
138
|
|
139
|
+
self._close_wfd_if_not_parent()
|
140
|
+
|
141
|
+
#
|
142
|
+
|
143
|
+
def __getstate__(self):
|
144
|
+
return dict(
|
145
|
+
**self.__dict__,
|
146
|
+
_wfd=None,
|
147
|
+
_cookie=None,
|
148
|
+
_fork_depth=None,
|
149
|
+
)
|
150
|
+
|
151
|
+
def __setstate__(self, state):
|
152
|
+
self.__dict__.update(state)
|
153
|
+
|
154
|
+
#
|
155
|
+
|
117
156
|
def should_die(self) -> bool:
|
157
|
+
self._close_wfd_if_not_parent()
|
158
|
+
|
118
159
|
try:
|
119
160
|
buf = os.read(check.not_none(self._rfd), 1)
|
120
161
|
except BlockingIOError:
|
@@ -125,3 +166,28 @@ class PipeDeathpact(BaseDeathpact):
|
|
125
166
|
self.die()
|
126
167
|
|
127
168
|
return True
|
169
|
+
|
170
|
+
|
171
|
+
#
|
172
|
+
|
173
|
+
|
174
|
+
class ForkAwarePipeDeathpact(PipeDeathpact):
|
175
|
+
"""
|
176
|
+
TODO:
|
177
|
+
- Despite no correct way to do threads+forks, still audit thread-safety. Is WeakSet threadsafe? Probably not..
|
178
|
+
"""
|
179
|
+
|
180
|
+
_PARENTS: ta.ClassVar[ta.MutableSet['ForkAwarePipeDeathpact']] = weakref.WeakSet()
|
181
|
+
|
182
|
+
def __init__(self, **kwargs: ta.Any) -> None:
|
183
|
+
super().__init__(**kwargs)
|
184
|
+
|
185
|
+
self._ForkHook.install()
|
186
|
+
self._PARENTS.add(self)
|
187
|
+
|
188
|
+
class _ForkHook(ForkHook):
|
189
|
+
@classmethod
|
190
|
+
def _after_fork_in_child(cls) -> None:
|
191
|
+
for pdp in ForkAwarePipeDeathpact._PARENTS:
|
192
|
+
pdp._close_wfd_if_not_parent() # noqa
|
193
|
+
ForkAwarePipeDeathpact._PARENTS.clear()
|
omlish/os/fcntl.py
CHANGED
@@ -19,7 +19,7 @@ class FcntlLockData:
|
|
19
19
|
|
20
20
|
#
|
21
21
|
|
22
|
-
|
22
|
+
_STRUCT_PACKING_BY_PLATFORM: ta.ClassVar[ta.Mapping[str, ta.Sequence[ta.Tuple[str, str]]]] = {
|
23
23
|
'linux': [
|
24
24
|
('type', 'h'),
|
25
25
|
('whence', 'h'),
|
@@ -36,24 +36,23 @@ class FcntlLockData:
|
|
36
36
|
],
|
37
37
|
}
|
38
38
|
|
39
|
-
|
39
|
+
@classmethod
|
40
|
+
def _struct_packing(cls) -> ta.Sequence[ta.Tuple[str, str]]:
|
40
41
|
try:
|
41
|
-
|
42
|
+
return cls._STRUCT_PACKING_BY_PLATFORM[sys.platform]
|
42
43
|
except KeyError:
|
43
44
|
raise OSError from None
|
44
45
|
|
45
|
-
|
46
|
-
|
46
|
+
def pack(self) -> bytes:
|
47
|
+
packing = self._struct_packing()
|
48
|
+
fmt = ''.join(f for _, f in packing)
|
49
|
+
tup = [getattr(self, a) for a, _ in packing]
|
47
50
|
return struct.pack(fmt, *tup)
|
48
51
|
|
49
52
|
@classmethod
|
50
53
|
def unpack(cls, data: bytes) -> 'FcntlLockData':
|
51
|
-
|
52
|
-
|
53
|
-
except KeyError:
|
54
|
-
raise OSError from None
|
55
|
-
|
56
|
-
fmt = ''.join(f for _, f in pack)
|
54
|
+
packing = cls._struct_packing()
|
55
|
+
fmt = ''.join(f for _, f in packing)
|
57
56
|
tup = struct.unpack(fmt, data)
|
58
|
-
kw = {a: v for (a, _), v in zip(
|
57
|
+
kw = {a: v for (a, _), v in zip(packing, tup)}
|
59
58
|
return FcntlLockData(**kw)
|
omlish/os/files.py
CHANGED
@@ -1,24 +1,39 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
# @omlish-lite
|
3
3
|
import contextlib
|
4
|
+
import errno
|
5
|
+
import fcntl
|
4
6
|
import os
|
5
7
|
import typing as ta
|
6
8
|
|
7
9
|
|
8
|
-
def
|
10
|
+
def is_fd_open(fd: int) -> bool:
|
11
|
+
try:
|
12
|
+
fcntl.fcntl(fd, fcntl.F_GETFD)
|
13
|
+
except OSError as e:
|
14
|
+
if e.errno == errno.EBADF:
|
15
|
+
return False
|
16
|
+
raise
|
17
|
+
else:
|
18
|
+
return True
|
19
|
+
|
20
|
+
|
21
|
+
def touch(path: str, mode: int = 0o666, exist_ok: bool = True) -> None:
|
9
22
|
if exist_ok:
|
10
23
|
# First try to bump modification time
|
11
24
|
# Implementation note: GNU touch uses the UTIME_NOW option of the utimensat() / futimens() functions.
|
12
25
|
try:
|
13
|
-
os.utime(
|
26
|
+
os.utime(path, None)
|
14
27
|
except OSError:
|
15
28
|
pass
|
16
29
|
else:
|
17
30
|
return
|
31
|
+
|
18
32
|
flags = os.O_CREAT | os.O_WRONLY
|
19
33
|
if not exist_ok:
|
20
34
|
flags |= os.O_EXCL
|
21
|
-
|
35
|
+
|
36
|
+
fd = os.open(path, flags, mode)
|
22
37
|
os.close(fd)
|
23
38
|
|
24
39
|
|
omlish/os/forkhooks.py
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
"""
|
4
|
+
TODO:
|
5
|
+
- ForkHook base class? all classmethods? prevents pickling
|
6
|
+
"""
|
7
|
+
import abc
|
8
|
+
import os
|
9
|
+
import threading
|
10
|
+
import typing as ta
|
11
|
+
|
12
|
+
from ..lite.check import check
|
13
|
+
|
14
|
+
|
15
|
+
##
|
16
|
+
|
17
|
+
|
18
|
+
class _ForkHookManager:
|
19
|
+
def __new__(cls, *args, **kwargs): # noqa
|
20
|
+
raise TypeError
|
21
|
+
|
22
|
+
def __init_subclass__(cls, **kwargs): # noqa
|
23
|
+
raise TypeError
|
24
|
+
|
25
|
+
#
|
26
|
+
|
27
|
+
# Intentionally not an RLock - do not nest calls.
|
28
|
+
_lock: ta.ClassVar[threading.Lock] = threading.Lock()
|
29
|
+
|
30
|
+
#
|
31
|
+
|
32
|
+
_installed: ta.ClassVar[bool] = False
|
33
|
+
|
34
|
+
@classmethod
|
35
|
+
def _install(cls) -> bool:
|
36
|
+
if cls._installed:
|
37
|
+
return False
|
38
|
+
|
39
|
+
check.state(not cls._installed)
|
40
|
+
|
41
|
+
os.register_at_fork(
|
42
|
+
before=cls._before_fork,
|
43
|
+
after_in_parent=cls._after_fork_in_parent,
|
44
|
+
after_in_child=cls._after_fork_in_child,
|
45
|
+
)
|
46
|
+
|
47
|
+
cls._installed = True
|
48
|
+
return True
|
49
|
+
|
50
|
+
@classmethod
|
51
|
+
def install(cls) -> bool:
|
52
|
+
with cls._lock:
|
53
|
+
return cls._install()
|
54
|
+
|
55
|
+
#
|
56
|
+
|
57
|
+
class Hook(ta.NamedTuple):
|
58
|
+
key: ta.Any
|
59
|
+
priority: int
|
60
|
+
|
61
|
+
# NOTE: these are called inside the global, non-reentrant manager lock
|
62
|
+
before_fork: ta.Optional[ta.Callable[[], None]] = None
|
63
|
+
after_fork_in_parent: ta.Optional[ta.Callable[[], None]] = None
|
64
|
+
after_fork_in_child: ta.Optional[ta.Callable[[], None]] = None
|
65
|
+
|
66
|
+
#
|
67
|
+
|
68
|
+
_hooks_by_key: ta.ClassVar[ta.Dict[str, Hook]] = {}
|
69
|
+
|
70
|
+
_hook_keys: ta.ClassVar[ta.FrozenSet[str]] = frozenset()
|
71
|
+
_priority_ordered_hooks: ta.ClassVar[ta.List[Hook]] = []
|
72
|
+
|
73
|
+
@classmethod
|
74
|
+
def _rebuild_hook_collections(cls) -> None:
|
75
|
+
cls._hook_keys = frozenset(cls._hooks_by_key)
|
76
|
+
|
77
|
+
# Uses on dict order preservation for insertion-order of hooks of equal priority (although that shouldn't be
|
78
|
+
# depended upon for usecase correctness.
|
79
|
+
cls._priority_ordered_hooks = sorted(cls._hooks_by_key.values(), key=lambda h: h.priority)
|
80
|
+
|
81
|
+
#
|
82
|
+
|
83
|
+
class HookAlreadyPresentError(Exception):
|
84
|
+
pass
|
85
|
+
|
86
|
+
@classmethod
|
87
|
+
def add_hook(cls, hook: Hook) -> None:
|
88
|
+
with cls._lock:
|
89
|
+
if hook.key in cls._hooks_by_key:
|
90
|
+
raise cls.HookAlreadyPresentError(hook.key)
|
91
|
+
|
92
|
+
check.isinstance(hook.priority, int)
|
93
|
+
|
94
|
+
cls._hooks_by_key[hook.key] = hook
|
95
|
+
cls._rebuild_hook_collections()
|
96
|
+
|
97
|
+
cls._install()
|
98
|
+
|
99
|
+
@classmethod
|
100
|
+
def try_add_hook(cls, hook: Hook) -> bool:
|
101
|
+
if hook.key in cls._hook_keys:
|
102
|
+
return False
|
103
|
+
|
104
|
+
try:
|
105
|
+
cls.add_hook(hook)
|
106
|
+
except cls.HookAlreadyPresentError:
|
107
|
+
return False
|
108
|
+
else:
|
109
|
+
return True
|
110
|
+
|
111
|
+
@classmethod
|
112
|
+
def contains_hook(cls, key: ta.Any) -> bool:
|
113
|
+
return key in cls._hook_keys
|
114
|
+
|
115
|
+
@classmethod
|
116
|
+
def remove_hook(cls, key: ta.Any) -> None:
|
117
|
+
with cls._lock:
|
118
|
+
del cls._hooks_by_key[key]
|
119
|
+
|
120
|
+
cls._rebuild_hook_collections()
|
121
|
+
|
122
|
+
#
|
123
|
+
|
124
|
+
@classmethod
|
125
|
+
def _before_fork(cls) -> None:
|
126
|
+
cls._lock.acquire()
|
127
|
+
|
128
|
+
for hook in cls._priority_ordered_hooks:
|
129
|
+
if (fn := hook.before_fork) is not None:
|
130
|
+
fn()
|
131
|
+
|
132
|
+
@classmethod
|
133
|
+
def _after_fork_in_parent(cls) -> None:
|
134
|
+
for hook in cls._priority_ordered_hooks:
|
135
|
+
if (fn := hook.after_fork_in_parent) is not None:
|
136
|
+
fn()
|
137
|
+
|
138
|
+
cls._lock.release()
|
139
|
+
|
140
|
+
@classmethod
|
141
|
+
def _after_fork_in_child(cls) -> None:
|
142
|
+
for hook in cls._priority_ordered_hooks:
|
143
|
+
if (fn := hook.after_fork_in_child) is not None:
|
144
|
+
fn()
|
145
|
+
|
146
|
+
cls._lock.release()
|
147
|
+
|
148
|
+
|
149
|
+
#
|
150
|
+
|
151
|
+
|
152
|
+
class ForkHook(abc.ABC): # noqa
|
153
|
+
@ta.final
|
154
|
+
def __new__(cls, *args, **kwargs): # noqa
|
155
|
+
raise TypeError
|
156
|
+
|
157
|
+
@classmethod
|
158
|
+
@ta.final
|
159
|
+
def install(cls) -> bool:
|
160
|
+
if _ForkHookManager.contains_hook(cls):
|
161
|
+
return False
|
162
|
+
|
163
|
+
return _ForkHookManager.try_add_hook(_ForkHookManager.Hook(
|
164
|
+
key=cls,
|
165
|
+
priority=cls._hook_priority,
|
166
|
+
|
167
|
+
before_fork=cls._before_fork,
|
168
|
+
after_fork_in_parent=cls._after_fork_in_parent,
|
169
|
+
after_fork_in_child=cls._after_fork_in_child,
|
170
|
+
))
|
171
|
+
|
172
|
+
def __init_subclass__(cls, install: bool = False, **kwargs: ta.Any) -> None:
|
173
|
+
super().__init_subclass__(**kwargs)
|
174
|
+
|
175
|
+
if install:
|
176
|
+
cls.install()
|
177
|
+
|
178
|
+
#
|
179
|
+
|
180
|
+
_hook_priority: ta.ClassVar[int] = 0
|
181
|
+
|
182
|
+
@classmethod # noqa
|
183
|
+
def _before_fork(cls) -> None:
|
184
|
+
pass
|
185
|
+
|
186
|
+
@classmethod # noqa
|
187
|
+
def _after_fork_in_parent(cls) -> None:
|
188
|
+
pass
|
189
|
+
|
190
|
+
@classmethod # noqa
|
191
|
+
def _after_fork_in_child(cls) -> None:
|
192
|
+
pass
|
193
|
+
|
194
|
+
|
195
|
+
##
|
196
|
+
|
197
|
+
|
198
|
+
class _ForkDepthTracker(ForkHook):
|
199
|
+
_hook_priority = -1000
|
200
|
+
|
201
|
+
_fork_depth: ta.ClassVar[int] = 0
|
202
|
+
|
203
|
+
@classmethod
|
204
|
+
def _after_fork_in_child(cls) -> None:
|
205
|
+
cls._fork_depth += 1
|
206
|
+
|
207
|
+
@classmethod
|
208
|
+
def get_fork_depth(cls) -> int:
|
209
|
+
cls.install()
|
210
|
+
|
211
|
+
return cls._fork_depth
|
212
|
+
|
213
|
+
|
214
|
+
def get_fork_depth() -> int:
|
215
|
+
return _ForkDepthTracker.get_fork_depth()
|
omlish/os/pidfiles/manager.py
CHANGED
@@ -15,7 +15,10 @@ from .pidfile import Pidfile
|
|
15
15
|
|
16
16
|
class _PidfileManager:
|
17
17
|
"""
|
18
|
-
Manager for controlled inheritance of Pidfiles across forks.
|
18
|
+
Manager for controlled inheritance of Pidfiles across forks in the presence of multiple threads. There is of course
|
19
|
+
no safe or correct way to mix the use of fork and multiple active threads, and one should never write code which
|
20
|
+
does so, but in the Real World one may still find oneself in such a situation outside of their control (such as when
|
21
|
+
running under Pycharm's debugger which forces the use of forked multiprocessing).
|
19
22
|
|
20
23
|
Not implemented as an instantiated class as there is no way to unregister at_fork listeners, and because Pidfiles
|
21
24
|
may be pickled and there must be no possibility of accidentally unpickling and instantiating a new instance of the
|