omlish 0.0.0.dev229__py3-none-any.whl → 0.0.0.dev231__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- omlish/__about__.py +2 -2
- omlish/collections/__init__.py +15 -15
- omlish/collections/frozen.py +0 -2
- omlish/collections/identity.py +0 -3
- omlish/collections/indexed.py +0 -2
- omlish/collections/mappings.py +0 -3
- omlish/collections/ordered.py +0 -2
- omlish/collections/persistent/__init__.py +0 -0
- omlish/collections/sorted/__init__.py +0 -0
- omlish/collections/{skiplist.py → sorted/skiplist.py} +0 -1
- omlish/collections/{sorted.py → sorted/sorted.py} +2 -5
- omlish/collections/unmodifiable.py +0 -2
- omlish/daemons/__init__.py +0 -0
- omlish/daemons/daemon.py +200 -0
- omlish/daemons/reparent.py +16 -0
- omlish/daemons/spawning.py +166 -0
- omlish/daemons/targets.py +89 -0
- omlish/daemons/waiting.py +95 -0
- omlish/dispatch/dispatch.py +4 -1
- omlish/dispatch/methods.py +4 -0
- omlish/formats/json/__init__.py +5 -0
- omlish/io/compress/brotli.py +3 -3
- omlish/io/fdio/__init__.py +3 -0
- omlish/lang/__init__.py +0 -2
- omlish/lang/contextmanagers.py +15 -10
- omlish/libc.py +2 -4
- omlish/lite/timeouts.py +1 -1
- omlish/marshal/__init__.py +10 -10
- omlish/marshal/base.py +1 -1
- omlish/marshal/standard.py +2 -2
- omlish/marshal/trivial/__init__.py +0 -0
- omlish/marshal/{forbidden.py → trivial/forbidden.py} +7 -7
- omlish/marshal/{nop.py → trivial/nop.py} +5 -5
- omlish/os/deathpacts/__init__.py +15 -0
- omlish/os/deathpacts/base.py +76 -0
- omlish/os/deathpacts/heartbeatfile.py +85 -0
- omlish/os/{death.py → deathpacts/pipe.py} +20 -90
- omlish/os/forkhooks.py +55 -31
- omlish/os/pidfiles/manager.py +11 -44
- omlish/os/pidfiles/pidfile.py +18 -1
- omlish/reflect/__init__.py +1 -0
- omlish/reflect/inspect.py +43 -0
- omlish/sql/queries/__init__.py +4 -4
- omlish/sql/queries/rendering2.py +248 -0
- omlish/text/parts.py +26 -23
- {omlish-0.0.0.dev229.dist-info → omlish-0.0.0.dev231.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev229.dist-info → omlish-0.0.0.dev231.dist-info}/RECORD +57 -45
- omlish/formats/json/delimted.py +0 -4
- /omlish/collections/{persistent.py → persistent/persistent.py} +0 -0
- /omlish/collections/{treap.py → persistent/treap.py} +0 -0
- /omlish/collections/{treapmap.py → persistent/treapmap.py} +0 -0
- /omlish/marshal/{utils.py → proxy.py} +0 -0
- /omlish/marshal/{singular → trivial}/any.py +0 -0
- /omlish/sql/queries/{building.py → std.py} +0 -0
- {omlish-0.0.0.dev229.dist-info → omlish-0.0.0.dev231.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev229.dist-info → omlish-0.0.0.dev231.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev229.dist-info → omlish-0.0.0.dev231.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev229.dist-info → omlish-0.0.0.dev231.dist-info}/top_level.txt +0 -0
@@ -1,84 +1,11 @@
|
|
1
|
-
import abc
|
2
1
|
import os
|
3
|
-
import signal
|
4
|
-
import sys
|
5
|
-
import time
|
6
2
|
import typing as ta
|
7
3
|
import weakref
|
8
4
|
|
9
|
-
from
|
10
|
-
from
|
11
|
-
from
|
12
|
-
|
13
|
-
|
14
|
-
##
|
15
|
-
|
16
|
-
|
17
|
-
class Deathpact(abc.ABC):
|
18
|
-
@abc.abstractmethod
|
19
|
-
def poll(self) -> None:
|
20
|
-
raise NotImplementedError
|
21
|
-
|
22
|
-
|
23
|
-
class NopDeathpact(Deathpact):
|
24
|
-
def poll(self) -> None:
|
25
|
-
pass
|
26
|
-
|
27
|
-
|
28
|
-
##
|
29
|
-
|
30
|
-
|
31
|
-
class BaseDeathpact(Deathpact, abc.ABC):
|
32
|
-
def __init__(
|
33
|
-
self,
|
34
|
-
*,
|
35
|
-
interval_s: float = .5,
|
36
|
-
signal: int | None = signal.SIGTERM,
|
37
|
-
output: ta.Literal['stdout', 'stderr'] | None = 'stderr',
|
38
|
-
on_die: ta.Callable[[], None] | None = None,
|
39
|
-
) -> None:
|
40
|
-
super().__init__()
|
41
|
-
|
42
|
-
self._interval_s = interval_s
|
43
|
-
self._signal = signal
|
44
|
-
self._output = output
|
45
|
-
self._on_die = on_die
|
46
|
-
|
47
|
-
self._last_check_t: float | None = None
|
48
|
-
|
49
|
-
def _print(self, msg: str) -> None:
|
50
|
-
match self._output:
|
51
|
-
case 'stdout':
|
52
|
-
f = sys.stdout
|
53
|
-
case 'stderr':
|
54
|
-
f = sys.stderr
|
55
|
-
case _:
|
56
|
-
return
|
57
|
-
print(f'{self} pid={os.getpid()}: {msg}', file=f)
|
58
|
-
|
59
|
-
def die(self) -> None:
|
60
|
-
self._print('Triggered! Process terminating!')
|
61
|
-
|
62
|
-
if self._on_die is not None:
|
63
|
-
self._on_die()
|
64
|
-
|
65
|
-
if self._signal is not None:
|
66
|
-
os.kill(os.getpid(), self._signal)
|
67
|
-
|
68
|
-
sys.exit(1)
|
69
|
-
|
70
|
-
@abc.abstractmethod
|
71
|
-
def should_die(self) -> bool:
|
72
|
-
raise NotImplementedError
|
73
|
-
|
74
|
-
def maybe_die(self) -> None:
|
75
|
-
if self.should_die():
|
76
|
-
self.die()
|
77
|
-
|
78
|
-
def poll(self) -> None:
|
79
|
-
if self._last_check_t is None or (time.monotonic() - self._last_check_t) >= self._interval_s:
|
80
|
-
self.maybe_die()
|
81
|
-
self._last_check_t = time.monotonic()
|
5
|
+
from ... import check
|
6
|
+
from ..forkhooks import ForkHook
|
7
|
+
from ..forkhooks import ProcessOriginTracker
|
8
|
+
from .base import BaseDeathpact
|
82
9
|
|
83
10
|
|
84
11
|
##
|
@@ -92,16 +19,13 @@ class PipeDeathpact(BaseDeathpact):
|
|
92
19
|
handle such cases.
|
93
20
|
"""
|
94
21
|
|
95
|
-
_COOKIE: ta.ClassVar[bytes] = os.urandom(16)
|
96
|
-
|
97
22
|
def __init__(self, **kwargs: ta.Any) -> None:
|
98
23
|
super().__init__(**kwargs)
|
99
24
|
|
100
25
|
self._rfd: int | None = None
|
101
26
|
self._wfd: int | None = None
|
102
27
|
|
103
|
-
self.
|
104
|
-
self._fork_depth: int | None = get_fork_depth()
|
28
|
+
self._process_origin = ProcessOriginTracker()
|
105
29
|
|
106
30
|
def __repr__(self) -> str:
|
107
31
|
return f'{self.__class__.__name__}(rfd={self._rfd}, wfd={self._wfd})'
|
@@ -111,11 +35,13 @@ class PipeDeathpact(BaseDeathpact):
|
|
111
35
|
return check.not_none(self._rfd)
|
112
36
|
|
113
37
|
def is_parent(self) -> bool:
|
114
|
-
return
|
38
|
+
return self._process_origin.is_in_origin_process()
|
115
39
|
|
116
40
|
#
|
117
41
|
|
118
42
|
def __enter__(self) -> ta.Self:
|
43
|
+
check.state(self.is_parent())
|
44
|
+
|
119
45
|
check.none(self._rfd)
|
120
46
|
check.none(self._wfd)
|
121
47
|
|
@@ -125,18 +51,24 @@ class PipeDeathpact(BaseDeathpact):
|
|
125
51
|
|
126
52
|
return self
|
127
53
|
|
128
|
-
def
|
54
|
+
def close(self) -> None:
|
55
|
+
if self._rfd is not None:
|
56
|
+
os.close(self._rfd)
|
57
|
+
self._rfd = None
|
58
|
+
|
129
59
|
if self._wfd is not None:
|
130
|
-
if
|
60
|
+
if self.is_parent():
|
131
61
|
os.close(check.not_none(self._wfd))
|
132
62
|
self._wfd = None
|
133
63
|
|
134
64
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
135
|
-
|
136
|
-
os.close(self._rfd)
|
137
|
-
self._rfd = None
|
65
|
+
self.close()
|
138
66
|
|
139
|
-
|
67
|
+
def _close_wfd_if_not_parent(self) -> None:
|
68
|
+
if self._wfd is not None:
|
69
|
+
if not self.is_parent():
|
70
|
+
os.close(check.not_none(self._wfd))
|
71
|
+
self._wfd = None
|
140
72
|
|
141
73
|
#
|
142
74
|
|
@@ -145,8 +77,6 @@ class PipeDeathpact(BaseDeathpact):
|
|
145
77
|
**self.__dict__,
|
146
78
|
**dict(
|
147
79
|
_wfd=None,
|
148
|
-
_cookie=None,
|
149
|
-
_fork_depth=None,
|
150
80
|
),
|
151
81
|
}
|
152
82
|
|
omlish/os/forkhooks.py
CHANGED
@@ -1,9 +1,5 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
# @omlish-lite
|
3
|
-
"""
|
4
|
-
TODO:
|
5
|
-
- ForkHook base class? all classmethods? prevents pickling
|
6
|
-
"""
|
7
3
|
import abc
|
8
4
|
import os
|
9
5
|
import threading
|
@@ -29,31 +25,6 @@ class _ForkHookManager:
|
|
29
25
|
|
30
26
|
#
|
31
27
|
|
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
28
|
class Hook(ta.NamedTuple):
|
58
29
|
key: ta.Any
|
59
30
|
priority: int
|
@@ -74,8 +45,8 @@ class _ForkHookManager:
|
|
74
45
|
def _rebuild_hook_collections(cls) -> None:
|
75
46
|
cls._hook_keys = frozenset(cls._hooks_by_key)
|
76
47
|
|
77
|
-
# Uses
|
78
|
-
# depended upon for usecase correctness.
|
48
|
+
# Uses dict order preservation to retain insertion-order of hooks of equal priority (although that shouldn't be
|
49
|
+
# depended upon for usecase correctness).
|
79
50
|
cls._priority_ordered_hooks = sorted(cls._hooks_by_key.values(), key=lambda h: h.priority)
|
80
51
|
|
81
52
|
#
|
@@ -121,6 +92,31 @@ class _ForkHookManager:
|
|
121
92
|
|
122
93
|
#
|
123
94
|
|
95
|
+
_installed: ta.ClassVar[bool] = False
|
96
|
+
|
97
|
+
@classmethod
|
98
|
+
def _install(cls) -> bool:
|
99
|
+
if cls._installed:
|
100
|
+
return False
|
101
|
+
|
102
|
+
check.state(not cls._installed)
|
103
|
+
|
104
|
+
os.register_at_fork(
|
105
|
+
before=cls._before_fork,
|
106
|
+
after_in_parent=cls._after_fork_in_parent,
|
107
|
+
after_in_child=cls._after_fork_in_child,
|
108
|
+
)
|
109
|
+
|
110
|
+
cls._installed = True
|
111
|
+
return True
|
112
|
+
|
113
|
+
@classmethod
|
114
|
+
def install(cls) -> bool:
|
115
|
+
with cls._lock:
|
116
|
+
return cls._install()
|
117
|
+
|
118
|
+
#
|
119
|
+
|
124
120
|
@classmethod
|
125
121
|
def _before_fork(cls) -> None:
|
126
122
|
cls._lock.acquire()
|
@@ -213,3 +209,31 @@ class _ForkDepthTracker(ForkHook):
|
|
213
209
|
|
214
210
|
def get_fork_depth() -> int:
|
215
211
|
return _ForkDepthTracker.get_fork_depth()
|
212
|
+
|
213
|
+
|
214
|
+
##
|
215
|
+
|
216
|
+
|
217
|
+
class ProcessOriginTracker:
|
218
|
+
_PROCESS_COOKIE: ta.ClassVar[bytes] = os.urandom(16)
|
219
|
+
|
220
|
+
def __init__(self, **kwargs: ta.Any) -> None:
|
221
|
+
super().__init__(**kwargs)
|
222
|
+
|
223
|
+
self._process_cookie: bytes | None = self._PROCESS_COOKIE
|
224
|
+
self._fork_depth: int | None = get_fork_depth()
|
225
|
+
|
226
|
+
def is_in_origin_process(self) -> bool:
|
227
|
+
return (self._PROCESS_COOKIE, get_fork_depth()) == (self._process_cookie, self._fork_depth)
|
228
|
+
|
229
|
+
def __getstate__(self):
|
230
|
+
return {
|
231
|
+
**self.__dict__,
|
232
|
+
**dict(
|
233
|
+
_cookie=None,
|
234
|
+
_fork_depth=None,
|
235
|
+
),
|
236
|
+
}
|
237
|
+
|
238
|
+
def __setstate__(self, state):
|
239
|
+
self.__dict__.update(state)
|
omlish/os/pidfiles/manager.py
CHANGED
@@ -7,71 +7,38 @@ import typing as ta
|
|
7
7
|
import weakref
|
8
8
|
|
9
9
|
from ...lite.check import check
|
10
|
+
from ..forkhooks import ForkHook
|
10
11
|
from .pidfile import Pidfile
|
11
12
|
|
12
13
|
|
13
14
|
##
|
14
15
|
|
15
16
|
|
16
|
-
class _PidfileManager:
|
17
|
+
class _PidfileManager(ForkHook):
|
17
18
|
"""
|
18
19
|
Manager for controlled inheritance of Pidfiles across forks in the presence of multiple threads. There is of course
|
19
20
|
no safe or correct way to mix the use of fork and multiple active threads, and one should never write code which
|
20
21
|
does so, but in the Real World one may still find oneself in such a situation outside of their control (such as when
|
21
22
|
running under Pycharm's debugger which forces the use of forked multiprocessing).
|
22
|
-
|
23
|
-
Not implemented as an instantiated class as there is no way to unregister at_fork listeners, and because Pidfiles
|
24
|
-
may be pickled and there must be no possibility of accidentally unpickling and instantiating a new instance of the
|
25
|
-
manager.
|
26
23
|
"""
|
27
24
|
|
28
|
-
def __new__(cls, *args, **kwargs): # noqa
|
29
|
-
raise TypeError
|
30
|
-
|
31
25
|
_lock: ta.ClassVar[threading.Lock] = threading.Lock()
|
32
|
-
_installed: ta.ClassVar[bool] = False
|
33
26
|
_pidfile_threads: ta.ClassVar[ta.MutableMapping[Pidfile, threading.Thread]] = weakref.WeakKeyDictionary()
|
27
|
+
_num_kills: ta.ClassVar[int] = 0
|
34
28
|
|
35
29
|
@classmethod
|
36
|
-
def
|
37
|
-
cls.
|
38
|
-
|
39
|
-
@classmethod
|
40
|
-
def _after_fork_in_parent(cls) -> None:
|
41
|
-
cls._lock.release()
|
30
|
+
def num_kills(cls) -> int:
|
31
|
+
return cls._num_kills
|
42
32
|
|
43
33
|
@classmethod
|
44
34
|
def _after_fork_in_child(cls) -> None:
|
45
|
-
th = threading.current_thread()
|
46
|
-
for pf, pf_th in list(cls._pidfile_threads.items()):
|
47
|
-
if pf_th is not th:
|
48
|
-
pf.close()
|
49
|
-
del cls._pidfile_threads[pf]
|
50
|
-
|
51
|
-
cls._lock.release()
|
52
|
-
|
53
|
-
#
|
54
|
-
|
55
|
-
@classmethod
|
56
|
-
def _install(cls) -> None:
|
57
|
-
check.state(not cls._installed)
|
58
|
-
|
59
|
-
os.register_at_fork(
|
60
|
-
before=cls._before_fork,
|
61
|
-
after_in_parent=cls._after_fork_in_parent,
|
62
|
-
after_in_child=cls._after_fork_in_child,
|
63
|
-
)
|
64
|
-
|
65
|
-
cls._installed = True
|
66
|
-
|
67
|
-
@classmethod
|
68
|
-
def install(cls) -> bool:
|
69
35
|
with cls._lock:
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
36
|
+
th = threading.current_thread()
|
37
|
+
for pf, pf_th in list(cls._pidfile_threads.items()):
|
38
|
+
if pf_th is not th:
|
39
|
+
pf.close()
|
40
|
+
del cls._pidfile_threads[pf]
|
41
|
+
cls._num_kills += 1
|
75
42
|
|
76
43
|
@classmethod
|
77
44
|
@contextlib.contextmanager
|
omlish/os/pidfiles/pidfile.py
CHANGED
@@ -56,8 +56,25 @@ class Pidfile:
|
|
56
56
|
|
57
57
|
#
|
58
58
|
|
59
|
+
_fd_to_dup: int
|
60
|
+
|
61
|
+
def dup(self) -> 'Pidfile':
|
62
|
+
fd = self._f.fileno()
|
63
|
+
dup = Pidfile(
|
64
|
+
self._path,
|
65
|
+
inheritable=self._inheritable,
|
66
|
+
)
|
67
|
+
dup._fd_to_dup = fd # noqa
|
68
|
+
return dup
|
69
|
+
|
70
|
+
#
|
71
|
+
|
59
72
|
def __enter__(self) -> 'Pidfile':
|
60
|
-
|
73
|
+
if hasattr(self, '_fd_to_dup'):
|
74
|
+
fd = os.dup(self._fd_to_dup)
|
75
|
+
del self._fd_to_dup
|
76
|
+
else:
|
77
|
+
fd = os.open(self._path, os.O_RDWR | os.O_CREAT, 0o600)
|
61
78
|
|
62
79
|
try:
|
63
80
|
if self._inheritable:
|
omlish/reflect/__init__.py
CHANGED
omlish/reflect/inspect.py
CHANGED
@@ -4,6 +4,7 @@ TODO:
|
|
4
4
|
"""
|
5
5
|
import typing as ta
|
6
6
|
|
7
|
+
from .. import check
|
7
8
|
from .. import lang
|
8
9
|
|
9
10
|
|
@@ -16,8 +17,50 @@ else:
|
|
16
17
|
annotationlib = lang.lazy_import('annotationlib', optional=True, cache_failure=True)
|
17
18
|
|
18
19
|
|
20
|
+
##
|
21
|
+
|
22
|
+
|
19
23
|
def get_annotations(obj: ta.Any) -> ta.Mapping[str, ta.Any]:
|
20
24
|
if (al := annotationlib()) is not None:
|
21
25
|
return al.get_annotations(obj, format=al.Format.FORWARDREF) # noqa
|
22
26
|
else:
|
23
27
|
return inspect.get_annotations(obj)
|
28
|
+
|
29
|
+
|
30
|
+
##
|
31
|
+
|
32
|
+
|
33
|
+
class _TypeHintsTarget:
|
34
|
+
def __init__(self, obj, annotations):
|
35
|
+
super().__init__()
|
36
|
+
|
37
|
+
self.obj = obj
|
38
|
+
self.__annotations__ = annotations
|
39
|
+
self.__globals__ = obj.__globals__
|
40
|
+
self.__type_params__ = obj.__type_params__
|
41
|
+
|
42
|
+
|
43
|
+
def get_filtered_type_hints(
|
44
|
+
obj: ta.Any,
|
45
|
+
*,
|
46
|
+
include: ta.Container[str] | None = None,
|
47
|
+
exclude: ta.Container[str] | None = None,
|
48
|
+
) -> ta.Mapping[str, ta.Any]:
|
49
|
+
"""
|
50
|
+
Gross hack: get_type_hints doesn't support recursive types, but it's nice to support functions with, at least,
|
51
|
+
recursive return types.
|
52
|
+
|
53
|
+
This is consciously constrained in what it supports - basically just functions.
|
54
|
+
"""
|
55
|
+
|
56
|
+
check.not_isinstance(include, str)
|
57
|
+
check.not_isinstance(exclude, str)
|
58
|
+
|
59
|
+
anns = {
|
60
|
+
k: v
|
61
|
+
for k, v in obj.__annotations__.items()
|
62
|
+
if (include is None or k in include)
|
63
|
+
and not (exclude is not None and k in exclude)
|
64
|
+
}
|
65
|
+
|
66
|
+
return ta.get_type_hints(_TypeHintsTarget(obj, anns))
|
omlish/sql/queries/__init__.py
CHANGED
@@ -11,10 +11,6 @@ from .binary import ( # noqa
|
|
11
11
|
BinaryOps,
|
12
12
|
)
|
13
13
|
|
14
|
-
from .building import ( # noqa
|
15
|
-
StdBuilder,
|
16
|
-
)
|
17
|
-
|
18
14
|
from .exprs import ( # noqa
|
19
15
|
CanExpr,
|
20
16
|
CanLiteral,
|
@@ -76,6 +72,10 @@ from .selects import ( # noqa
|
|
76
72
|
SelectItem,
|
77
73
|
)
|
78
74
|
|
75
|
+
from .std import ( # noqa
|
76
|
+
StdBuilder,
|
77
|
+
)
|
78
|
+
|
79
79
|
from .stmts import ( # noqa
|
80
80
|
CanExpr,
|
81
81
|
ExprStmt,
|