omlish 0.0.0.dev224__py3-none-any.whl → 0.0.0.dev225__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 +15 -15
- omlish/asyncs/asyncs.py +0 -1
- omlish/bootstrap/sys.py +2 -2
- omlish/dataclasses/impl/metaclass.py +5 -0
- 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 +1 -0
- omlish/lang/imports.py +22 -0
- omlish/libc.py +10 -0
- omlish/multiprocessing/__init__.py +0 -7
- omlish/os/pidfiles/__init__.py +0 -0
- omlish/os/pidfiles/manager.py +97 -0
- omlish/os/pidfiles/pidfile.py +142 -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 +96 -0
- omlish/subprocesses/base.py +215 -0
- omlish/subprocesses/run.py +98 -0
- omlish/subprocesses/sync.py +147 -0
- omlish/subprocesses/utils.py +22 -0
- omlish/subprocesses/wrap.py +23 -0
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev225.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev225.dist-info}/RECORD +34 -25
- 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.dev225.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev225.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev225.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev225.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
@@ -10,9 +10,9 @@ import sys
|
|
10
10
|
import typing as ta
|
11
11
|
|
12
12
|
from ...lite.check import check
|
13
|
-
from ...subprocesses import AbstractAsyncSubprocesses
|
14
|
-
from ...subprocesses import SubprocessRun
|
15
|
-
from ...subprocesses import SubprocessRunOutput
|
13
|
+
from ...subprocesses.async_ import AbstractAsyncSubprocesses
|
14
|
+
from ...subprocesses.run import SubprocessRun
|
15
|
+
from ...subprocesses.run import SubprocessRunOutput
|
16
16
|
from .timeouts import asyncio_maybe_timeout
|
17
17
|
|
18
18
|
|
@@ -157,19 +157,19 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
|
|
157
157
|
timeout: ta.Optional[float] = None,
|
158
158
|
**kwargs: ta.Any,
|
159
159
|
) -> ta.AsyncGenerator[asyncio.subprocess.Process, None]:
|
160
|
-
fac: ta.Any
|
161
|
-
if shell:
|
162
|
-
fac = functools.partial(
|
163
|
-
asyncio.create_subprocess_shell,
|
164
|
-
check.single(cmd),
|
165
|
-
)
|
166
|
-
else:
|
167
|
-
fac = functools.partial(
|
168
|
-
asyncio.create_subprocess_exec,
|
169
|
-
*cmd,
|
170
|
-
)
|
171
|
-
|
172
160
|
with self.prepare_and_wrap( *cmd, shell=shell, **kwargs) as (cmd, kwargs): # noqa
|
161
|
+
fac: ta.Any
|
162
|
+
if shell:
|
163
|
+
fac = functools.partial(
|
164
|
+
asyncio.create_subprocess_shell,
|
165
|
+
check.single(cmd),
|
166
|
+
)
|
167
|
+
else:
|
168
|
+
fac = functools.partial(
|
169
|
+
asyncio.create_subprocess_exec,
|
170
|
+
*cmd,
|
171
|
+
)
|
172
|
+
|
173
173
|
proc: asyncio.subprocess.Process = await fac(**kwargs)
|
174
174
|
try:
|
175
175
|
yield proc
|
omlish/asyncs/asyncs.py
CHANGED
omlish/bootstrap/sys.py
CHANGED
@@ -23,13 +23,13 @@ if ta.TYPE_CHECKING:
|
|
23
23
|
from .. import libc
|
24
24
|
from ..formats import dotenv
|
25
25
|
from ..logs import all as logs
|
26
|
-
from ..os import pidfile
|
26
|
+
from ..os.pidfiles import pidfile
|
27
27
|
|
28
28
|
else:
|
29
29
|
libc = lang.proxy_import('..libc', __package__)
|
30
30
|
logs = lang.proxy_import('..logs', __package__)
|
31
31
|
dotenv = lang.proxy_import('..formats.dotenv', __package__)
|
32
|
-
pidfile = lang.proxy_import('..os.pidfile', __package__)
|
32
|
+
pidfile = lang.proxy_import('..os.pidfiles.pidfile', __package__)
|
33
33
|
|
34
34
|
|
35
35
|
##
|
@@ -188,8 +188,13 @@ class Data(
|
|
188
188
|
metaclass=DataMeta,
|
189
189
|
):
|
190
190
|
def __init__(self, *args, **kwargs):
|
191
|
+
# Typechecking barrier
|
191
192
|
super().__init__(*args, **kwargs)
|
192
193
|
|
194
|
+
def __init_subclass__(cls, **kwargs):
|
195
|
+
# Typechecking barrier
|
196
|
+
super().__init_subclass__(**kwargs)
|
197
|
+
|
193
198
|
def __post_init__(self, *args, **kwargs) -> None:
|
194
199
|
try:
|
195
200
|
spi = super().__post_init__ # type: ignore # noqa
|
omlish/http/coro/server.py
CHANGED
@@ -65,8 +65,6 @@ import typing as ta
|
|
65
65
|
|
66
66
|
from ...lite.check import check
|
67
67
|
from ...sockets.addresses import SocketAddress
|
68
|
-
from ...sockets.handlers import SocketHandler_
|
69
|
-
from ...sockets.io import SocketIoPair
|
70
68
|
from ..handlers import HttpHandler
|
71
69
|
from ..handlers import HttpHandlerRequest
|
72
70
|
from ..handlers import HttpHandlerResponseData
|
@@ -423,6 +421,9 @@ class CoroHttpServer:
|
|
423
421
|
def coro_handle(self) -> ta.Generator[Io, ta.Optional[bytes], None]:
|
424
422
|
return self._coro_run_handler(self._coro_handle_one())
|
425
423
|
|
424
|
+
class Close(Exception): # noqa
|
425
|
+
pass
|
426
|
+
|
426
427
|
def _coro_run_handler(
|
427
428
|
self,
|
428
429
|
gen: ta.Generator[
|
@@ -462,7 +463,7 @@ class CoroHttpServer:
|
|
462
463
|
|
463
464
|
try:
|
464
465
|
o = gen.send(i)
|
465
|
-
except
|
466
|
+
except self.Close:
|
466
467
|
return
|
467
468
|
except StopIteration:
|
468
469
|
break
|
@@ -491,7 +492,7 @@ class CoroHttpServer:
|
|
491
492
|
break
|
492
493
|
|
493
494
|
if isinstance(parsed, EmptyParsedHttpResult):
|
494
|
-
raise
|
495
|
+
raise self.Close
|
495
496
|
|
496
497
|
if isinstance(parsed, ParseHttpRequestError):
|
497
498
|
err = self._build_error(
|
@@ -581,53 +582,3 @@ class CoroHttpServer:
|
|
581
582
|
handler_response.close()
|
582
583
|
|
583
584
|
raise
|
584
|
-
|
585
|
-
|
586
|
-
##
|
587
|
-
|
588
|
-
|
589
|
-
class CoroHttpServerSocketHandler(SocketHandler_):
|
590
|
-
def __init__(
|
591
|
-
self,
|
592
|
-
server_factory: CoroHttpServerFactory,
|
593
|
-
*,
|
594
|
-
log_handler: ta.Optional[ta.Callable[[CoroHttpServer, CoroHttpServer.AnyLogIo], None]] = None,
|
595
|
-
) -> None:
|
596
|
-
super().__init__()
|
597
|
-
|
598
|
-
self._server_factory = server_factory
|
599
|
-
self._log_handler = log_handler
|
600
|
-
|
601
|
-
def __call__(self, client_address: SocketAddress, fp: SocketIoPair) -> None:
|
602
|
-
server = self._server_factory(client_address)
|
603
|
-
|
604
|
-
gen = server.coro_handle()
|
605
|
-
|
606
|
-
o = next(gen)
|
607
|
-
while True:
|
608
|
-
if isinstance(o, CoroHttpServer.AnyLogIo):
|
609
|
-
i = None
|
610
|
-
if self._log_handler is not None:
|
611
|
-
self._log_handler(server, o)
|
612
|
-
|
613
|
-
elif isinstance(o, CoroHttpServer.ReadIo):
|
614
|
-
i = fp.r.read(o.sz)
|
615
|
-
|
616
|
-
elif isinstance(o, CoroHttpServer.ReadLineIo):
|
617
|
-
i = fp.r.readline(o.sz)
|
618
|
-
|
619
|
-
elif isinstance(o, CoroHttpServer.WriteIo):
|
620
|
-
i = None
|
621
|
-
fp.w.write(o.data)
|
622
|
-
fp.w.flush()
|
623
|
-
|
624
|
-
else:
|
625
|
-
raise TypeError(o)
|
626
|
-
|
627
|
-
try:
|
628
|
-
if i is not None:
|
629
|
-
o = gen.send(i)
|
630
|
-
else:
|
631
|
-
o = next(gen)
|
632
|
-
except StopIteration:
|
633
|
-
break
|
omlish/http/coro/simple.py
CHANGED
@@ -21,7 +21,7 @@ from ..parsing import HttpRequestParser
|
|
21
21
|
from ..versions import HttpProtocolVersion
|
22
22
|
from ..versions import HttpProtocolVersions
|
23
23
|
from .server import CoroHttpServer
|
24
|
-
from .
|
24
|
+
from .sockets import CoroHttpServerSocketHandler
|
25
25
|
|
26
26
|
|
27
27
|
if ta.TYPE_CHECKING:
|
@@ -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
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
|
|
File without changes
|
@@ -0,0 +1,97 @@
|
|
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
|
+
def __new__(cls, *args, **kwargs): # noqa
|
18
|
+
raise TypeError
|
19
|
+
|
20
|
+
_lock: ta.ClassVar[threading.Lock] = threading.Lock()
|
21
|
+
_installed: ta.ClassVar[bool] = False
|
22
|
+
_pidfile_threads: ta.ClassVar[ta.MutableMapping[Pidfile, threading.Thread]] = weakref.WeakKeyDictionary()
|
23
|
+
|
24
|
+
@classmethod
|
25
|
+
def _before_fork(cls) -> None:
|
26
|
+
cls._lock.acquire()
|
27
|
+
|
28
|
+
@classmethod
|
29
|
+
def _after_fork_in_parent(cls) -> None:
|
30
|
+
cls._lock.release()
|
31
|
+
|
32
|
+
@classmethod
|
33
|
+
def _after_fork_in_child(cls) -> None:
|
34
|
+
th = threading.current_thread()
|
35
|
+
for pf, pf_th in list(cls._pidfile_threads.items()):
|
36
|
+
if pf_th is not th:
|
37
|
+
pf.close()
|
38
|
+
del cls._pidfile_threads[pf]
|
39
|
+
|
40
|
+
cls._lock.release()
|
41
|
+
|
42
|
+
#
|
43
|
+
|
44
|
+
@classmethod
|
45
|
+
def _install(cls) -> None:
|
46
|
+
check.state(not cls._installed)
|
47
|
+
|
48
|
+
os.register_at_fork(
|
49
|
+
before=cls._before_fork,
|
50
|
+
after_in_parent=cls._after_fork_in_parent,
|
51
|
+
after_in_child=cls._after_fork_in_child,
|
52
|
+
)
|
53
|
+
|
54
|
+
cls._installed = True
|
55
|
+
|
56
|
+
@classmethod
|
57
|
+
def install(cls) -> bool:
|
58
|
+
with cls._lock:
|
59
|
+
if cls._installed:
|
60
|
+
return False
|
61
|
+
|
62
|
+
cls._install()
|
63
|
+
return True
|
64
|
+
|
65
|
+
@classmethod
|
66
|
+
@contextlib.contextmanager
|
67
|
+
def inheritable_pidfile_context(
|
68
|
+
cls,
|
69
|
+
path: str,
|
70
|
+
*,
|
71
|
+
inheritable: bool = True,
|
72
|
+
**kwargs: ta.Any,
|
73
|
+
) -> ta.Iterator[Pidfile]:
|
74
|
+
check.arg(inheritable)
|
75
|
+
|
76
|
+
cls.install()
|
77
|
+
|
78
|
+
pf = Pidfile(
|
79
|
+
path,
|
80
|
+
inheritable=False,
|
81
|
+
**kwargs,
|
82
|
+
)
|
83
|
+
|
84
|
+
with cls._lock:
|
85
|
+
cls._pidfile_threads[pf] = threading.current_thread()
|
86
|
+
try:
|
87
|
+
with pf:
|
88
|
+
os.set_inheritable(check.not_none(pf.fileno()), True)
|
89
|
+
yield pf
|
90
|
+
|
91
|
+
finally:
|
92
|
+
with cls._lock:
|
93
|
+
del cls._pidfile_threads[pf]
|
94
|
+
|
95
|
+
|
96
|
+
def open_inheritable_pidfile(path: str, **kwargs: ta.Any) -> ta.ContextManager[Pidfile]:
|
97
|
+
return _PidfileManager.inheritable_pidfile_context(path, **kwargs) # noqa
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# ruff: noqa: UP007
|
2
|
+
# @omlish-lite
|
3
|
+
"""
|
4
|
+
TODO:
|
5
|
+
- reliable pid retrieval
|
6
|
+
- contents are *ignored*, just advisory
|
7
|
+
- check double-check:
|
8
|
+
- 1) get pid of flock holder
|
9
|
+
- 2) get pidfd to that
|
10
|
+
- 3) recheck current pid of flock holder == that pid
|
11
|
+
- racy as to if it's a different actual process as initial check, just with same pid, but due to 'identity' / semantic
|
12
|
+
meaning of the named pidfile the processes are considered equivalent
|
13
|
+
"""
|
14
|
+
import fcntl
|
15
|
+
import os
|
16
|
+
import signal
|
17
|
+
import typing as ta
|
18
|
+
|
19
|
+
|
20
|
+
##
|
21
|
+
|
22
|
+
|
23
|
+
class Pidfile:
|
24
|
+
def __init__(
|
25
|
+
self,
|
26
|
+
path: str,
|
27
|
+
*,
|
28
|
+
inheritable: bool = True,
|
29
|
+
) -> None:
|
30
|
+
super().__init__()
|
31
|
+
|
32
|
+
self._path = path
|
33
|
+
self._inheritable = inheritable
|
34
|
+
|
35
|
+
def __repr__(self) -> str:
|
36
|
+
return f'{self.__class__.__name__}({self._path!r})'
|
37
|
+
|
38
|
+
#
|
39
|
+
|
40
|
+
_f: ta.TextIO
|
41
|
+
|
42
|
+
def fileno(self) -> ta.Optional[int]:
|
43
|
+
if hasattr(self, '_f'):
|
44
|
+
return self._f.fileno()
|
45
|
+
else:
|
46
|
+
return None
|
47
|
+
|
48
|
+
#
|
49
|
+
|
50
|
+
def __enter__(self) -> 'Pidfile':
|
51
|
+
fd = os.open(self._path, os.O_RDWR | os.O_CREAT, 0o600)
|
52
|
+
|
53
|
+
try:
|
54
|
+
if self._inheritable:
|
55
|
+
os.set_inheritable(fd, True)
|
56
|
+
|
57
|
+
f = os.fdopen(fd, 'r+')
|
58
|
+
|
59
|
+
except Exception:
|
60
|
+
try:
|
61
|
+
os.close(fd)
|
62
|
+
except Exception: # noqa
|
63
|
+
pass
|
64
|
+
raise
|
65
|
+
|
66
|
+
self._f = f
|
67
|
+
return self
|
68
|
+
|
69
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
70
|
+
self.close()
|
71
|
+
|
72
|
+
#
|
73
|
+
|
74
|
+
def __getstate__(self):
|
75
|
+
state = self.__dict__.copy()
|
76
|
+
|
77
|
+
if '_f' in state:
|
78
|
+
if os.get_inheritable(fd := state.pop('_f').fileno()):
|
79
|
+
state['__fd'] = fd
|
80
|
+
|
81
|
+
return state
|
82
|
+
|
83
|
+
def __setstate__(self, state):
|
84
|
+
if '_f' in state:
|
85
|
+
raise RuntimeError
|
86
|
+
|
87
|
+
if '__fd' in state:
|
88
|
+
state['_f'] = os.fdopen(state.pop('__fd'), 'r+')
|
89
|
+
|
90
|
+
self.__dict__.update(state)
|
91
|
+
|
92
|
+
#
|
93
|
+
|
94
|
+
def close(self) -> bool:
|
95
|
+
if not hasattr(self, '_f'):
|
96
|
+
return False
|
97
|
+
|
98
|
+
self._f.close()
|
99
|
+
del self._f
|
100
|
+
return True
|
101
|
+
|
102
|
+
def try_lock(self) -> bool:
|
103
|
+
try:
|
104
|
+
fcntl.flock(self._f, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
105
|
+
return True
|
106
|
+
|
107
|
+
except OSError:
|
108
|
+
return False
|
109
|
+
|
110
|
+
def ensure_locked(self) -> None:
|
111
|
+
if not self.try_lock():
|
112
|
+
raise RuntimeError('Could not get lock')
|
113
|
+
|
114
|
+
#
|
115
|
+
|
116
|
+
def write(self, pid: ta.Optional[int] = None) -> None:
|
117
|
+
self.ensure_locked()
|
118
|
+
|
119
|
+
if pid is None:
|
120
|
+
pid = os.getpid()
|
121
|
+
|
122
|
+
self._f.seek(0)
|
123
|
+
self._f.truncate()
|
124
|
+
self._f.write(f'{pid}\n')
|
125
|
+
self._f.flush()
|
126
|
+
|
127
|
+
def clear(self) -> None:
|
128
|
+
self.ensure_locked()
|
129
|
+
|
130
|
+
self._f.seek(0)
|
131
|
+
self._f.truncate()
|
132
|
+
|
133
|
+
def read(self) -> int:
|
134
|
+
if self.try_lock():
|
135
|
+
raise RuntimeError('Got lock')
|
136
|
+
|
137
|
+
self._f.seek(0)
|
138
|
+
return int(self._f.read()) # FIXME: could be empty or hold old value, race w proc start
|
139
|
+
|
140
|
+
def kill(self, sig: int = signal.SIGTERM) -> None:
|
141
|
+
pid = self.read()
|
142
|
+
os.kill(pid, sig) # FIXME: Still racy - pidfd_send_signal?
|
omlish/secrets/crypto.py
CHANGED
@@ -76,7 +76,6 @@ class Crypto(abc.ABC):
|
|
76
76
|
|
77
77
|
|
78
78
|
class FernetCrypto(Crypto):
|
79
|
-
|
80
79
|
def generate_key(self) -> bytes:
|
81
80
|
return cry_fernet.Fernet.generate_key()
|
82
81
|
|
@@ -98,7 +97,7 @@ class FernetCrypto(Crypto):
|
|
98
97
|
raise DecryptionError from e
|
99
98
|
|
100
99
|
|
101
|
-
class
|
100
|
+
class AesgcmCrypto(Crypto):
|
102
101
|
"""https://stackoverflow.com/a/59835994"""
|
103
102
|
|
104
103
|
def generate_key(self) -> bytes:
|
omlish/secrets/openssl.py
CHANGED
@@ -33,7 +33,7 @@ else:
|
|
33
33
|
DEFAULT_KEY_SIZE = 64
|
34
34
|
|
35
35
|
|
36
|
-
def generate_key(
|
36
|
+
def generate_key(sz: int = DEFAULT_KEY_SIZE) -> bytes:
|
37
37
|
# !! https://docs.openssl.org/3.0/man7/passphrase-encoding/
|
38
38
|
# Must not contain null bytes!
|
39
39
|
return secrets.token_hex(sz).encode('ascii')
|
omlish/secrets/tempssl.py
CHANGED
@@ -6,9 +6,9 @@ import tempfile
|
|
6
6
|
import typing as ta
|
7
7
|
|
8
8
|
from ..lite.cached import cached_nullary
|
9
|
-
from ..subprocesses import SubprocessRun
|
10
|
-
from ..subprocesses import
|
11
|
-
from ..subprocesses import
|
9
|
+
from ..subprocesses.run import SubprocessRun
|
10
|
+
from ..subprocesses.run import SubprocessRunnable
|
11
|
+
from ..subprocesses.run import SubprocessRunOutput
|
12
12
|
from .ssl import SslCert
|
13
13
|
|
14
14
|
|
@@ -18,7 +18,7 @@ class TempSslCert(ta.NamedTuple):
|
|
18
18
|
|
19
19
|
|
20
20
|
@dc.dataclass(frozen=True)
|
21
|
-
class TempSslCertGenerator:
|
21
|
+
class TempSslCertGenerator(SubprocessRunnable[TempSslCert]):
|
22
22
|
@cached_nullary
|
23
23
|
def temp_dir(self) -> str:
|
24
24
|
return tempfile.mkdtemp()
|
@@ -64,9 +64,6 @@ class TempSslCertGenerator:
|
|
64
64
|
temp_dir=self.temp_dir(),
|
65
65
|
)
|
66
66
|
|
67
|
-
def run(self) -> TempSslCert:
|
68
|
-
return self.handle_run_output(subprocesses.run_(self.make_run()))
|
69
|
-
|
70
67
|
|
71
68
|
def generate_temp_localhost_ssl_cert() -> TempSslCert:
|
72
69
|
return TempSslCertGenerator().run()
|
omlish/sockets/handlers.py
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
import abc
|
4
4
|
import concurrent.futures as cf
|
5
5
|
import dataclasses as dc
|
6
|
+
import logging
|
6
7
|
import socket
|
7
8
|
import typing as ta
|
8
9
|
|
@@ -132,3 +133,24 @@ class ExecutorSocketServerHandler(SocketServerHandler_):
|
|
132
133
|
|
133
134
|
def __call__(self, conn: SocketAndAddress) -> None:
|
134
135
|
self.executor.submit(self.handler, conn)
|
136
|
+
|
137
|
+
|
138
|
+
#
|
139
|
+
|
140
|
+
|
141
|
+
@dc.dataclass(frozen=True)
|
142
|
+
class ExceptionLoggingSocketServerHandler(SocketServerHandler_):
|
143
|
+
handler: SocketServerHandler
|
144
|
+
log: logging.Logger
|
145
|
+
|
146
|
+
ignored: ta.Optional[ta.Container[ta.Type[Exception]]] = None
|
147
|
+
|
148
|
+
def __call__(self, conn: SocketAndAddress) -> None:
|
149
|
+
try:
|
150
|
+
return self.handler(conn)
|
151
|
+
|
152
|
+
except Exception as e: # noqa
|
153
|
+
if (ignored := self.ignored) is None or type(e) not in ignored:
|
154
|
+
self.log.exception('Error in handler %r for conn %r', self.handler, conn)
|
155
|
+
|
156
|
+
raise
|
File without changes
|