omlish 0.0.0.dev224__py3-none-any.whl → 0.0.0.dev226__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 +2 -2
- omlish/asyncs/asyncio/subprocesses.py +19 -18
- omlish/asyncs/asyncio/timeouts.py +5 -2
- omlish/asyncs/asyncs.py +0 -1
- omlish/bootstrap/sys.py +2 -2
- omlish/dataclasses/impl/metaclass.py +5 -0
- omlish/diag/lslocks.py +64 -0
- omlish/diag/lsof.py +264 -0
- omlish/diag/ps.py +40 -21
- omlish/http/coro/server.py +5 -54
- omlish/http/coro/simple.py +1 -1
- omlish/http/coro/sockets.py +59 -0
- omlish/lang/__init__.py +8 -8
- omlish/lang/imports.py +22 -0
- omlish/libc.py +10 -0
- omlish/lite/dataclasses.py +23 -0
- omlish/lite/timeouts.py +202 -0
- omlish/multiprocessing/__init__.py +0 -7
- omlish/os/fcntl.py +59 -0
- omlish/os/pidfiles/__init__.py +0 -0
- omlish/os/pidfiles/manager.py +111 -0
- omlish/os/pidfiles/pidfile.py +152 -0
- omlish/secrets/crypto.py +1 -2
- omlish/secrets/openssl.py +1 -1
- omlish/secrets/tempssl.py +4 -7
- omlish/sockets/handlers.py +4 -0
- omlish/sockets/server/handlers.py +22 -0
- omlish/subprocesses/__init__.py +0 -0
- omlish/subprocesses/async_.py +97 -0
- omlish/subprocesses/base.py +221 -0
- omlish/subprocesses/run.py +138 -0
- omlish/subprocesses/sync.py +153 -0
- omlish/subprocesses/utils.py +22 -0
- omlish/subprocesses/wrap.py +23 -0
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev226.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev226.dist-info}/RECORD +41 -29
- omlish/lang/timeouts.py +0 -53
- omlish/os/pidfile.py +0 -69
- omlish/subprocesses.py +0 -510
- /omlish/{multiprocessing → os}/death.py +0 -0
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev226.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev226.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev226.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev226.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import typing as ta
|
4
|
+
|
5
|
+
from ...sockets.addresses import SocketAddress
|
6
|
+
from ...sockets.handlers import SocketHandler_
|
7
|
+
from ...sockets.io import SocketIoPair
|
8
|
+
from .server import CoroHttpServer
|
9
|
+
from .server import CoroHttpServerFactory
|
10
|
+
|
11
|
+
|
12
|
+
##
|
13
|
+
|
14
|
+
|
15
|
+
class CoroHttpServerSocketHandler(SocketHandler_):
|
16
|
+
def __init__(
|
17
|
+
self,
|
18
|
+
server_factory: CoroHttpServerFactory,
|
19
|
+
*,
|
20
|
+
log_handler: ta.Optional[ta.Callable[[CoroHttpServer, CoroHttpServer.AnyLogIo], None]] = None,
|
21
|
+
) -> None:
|
22
|
+
super().__init__()
|
23
|
+
|
24
|
+
self._server_factory = server_factory
|
25
|
+
self._log_handler = log_handler
|
26
|
+
|
27
|
+
def __call__(self, client_address: SocketAddress, fp: SocketIoPair) -> None:
|
28
|
+
server = self._server_factory(client_address)
|
29
|
+
|
30
|
+
gen = server.coro_handle()
|
31
|
+
|
32
|
+
o = next(gen)
|
33
|
+
while True:
|
34
|
+
if isinstance(o, CoroHttpServer.AnyLogIo):
|
35
|
+
i = None
|
36
|
+
if self._log_handler is not None:
|
37
|
+
self._log_handler(server, o)
|
38
|
+
|
39
|
+
elif isinstance(o, CoroHttpServer.ReadIo):
|
40
|
+
i = fp.r.read(o.sz)
|
41
|
+
|
42
|
+
elif isinstance(o, CoroHttpServer.ReadLineIo):
|
43
|
+
i = fp.r.readline(o.sz)
|
44
|
+
|
45
|
+
elif isinstance(o, CoroHttpServer.WriteIo):
|
46
|
+
i = None
|
47
|
+
fp.w.write(o.data)
|
48
|
+
fp.w.flush()
|
49
|
+
|
50
|
+
else:
|
51
|
+
raise TypeError(o)
|
52
|
+
|
53
|
+
try:
|
54
|
+
if i is not None:
|
55
|
+
o = gen.send(i)
|
56
|
+
else:
|
57
|
+
o = next(gen)
|
58
|
+
except StopIteration:
|
59
|
+
break
|
omlish/lang/__init__.py
CHANGED
@@ -138,6 +138,7 @@ from .generators import ( # noqa
|
|
138
138
|
from .imports import ( # noqa
|
139
139
|
can_import,
|
140
140
|
import_all,
|
141
|
+
import_attr,
|
141
142
|
import_module,
|
142
143
|
import_module_attr,
|
143
144
|
lazy_import,
|
@@ -225,14 +226,6 @@ from .sys import ( # noqa
|
|
225
226
|
is_gil_enabled,
|
226
227
|
)
|
227
228
|
|
228
|
-
from .timeouts import ( # noqa
|
229
|
-
DeadlineTimeout,
|
230
|
-
InfiniteTimeout,
|
231
|
-
Timeout,
|
232
|
-
TimeoutLike,
|
233
|
-
timeout,
|
234
|
-
)
|
235
|
-
|
236
229
|
from .typing import ( # noqa
|
237
230
|
BytesLike,
|
238
231
|
SequenceNotStr,
|
@@ -243,6 +236,13 @@ from .typing import ( # noqa
|
|
243
236
|
|
244
237
|
##
|
245
238
|
|
239
|
+
from ..lite.timeouts import ( # noqa
|
240
|
+
DeadlineTimeout,
|
241
|
+
InfiniteTimeout,
|
242
|
+
Timeout,
|
243
|
+
TimeoutLike,
|
244
|
+
)
|
245
|
+
|
246
246
|
from ..lite.types import ( # noqa
|
247
247
|
BUILTIN_SCALAR_ITERABLE_TYPES,
|
248
248
|
)
|
omlish/lang/imports.py
CHANGED
@@ -108,6 +108,28 @@ def import_module_attr(dotted_path: str) -> ta.Any:
|
|
108
108
|
raise AttributeError(f'Module {module_name!r} has no attr {class_name!r}') from None
|
109
109
|
|
110
110
|
|
111
|
+
def import_attr(dotted_path: str) -> ta.Any:
|
112
|
+
parts = dotted_path.split('.')
|
113
|
+
mod: ta.Any = None
|
114
|
+
mod_pos = 0
|
115
|
+
while mod_pos < len(parts):
|
116
|
+
mod_name = '.'.join(parts[:mod_pos + 1])
|
117
|
+
try:
|
118
|
+
mod = importlib.import_module(mod_name)
|
119
|
+
except ImportError:
|
120
|
+
break
|
121
|
+
mod_pos += 1
|
122
|
+
if mod is None:
|
123
|
+
raise ImportError(dotted_path)
|
124
|
+
obj = mod
|
125
|
+
for att_pos in range(mod_pos, len(parts)):
|
126
|
+
obj = getattr(obj, parts[att_pos])
|
127
|
+
return obj
|
128
|
+
|
129
|
+
|
130
|
+
##
|
131
|
+
|
132
|
+
|
111
133
|
SPECIAL_IMPORTABLE: ta.AbstractSet[str] = frozenset([
|
112
134
|
'__init__.py',
|
113
135
|
'__main__.py',
|
omlish/libc.py
CHANGED
@@ -392,6 +392,14 @@ if LINUX or DARWIN:
|
|
392
392
|
return CMSG_ALIGN(ct.sizeof(cmsghdr)) + sz
|
393
393
|
|
394
394
|
def sendfd(sock, fd, data='.'):
|
395
|
+
"""
|
396
|
+
Note: stdlib as of 3.7:
|
397
|
+
|
398
|
+
https://github.com/python/cpython/blob/84ed9a68bd9a13252b376b21a9167dabae254325/Lib/multiprocessing/reduction.py#L141
|
399
|
+
|
400
|
+
But still kept around due to other use of cmsg machinery.
|
401
|
+
""" # noqa
|
402
|
+
|
395
403
|
if not data:
|
396
404
|
raise ValueError(data)
|
397
405
|
|
@@ -424,6 +432,8 @@ if LINUX or DARWIN:
|
|
424
432
|
return libc.sendmsg(sock, msgh, 0)
|
425
433
|
|
426
434
|
def recvfd(sock, buf_len=4096):
|
435
|
+
"""See sendfd."""
|
436
|
+
|
427
437
|
if buf_len < 1:
|
428
438
|
raise ValueError(buf_len)
|
429
439
|
|
omlish/lite/dataclasses.py
CHANGED
@@ -42,3 +42,26 @@ def dataclass_maybe_post_init(sup: ta.Any) -> bool:
|
|
42
42
|
return False
|
43
43
|
fn()
|
44
44
|
return True
|
45
|
+
|
46
|
+
|
47
|
+
def dataclass_repr_filtered(
|
48
|
+
obj: ta.Any,
|
49
|
+
fn: ta.Callable[[ta.Any, dc.Field, ta.Any], bool],
|
50
|
+
) -> str:
|
51
|
+
return (
|
52
|
+
f'{obj.__class__.__qualname__}(' +
|
53
|
+
', '.join([
|
54
|
+
f'{f.name}={v!r}'
|
55
|
+
for f in dc.fields(obj)
|
56
|
+
if fn(obj, f, v := getattr(obj, f.name))
|
57
|
+
]) +
|
58
|
+
')'
|
59
|
+
)
|
60
|
+
|
61
|
+
|
62
|
+
def dataclass_repr_omit_none(obj: ta.Any) -> str:
|
63
|
+
return dataclass_repr_filtered(obj, lambda o, f, v: v is not None)
|
64
|
+
|
65
|
+
|
66
|
+
def dataclass_repr_omit_falsey(obj: ta.Any) -> str:
|
67
|
+
return dataclass_repr_filtered(obj, lambda o, f, v: bool(v))
|
omlish/lite/timeouts.py
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
"""
|
3
|
+
TODO:
|
4
|
+
- Event (/ Predicate)
|
5
|
+
"""
|
6
|
+
import abc
|
7
|
+
import time
|
8
|
+
import typing as ta
|
9
|
+
|
10
|
+
|
11
|
+
TimeoutLike = ta.Union['Timeout', 'Timeout.Default', ta.Iterable['TimeoutLike'], float] # ta.TypeAlias
|
12
|
+
|
13
|
+
|
14
|
+
##
|
15
|
+
|
16
|
+
|
17
|
+
class Timeout(abc.ABC):
|
18
|
+
@property
|
19
|
+
@abc.abstractmethod
|
20
|
+
def can_expire(self) -> bool:
|
21
|
+
"""Indicates whether or not this timeout will ever expire."""
|
22
|
+
|
23
|
+
raise NotImplementedError
|
24
|
+
|
25
|
+
@abc.abstractmethod
|
26
|
+
def expired(self) -> bool:
|
27
|
+
"""Return whether or not this timeout has expired."""
|
28
|
+
|
29
|
+
raise NotImplementedError
|
30
|
+
|
31
|
+
@abc.abstractmethod
|
32
|
+
def remaining(self) -> float:
|
33
|
+
"""Returns the time (in seconds) remaining until the timeout expires. May be negative and/or infinite."""
|
34
|
+
|
35
|
+
raise NotImplementedError
|
36
|
+
|
37
|
+
@abc.abstractmethod
|
38
|
+
def __call__(self) -> float:
|
39
|
+
"""Returns the time (in seconds) remaining until the timeout expires, or raises if the timeout has expired."""
|
40
|
+
|
41
|
+
raise NotImplementedError
|
42
|
+
|
43
|
+
@abc.abstractmethod
|
44
|
+
def or_(self, o: ta.Any) -> ta.Any:
|
45
|
+
"""Evaluates time remaining via remaining() if this timeout can expire, otherwise returns `o`."""
|
46
|
+
|
47
|
+
raise NotImplementedError
|
48
|
+
|
49
|
+
#
|
50
|
+
|
51
|
+
@classmethod
|
52
|
+
def _now(cls) -> float:
|
53
|
+
return time.time()
|
54
|
+
|
55
|
+
#
|
56
|
+
|
57
|
+
class Default:
|
58
|
+
def __new__(cls, *args, **kwargs): # noqa
|
59
|
+
raise TypeError
|
60
|
+
|
61
|
+
class _NOT_SPECIFIED: # noqa
|
62
|
+
def __new__(cls, *args, **kwargs): # noqa
|
63
|
+
raise TypeError
|
64
|
+
|
65
|
+
@classmethod
|
66
|
+
def of(
|
67
|
+
cls,
|
68
|
+
obj: ta.Optional[TimeoutLike],
|
69
|
+
default: ta.Union[TimeoutLike, ta.Type[_NOT_SPECIFIED]] = _NOT_SPECIFIED,
|
70
|
+
) -> 'Timeout':
|
71
|
+
if obj is None:
|
72
|
+
return InfiniteTimeout()
|
73
|
+
|
74
|
+
elif isinstance(obj, Timeout):
|
75
|
+
return obj
|
76
|
+
|
77
|
+
elif isinstance(obj, (float, int)):
|
78
|
+
return DeadlineTimeout(cls._now() + obj)
|
79
|
+
|
80
|
+
elif isinstance(obj, ta.Iterable):
|
81
|
+
return CompositeTimeout(*[Timeout.of(c) for c in obj])
|
82
|
+
|
83
|
+
elif obj is Timeout.Default:
|
84
|
+
if default is Timeout._NOT_SPECIFIED or default is Timeout.Default:
|
85
|
+
raise RuntimeError('Must specify a default timeout')
|
86
|
+
|
87
|
+
else:
|
88
|
+
return Timeout.of(default) # type: ignore[arg-type]
|
89
|
+
|
90
|
+
else:
|
91
|
+
raise TypeError(obj)
|
92
|
+
|
93
|
+
@classmethod
|
94
|
+
def of_deadline(cls, deadline: float) -> 'DeadlineTimeout':
|
95
|
+
return DeadlineTimeout(deadline)
|
96
|
+
|
97
|
+
@classmethod
|
98
|
+
def of_predicate(cls, expired_fn: ta.Callable[[], bool]) -> 'PredicateTimeout':
|
99
|
+
return PredicateTimeout(expired_fn)
|
100
|
+
|
101
|
+
|
102
|
+
class DeadlineTimeout(Timeout):
|
103
|
+
def __init__(
|
104
|
+
self,
|
105
|
+
deadline: float,
|
106
|
+
exc: ta.Union[ta.Type[BaseException], BaseException] = TimeoutError,
|
107
|
+
) -> None:
|
108
|
+
super().__init__()
|
109
|
+
|
110
|
+
self.deadline = deadline
|
111
|
+
self.exc = exc
|
112
|
+
|
113
|
+
@property
|
114
|
+
def can_expire(self) -> bool:
|
115
|
+
return True
|
116
|
+
|
117
|
+
def expired(self) -> bool:
|
118
|
+
return not (self.remaining() > 0)
|
119
|
+
|
120
|
+
def remaining(self) -> float:
|
121
|
+
return self.deadline - self._now()
|
122
|
+
|
123
|
+
def __call__(self) -> float:
|
124
|
+
if (rem := self.remaining()) > 0:
|
125
|
+
return rem
|
126
|
+
raise self.exc
|
127
|
+
|
128
|
+
def or_(self, o: ta.Any) -> ta.Any:
|
129
|
+
return self()
|
130
|
+
|
131
|
+
|
132
|
+
class InfiniteTimeout(Timeout):
|
133
|
+
@property
|
134
|
+
def can_expire(self) -> bool:
|
135
|
+
return False
|
136
|
+
|
137
|
+
def expired(self) -> bool:
|
138
|
+
return False
|
139
|
+
|
140
|
+
def remaining(self) -> float:
|
141
|
+
return float('inf')
|
142
|
+
|
143
|
+
def __call__(self) -> float:
|
144
|
+
return float('inf')
|
145
|
+
|
146
|
+
def or_(self, o: ta.Any) -> ta.Any:
|
147
|
+
return o
|
148
|
+
|
149
|
+
|
150
|
+
class CompositeTimeout(Timeout):
|
151
|
+
def __init__(self, *children: Timeout) -> None:
|
152
|
+
super().__init__()
|
153
|
+
|
154
|
+
self.children = children
|
155
|
+
|
156
|
+
@property
|
157
|
+
def can_expire(self) -> bool:
|
158
|
+
return any(c.can_expire for c in self.children)
|
159
|
+
|
160
|
+
def expired(self) -> bool:
|
161
|
+
return any(c.expired() for c in self.children)
|
162
|
+
|
163
|
+
def remaining(self) -> float:
|
164
|
+
return min(c.remaining() for c in self.children)
|
165
|
+
|
166
|
+
def __call__(self) -> float:
|
167
|
+
return min(c() for c in self.children)
|
168
|
+
|
169
|
+
def or_(self, o: ta.Any) -> ta.Any:
|
170
|
+
if self.can_expire:
|
171
|
+
return self()
|
172
|
+
return o
|
173
|
+
|
174
|
+
|
175
|
+
class PredicateTimeout(Timeout):
|
176
|
+
def __init__(
|
177
|
+
self,
|
178
|
+
expired_fn: ta.Callable[[], bool],
|
179
|
+
exc: ta.Union[ta.Type[BaseException], BaseException] = TimeoutError,
|
180
|
+
) -> None:
|
181
|
+
super().__init__()
|
182
|
+
|
183
|
+
self.expired_fn = expired_fn
|
184
|
+
self.exc = exc
|
185
|
+
|
186
|
+
@property
|
187
|
+
def can_expire(self) -> bool:
|
188
|
+
return True
|
189
|
+
|
190
|
+
def expired(self) -> bool:
|
191
|
+
return self.expired_fn()
|
192
|
+
|
193
|
+
def remaining(self) -> float:
|
194
|
+
return float('inf')
|
195
|
+
|
196
|
+
def __call__(self) -> float:
|
197
|
+
if not self.expired_fn():
|
198
|
+
return float('inf')
|
199
|
+
raise self.exc
|
200
|
+
|
201
|
+
def or_(self, o: ta.Any) -> ta.Any:
|
202
|
+
return self()
|
omlish/os/fcntl.py
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import dataclasses as dc
|
4
|
+
import os
|
5
|
+
import struct
|
6
|
+
import sys
|
7
|
+
import typing as ta
|
8
|
+
|
9
|
+
|
10
|
+
@dc.dataclass(frozen=True)
|
11
|
+
class FcntlLockData:
|
12
|
+
# cmd = {F_SETLK, F_SETLKW, F_GETLK}
|
13
|
+
|
14
|
+
type: int # {F_RDLCK, F_WRLCK, F_UNLCK}
|
15
|
+
whence: int = os.SEEK_SET
|
16
|
+
start: int = 0
|
17
|
+
len: int = 0
|
18
|
+
pid: int = 0
|
19
|
+
|
20
|
+
#
|
21
|
+
|
22
|
+
_STRUCT_PACK_BY_PLATFORM: ta.ClassVar[ta.Mapping[str, ta.Sequence[ta.Tuple[str, str]]]] = {
|
23
|
+
'linux': [
|
24
|
+
('type', 'h'),
|
25
|
+
('whence', 'h'),
|
26
|
+
('start', 'q'),
|
27
|
+
('len', 'q'),
|
28
|
+
('pid', 'i'),
|
29
|
+
],
|
30
|
+
'darwin': [
|
31
|
+
('start', 'q'),
|
32
|
+
('len', 'q'),
|
33
|
+
('pid', 'i'),
|
34
|
+
('type', 'h'),
|
35
|
+
('whence', 'h'),
|
36
|
+
],
|
37
|
+
}
|
38
|
+
|
39
|
+
def pack(self) -> bytes:
|
40
|
+
try:
|
41
|
+
pack = self._STRUCT_PACK_BY_PLATFORM[sys.platform]
|
42
|
+
except KeyError:
|
43
|
+
raise OSError from None
|
44
|
+
|
45
|
+
fmt = ''.join(f for _, f in pack)
|
46
|
+
tup = [getattr(self, a) for a, _ in pack]
|
47
|
+
return struct.pack(fmt, *tup)
|
48
|
+
|
49
|
+
@classmethod
|
50
|
+
def unpack(cls, data: bytes) -> 'FcntlLockData':
|
51
|
+
try:
|
52
|
+
pack = cls._STRUCT_PACK_BY_PLATFORM[sys.platform]
|
53
|
+
except KeyError:
|
54
|
+
raise OSError from None
|
55
|
+
|
56
|
+
fmt = ''.join(f for _, f in pack)
|
57
|
+
tup = struct.unpack(fmt, data)
|
58
|
+
kw = {a: v for (a, _), v in zip(pack, tup)}
|
59
|
+
return FcntlLockData(**kw)
|
File without changes
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# ruff: noqa: UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import contextlib
|
4
|
+
import os
|
5
|
+
import threading
|
6
|
+
import typing as ta
|
7
|
+
import weakref
|
8
|
+
|
9
|
+
from ...lite.check import check
|
10
|
+
from .pidfile import Pidfile
|
11
|
+
|
12
|
+
|
13
|
+
##
|
14
|
+
|
15
|
+
|
16
|
+
class _PidfileManager:
|
17
|
+
"""
|
18
|
+
Manager for controlled inheritance of Pidfiles across forks.
|
19
|
+
|
20
|
+
Not implemented as an instantiated class as there is no way to unregister at_fork listeners, and because Pidfiles
|
21
|
+
may be pickled and there must be no possibility of accidentally unpickling and instantiating a new instance of the
|
22
|
+
manager.
|
23
|
+
"""
|
24
|
+
|
25
|
+
def __new__(cls, *args, **kwargs): # noqa
|
26
|
+
raise TypeError
|
27
|
+
|
28
|
+
_lock: ta.ClassVar[threading.Lock] = threading.Lock()
|
29
|
+
_installed: ta.ClassVar[bool] = False
|
30
|
+
_pidfile_threads: ta.ClassVar[ta.MutableMapping[Pidfile, threading.Thread]] = weakref.WeakKeyDictionary()
|
31
|
+
|
32
|
+
@classmethod
|
33
|
+
def _before_fork(cls) -> None:
|
34
|
+
cls._lock.acquire()
|
35
|
+
|
36
|
+
@classmethod
|
37
|
+
def _after_fork_in_parent(cls) -> None:
|
38
|
+
cls._lock.release()
|
39
|
+
|
40
|
+
@classmethod
|
41
|
+
def _after_fork_in_child(cls) -> None:
|
42
|
+
th = threading.current_thread()
|
43
|
+
for pf, pf_th in list(cls._pidfile_threads.items()):
|
44
|
+
if pf_th is not th:
|
45
|
+
pf.close()
|
46
|
+
del cls._pidfile_threads[pf]
|
47
|
+
|
48
|
+
cls._lock.release()
|
49
|
+
|
50
|
+
#
|
51
|
+
|
52
|
+
@classmethod
|
53
|
+
def _install(cls) -> None:
|
54
|
+
check.state(not cls._installed)
|
55
|
+
|
56
|
+
os.register_at_fork(
|
57
|
+
before=cls._before_fork,
|
58
|
+
after_in_parent=cls._after_fork_in_parent,
|
59
|
+
after_in_child=cls._after_fork_in_child,
|
60
|
+
)
|
61
|
+
|
62
|
+
cls._installed = True
|
63
|
+
|
64
|
+
@classmethod
|
65
|
+
def install(cls) -> bool:
|
66
|
+
with cls._lock:
|
67
|
+
if cls._installed:
|
68
|
+
return False
|
69
|
+
|
70
|
+
cls._install()
|
71
|
+
return True
|
72
|
+
|
73
|
+
@classmethod
|
74
|
+
@contextlib.contextmanager
|
75
|
+
def inheritable_pidfile_context(
|
76
|
+
cls,
|
77
|
+
path: str,
|
78
|
+
*,
|
79
|
+
inheritable: bool = True,
|
80
|
+
**kwargs: ta.Any,
|
81
|
+
) -> ta.Iterator[Pidfile]:
|
82
|
+
"""
|
83
|
+
A contextmanager for creating and managing a Pidfile which will only be inherited by forks of the calling /
|
84
|
+
creating thread.
|
85
|
+
"""
|
86
|
+
|
87
|
+
check.arg(inheritable)
|
88
|
+
|
89
|
+
cls.install()
|
90
|
+
|
91
|
+
pf = Pidfile(
|
92
|
+
path,
|
93
|
+
inheritable=False,
|
94
|
+
**kwargs,
|
95
|
+
)
|
96
|
+
|
97
|
+
with cls._lock:
|
98
|
+
cls._pidfile_threads[pf] = threading.current_thread()
|
99
|
+
try:
|
100
|
+
|
101
|
+
with pf:
|
102
|
+
os.set_inheritable(check.not_none(pf.fileno()), True)
|
103
|
+
yield pf
|
104
|
+
|
105
|
+
finally:
|
106
|
+
with cls._lock:
|
107
|
+
del cls._pidfile_threads[pf]
|
108
|
+
|
109
|
+
|
110
|
+
def open_inheritable_pidfile(path: str, **kwargs: ta.Any) -> ta.ContextManager[Pidfile]:
|
111
|
+
return _PidfileManager.inheritable_pidfile_context(path, **kwargs) # noqa
|