omlish 0.0.0.dev230__py3-none-any.whl → 0.0.0.dev231__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/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/lang/__init__.py +0 -2
- omlish/lang/contextmanagers.py +15 -10
- omlish/libc.py +2 -4
- omlish/lite/timeouts.py +1 -1
- omlish/os/pidfiles/pidfile.py +18 -1
- {omlish-0.0.0.dev230.dist-info → omlish-0.0.0.dev231.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev230.dist-info → omlish-0.0.0.dev231.dist-info}/RECORD +18 -12
- {omlish-0.0.0.dev230.dist-info → omlish-0.0.0.dev231.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev230.dist-info → omlish-0.0.0.dev231.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev230.dist-info → omlish-0.0.0.dev231.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev230.dist-info → omlish-0.0.0.dev231.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
File without changes
|
omlish/daemons/daemon.py
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
"""
|
2
|
+
TODO:
|
3
|
+
- OK.. this is useful for non-system-daemons too... which have no pidfile..
|
4
|
+
- split out pidfile concern to.. identity?
|
5
|
+
- async[io] support, really just waiting
|
6
|
+
- helpers for http, json, etc
|
7
|
+
- heartbeat? status checks? heartbeat file?
|
8
|
+
- zmq
|
9
|
+
- cli, cfg reload
|
10
|
+
- bootstrap
|
11
|
+
- rsrc limit
|
12
|
+
- logs
|
13
|
+
- https://github.com/Homebrew/homebrew-services
|
14
|
+
- deathpacts
|
15
|
+
- timebomb
|
16
|
+
- pickle protocol, revision / venv check, multiprocessing manager support
|
17
|
+
"""
|
18
|
+
import contextlib
|
19
|
+
import functools
|
20
|
+
import logging
|
21
|
+
import os.path
|
22
|
+
import threading
|
23
|
+
import time
|
24
|
+
import typing as ta
|
25
|
+
|
26
|
+
from .. import check
|
27
|
+
from .. import dataclasses as dc
|
28
|
+
from .. import lang
|
29
|
+
from ..os.pidfiles.manager import _PidfileManager # noqa
|
30
|
+
from ..os.pidfiles.manager import open_inheritable_pidfile
|
31
|
+
from ..os.pidfiles.pidfile import Pidfile
|
32
|
+
from .reparent import reparent_process
|
33
|
+
from .spawning import InProcessSpawner
|
34
|
+
from .spawning import Spawn
|
35
|
+
from .spawning import Spawner
|
36
|
+
from .spawning import Spawning
|
37
|
+
from .spawning import spawner_for
|
38
|
+
from .targets import Target
|
39
|
+
from .targets import target_runner_for
|
40
|
+
from .waiting import Wait
|
41
|
+
from .waiting import waiter_for
|
42
|
+
|
43
|
+
|
44
|
+
log = logging.getLogger(__name__)
|
45
|
+
|
46
|
+
|
47
|
+
##
|
48
|
+
|
49
|
+
|
50
|
+
class Daemon:
|
51
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
52
|
+
class Config:
|
53
|
+
target: Target
|
54
|
+
spawning: Spawning
|
55
|
+
|
56
|
+
#
|
57
|
+
|
58
|
+
reparent_process: bool = False
|
59
|
+
|
60
|
+
pid_file: str | None = None
|
61
|
+
|
62
|
+
#
|
63
|
+
|
64
|
+
wait: Wait | None = None
|
65
|
+
|
66
|
+
wait_timeout: lang.TimeoutLike = 10.
|
67
|
+
wait_sleep_s: float = .1
|
68
|
+
|
69
|
+
launched_timeout_s: float = 5.
|
70
|
+
|
71
|
+
#
|
72
|
+
|
73
|
+
def __post_init__(self) -> None:
|
74
|
+
check.isinstance(self.pid_file, (str, None))
|
75
|
+
|
76
|
+
def __init__(self, config: Config) -> None:
|
77
|
+
super().__init__()
|
78
|
+
|
79
|
+
self._config = config
|
80
|
+
|
81
|
+
@property
|
82
|
+
def config(self) -> Config:
|
83
|
+
return self._config
|
84
|
+
|
85
|
+
#
|
86
|
+
|
87
|
+
@property
|
88
|
+
def has_pidfile(self) -> bool:
|
89
|
+
return self._config.pid_file is not None
|
90
|
+
|
91
|
+
def _non_inheritable_pidfile(self) -> Pidfile:
|
92
|
+
check.state(self.has_pidfile)
|
93
|
+
return Pidfile(
|
94
|
+
check.non_empty_str(self._config.pid_file),
|
95
|
+
inheritable=False,
|
96
|
+
)
|
97
|
+
|
98
|
+
def is_running(self) -> bool:
|
99
|
+
check.state(self.has_pidfile)
|
100
|
+
|
101
|
+
if not os.path.isfile(check.non_empty_str(self._config.pid_file)):
|
102
|
+
return False
|
103
|
+
|
104
|
+
with self._non_inheritable_pidfile() as pf:
|
105
|
+
return not pf.try_acquire_lock()
|
106
|
+
|
107
|
+
#
|
108
|
+
|
109
|
+
def _inner_launch(
|
110
|
+
self,
|
111
|
+
*,
|
112
|
+
pidfile_manager: ta.ContextManager | None,
|
113
|
+
launched_callback: ta.Callable[[], None] | None = None,
|
114
|
+
) -> None:
|
115
|
+
try:
|
116
|
+
if self._config.reparent_process:
|
117
|
+
log.info('Reparenting')
|
118
|
+
reparent_process()
|
119
|
+
|
120
|
+
with contextlib.ExitStack() as es:
|
121
|
+
pidfile: Pidfile | None = None # noqa
|
122
|
+
if pidfile_manager is not None:
|
123
|
+
pidfile = check.isinstance(es.enter_context(pidfile_manager), Pidfile)
|
124
|
+
pidfile.write()
|
125
|
+
|
126
|
+
if launched_callback is not None:
|
127
|
+
launched_callback()
|
128
|
+
|
129
|
+
runner = target_runner_for(self._config.target)
|
130
|
+
runner.run()
|
131
|
+
|
132
|
+
finally:
|
133
|
+
if launched_callback is not None:
|
134
|
+
launched_callback()
|
135
|
+
|
136
|
+
def launch_no_wait(self) -> bool:
|
137
|
+
with contextlib.ExitStack() as es:
|
138
|
+
spawner: Spawner = es.enter_context(spawner_for(self._config.spawning))
|
139
|
+
|
140
|
+
#
|
141
|
+
|
142
|
+
inherit_fds: set[int] = set()
|
143
|
+
launched_event: threading.Event | None = None
|
144
|
+
|
145
|
+
pidfile: Pidfile | None = None # noqa
|
146
|
+
pidfile_manager: ta.ContextManager | None = None
|
147
|
+
|
148
|
+
if (pid_file := self._config.pid_file) is not None:
|
149
|
+
if not isinstance(spawner, InProcessSpawner):
|
150
|
+
pidfile = es.enter_context(open_inheritable_pidfile(pid_file))
|
151
|
+
pidfile_manager = lang.NopContextManager(pidfile)
|
152
|
+
|
153
|
+
else:
|
154
|
+
check.state(not self._config.reparent_process)
|
155
|
+
pidfile = es.enter_context(Pidfile(pid_file))
|
156
|
+
pidfile_manager = pidfile.dup()
|
157
|
+
launched_event = threading.Event()
|
158
|
+
|
159
|
+
if not pidfile.try_acquire_lock():
|
160
|
+
return False
|
161
|
+
|
162
|
+
inherit_fds.add(check.isinstance(pidfile.fileno(), int))
|
163
|
+
|
164
|
+
#
|
165
|
+
|
166
|
+
spawner.spawn(Spawn(
|
167
|
+
functools.partial(
|
168
|
+
self._inner_launch,
|
169
|
+
pidfile_manager=pidfile_manager,
|
170
|
+
launched_callback=launched_event.set if launched_event is not None else None,
|
171
|
+
),
|
172
|
+
inherit_fds=inherit_fds,
|
173
|
+
))
|
174
|
+
|
175
|
+
if launched_event is not None:
|
176
|
+
check.state(launched_event.wait(timeout=self._config.launched_timeout_s))
|
177
|
+
|
178
|
+
return True
|
179
|
+
|
180
|
+
#
|
181
|
+
|
182
|
+
def wait_sync(self, timeout: lang.TimeoutLike = lang.Timeout.Default) -> None:
|
183
|
+
if self._config.wait is None:
|
184
|
+
return
|
185
|
+
|
186
|
+
timeout = lang.Timeout.of(timeout, self._config.wait_timeout)
|
187
|
+
waiter = waiter_for(self._config.wait)
|
188
|
+
while not waiter.do_wait():
|
189
|
+
timeout()
|
190
|
+
time.sleep(self._config.wait_sleep_s or 0.)
|
191
|
+
|
192
|
+
#
|
193
|
+
|
194
|
+
class _NOT_SET(lang.Marker): # noqa
|
195
|
+
pass
|
196
|
+
|
197
|
+
def launch(self, timeout: lang.TimeoutLike = lang.Timeout.Default) -> None:
|
198
|
+
self.launch_no_wait()
|
199
|
+
|
200
|
+
self.wait_sync(timeout)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
|
4
|
+
|
5
|
+
def reparent_process() -> None:
|
6
|
+
if (pid := os.fork()): # noqa
|
7
|
+
sys.exit(0)
|
8
|
+
raise RuntimeError('Unreachable') # noqa
|
9
|
+
|
10
|
+
os.setsid()
|
11
|
+
|
12
|
+
if (pid := os.fork()): # noqa
|
13
|
+
sys.exit(0)
|
14
|
+
|
15
|
+
sys.stdout.flush()
|
16
|
+
sys.stderr.flush()
|
@@ -0,0 +1,166 @@
|
|
1
|
+
import abc
|
2
|
+
import functools
|
3
|
+
import os
|
4
|
+
import sys
|
5
|
+
import threading
|
6
|
+
import typing as ta
|
7
|
+
|
8
|
+
from .. import check
|
9
|
+
from .. import dataclasses as dc
|
10
|
+
from .. import lang
|
11
|
+
from ..diag import pydevd
|
12
|
+
|
13
|
+
|
14
|
+
if ta.TYPE_CHECKING:
|
15
|
+
import multiprocessing as mp
|
16
|
+
import multiprocessing.context
|
17
|
+
import multiprocessing.process # noqa
|
18
|
+
|
19
|
+
from ..multiprocessing import spawn as omp_spawn
|
20
|
+
|
21
|
+
else:
|
22
|
+
mp = lang.proxy_import('multiprocessing', extras=['context', 'process'])
|
23
|
+
subprocess = lang.proxy_import('subprocess')
|
24
|
+
|
25
|
+
omp_spawn = lang.proxy_import('..multiprocessing.spawn', __package__)
|
26
|
+
|
27
|
+
|
28
|
+
##
|
29
|
+
|
30
|
+
|
31
|
+
class Spawning(dc.Case):
|
32
|
+
pass
|
33
|
+
|
34
|
+
|
35
|
+
class Spawn(dc.Frozen, final=True):
|
36
|
+
fn: ta.Callable[[], None]
|
37
|
+
|
38
|
+
_: dc.KW_ONLY
|
39
|
+
|
40
|
+
inherit_fds: ta.Collection[int] | None = None
|
41
|
+
|
42
|
+
|
43
|
+
class Spawner(lang.ContextManaged, abc.ABC):
|
44
|
+
@abc.abstractmethod
|
45
|
+
def spawn(self, spawn: Spawn) -> None:
|
46
|
+
raise NotImplementedError
|
47
|
+
|
48
|
+
|
49
|
+
class InProcessSpawner(Spawner, abc.ABC):
|
50
|
+
pass
|
51
|
+
|
52
|
+
|
53
|
+
@functools.singledispatch
|
54
|
+
def spawner_for(spawning: Spawning) -> Spawner:
|
55
|
+
raise TypeError(spawning)
|
56
|
+
|
57
|
+
|
58
|
+
##
|
59
|
+
|
60
|
+
|
61
|
+
class MultiprocessingSpawning(Spawning, kw_only=True):
|
62
|
+
# Defaults to 'fork' if under pydevd, else 'spawn'
|
63
|
+
start_method: str | None = None
|
64
|
+
|
65
|
+
non_daemon: bool = False
|
66
|
+
|
67
|
+
|
68
|
+
class MultiprocessingSpawner(Spawner):
|
69
|
+
def __init__(self, spawning: MultiprocessingSpawning) -> None:
|
70
|
+
super().__init__()
|
71
|
+
|
72
|
+
self._spawning = spawning
|
73
|
+
self._process: ta.Optional['mp.process.BaseProcess'] = None # noqa
|
74
|
+
|
75
|
+
def _process_cls(self, spawn: Spawn) -> type['mp.process.BaseProcess']:
|
76
|
+
if (start_method := self._spawning.start_method) is None:
|
77
|
+
# Unfortunately, pydevd forces the use of the 'fork' start_method, which cannot be mixed with 'spawn':
|
78
|
+
# https://github.com/python/cpython/blob/a7427f2db937adb4c787754deb4c337f1894fe86/Lib/multiprocessing/spawn.py#L102 # noqa
|
79
|
+
if pydevd.is_running():
|
80
|
+
start_method = 'fork'
|
81
|
+
else:
|
82
|
+
start_method = 'spawn'
|
83
|
+
|
84
|
+
ctx: 'mp.context.BaseContext' # noqa
|
85
|
+
if start_method == 'fork':
|
86
|
+
ctx = mp.get_context(check.non_empty_str(start_method))
|
87
|
+
|
88
|
+
elif start_method == 'spawn':
|
89
|
+
ctx = omp_spawn.ExtrasSpawnContext(omp_spawn.SpawnExtras(
|
90
|
+
pass_fds=frozenset(spawn.inherit_fds) if spawn.inherit_fds is not None else None,
|
91
|
+
))
|
92
|
+
|
93
|
+
else:
|
94
|
+
raise ValueError(start_method)
|
95
|
+
|
96
|
+
return ctx.Process # type: ignore
|
97
|
+
|
98
|
+
def spawn(self, spawn: Spawn) -> None:
|
99
|
+
check.none(self._process)
|
100
|
+
self._process = self._process_cls(spawn)(
|
101
|
+
target=spawn.fn,
|
102
|
+
daemon=not self._spawning.non_daemon,
|
103
|
+
)
|
104
|
+
self._process.start()
|
105
|
+
|
106
|
+
|
107
|
+
@spawner_for.register
|
108
|
+
def _(spawning: MultiprocessingSpawning) -> MultiprocessingSpawner:
|
109
|
+
return MultiprocessingSpawner(spawning)
|
110
|
+
|
111
|
+
|
112
|
+
##
|
113
|
+
|
114
|
+
|
115
|
+
class ForkSpawning(Spawning):
|
116
|
+
pass
|
117
|
+
|
118
|
+
|
119
|
+
class ForkSpawner(Spawner, dc.Frozen):
|
120
|
+
spawning: ForkSpawning
|
121
|
+
|
122
|
+
def spawn(self, spawn: Spawn) -> None:
|
123
|
+
if (pid := os.fork()): # noqa
|
124
|
+
return
|
125
|
+
|
126
|
+
try:
|
127
|
+
spawn.fn()
|
128
|
+
except BaseException: # noqa
|
129
|
+
sys.exit(1)
|
130
|
+
else:
|
131
|
+
sys.exit(0)
|
132
|
+
|
133
|
+
raise RuntimeError('Unreachable') # noqa
|
134
|
+
|
135
|
+
|
136
|
+
@spawner_for.register
|
137
|
+
def _(spawning: ForkSpawning) -> ForkSpawner:
|
138
|
+
return ForkSpawner(spawning)
|
139
|
+
|
140
|
+
|
141
|
+
##
|
142
|
+
|
143
|
+
|
144
|
+
class ThreadSpawning(Spawning, kw_only=True):
|
145
|
+
non_daemon: bool = False
|
146
|
+
|
147
|
+
|
148
|
+
class ThreadSpawner(InProcessSpawner):
|
149
|
+
def __init__(self, spawning: ThreadSpawning) -> None:
|
150
|
+
super().__init__()
|
151
|
+
|
152
|
+
self._spawning = spawning
|
153
|
+
self._thread: threading.Thread | None = None
|
154
|
+
|
155
|
+
def spawn(self, spawn: Spawn) -> None:
|
156
|
+
check.none(self._thread)
|
157
|
+
self._thread = threading.Thread(
|
158
|
+
target=spawn.fn,
|
159
|
+
daemon=not self._spawning.non_daemon,
|
160
|
+
)
|
161
|
+
self._thread.start()
|
162
|
+
|
163
|
+
|
164
|
+
@spawner_for.register
|
165
|
+
def _(spawning: ThreadSpawning) -> ThreadSpawner:
|
166
|
+
return ThreadSpawner(spawning)
|
@@ -0,0 +1,89 @@
|
|
1
|
+
import abc
|
2
|
+
import functools
|
3
|
+
import os
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
from .. import check
|
7
|
+
from .. import dataclasses as dc
|
8
|
+
from .. import lang
|
9
|
+
|
10
|
+
|
11
|
+
if ta.TYPE_CHECKING:
|
12
|
+
import runpy
|
13
|
+
else:
|
14
|
+
runpy = lang.proxy_import('runpy')
|
15
|
+
|
16
|
+
|
17
|
+
##
|
18
|
+
|
19
|
+
|
20
|
+
class Target(dc.Case):
|
21
|
+
pass
|
22
|
+
|
23
|
+
|
24
|
+
class TargetRunner(abc.ABC):
|
25
|
+
@abc.abstractmethod
|
26
|
+
def run(self) -> None:
|
27
|
+
raise NotImplementedError
|
28
|
+
|
29
|
+
|
30
|
+
@functools.singledispatch
|
31
|
+
def target_runner_for(target: Target) -> TargetRunner:
|
32
|
+
raise TypeError(target)
|
33
|
+
|
34
|
+
|
35
|
+
##
|
36
|
+
|
37
|
+
|
38
|
+
class FnTarget(Target):
|
39
|
+
fn: ta.Callable[[], None]
|
40
|
+
|
41
|
+
|
42
|
+
class FnTargetRunner(TargetRunner, dc.Frozen):
|
43
|
+
target: FnTarget
|
44
|
+
|
45
|
+
def run(self) -> None:
|
46
|
+
self.target.fn()
|
47
|
+
|
48
|
+
|
49
|
+
@target_runner_for.register
|
50
|
+
def _(target: FnTarget) -> FnTargetRunner:
|
51
|
+
return FnTargetRunner(target)
|
52
|
+
|
53
|
+
|
54
|
+
##
|
55
|
+
|
56
|
+
|
57
|
+
class NameTarget(Target):
|
58
|
+
name: str
|
59
|
+
|
60
|
+
|
61
|
+
class NameTargetRunner(TargetRunner, dc.Frozen):
|
62
|
+
target: NameTarget
|
63
|
+
|
64
|
+
def run(self) -> None:
|
65
|
+
name = self.target.name
|
66
|
+
if lang.can_import(name):
|
67
|
+
runpy._run_module_as_main(name) # type: ignore # noqa
|
68
|
+
else:
|
69
|
+
obj = lang.import_attr(self.target.name)
|
70
|
+
obj()
|
71
|
+
|
72
|
+
|
73
|
+
##
|
74
|
+
|
75
|
+
|
76
|
+
class ExecTarget(Target):
|
77
|
+
cmd: ta.Sequence[str] = dc.xfield(coerce=check.of_not_isinstance(str))
|
78
|
+
|
79
|
+
|
80
|
+
class ExecTargetRunner(TargetRunner, dc.Frozen):
|
81
|
+
target: ExecTarget
|
82
|
+
|
83
|
+
def run(self) -> None:
|
84
|
+
os.execl(*self.target.cmd)
|
85
|
+
|
86
|
+
|
87
|
+
@target_runner_for.register
|
88
|
+
def _(target: ExecTarget) -> ExecTargetRunner:
|
89
|
+
return ExecTargetRunner(target)
|
@@ -0,0 +1,95 @@
|
|
1
|
+
import abc
|
2
|
+
import functools
|
3
|
+
import socket
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
from .. import dataclasses as dc
|
7
|
+
|
8
|
+
|
9
|
+
##
|
10
|
+
|
11
|
+
|
12
|
+
class Wait(dc.Case):
|
13
|
+
pass
|
14
|
+
|
15
|
+
|
16
|
+
class Waiter(abc.ABC):
|
17
|
+
@abc.abstractmethod
|
18
|
+
def do_wait(self) -> bool:
|
19
|
+
raise NotImplementedError
|
20
|
+
|
21
|
+
|
22
|
+
@functools.singledispatch
|
23
|
+
def waiter_for(wait: Wait) -> Waiter:
|
24
|
+
raise TypeError(wait)
|
25
|
+
|
26
|
+
|
27
|
+
##
|
28
|
+
|
29
|
+
|
30
|
+
class SequentialWait(Wait):
|
31
|
+
waits: ta.Sequence[Wait]
|
32
|
+
|
33
|
+
|
34
|
+
class SequentialWaiter(Waiter):
|
35
|
+
def __init__(self, waiters: ta.Sequence[Waiter]) -> None:
|
36
|
+
super().__init__()
|
37
|
+
|
38
|
+
self._waiters = waiters
|
39
|
+
self._idx = 0
|
40
|
+
|
41
|
+
def do_wait(self) -> bool:
|
42
|
+
while self._idx < len(self._waiters):
|
43
|
+
if not self._waiters[self._idx].do_wait():
|
44
|
+
return False
|
45
|
+
self._idx += 1
|
46
|
+
return True
|
47
|
+
|
48
|
+
|
49
|
+
@waiter_for.register
|
50
|
+
def _(wait: SequentialWait) -> SequentialWaiter:
|
51
|
+
return SequentialWaiter([waiter_for(c) for c in wait.waits])
|
52
|
+
|
53
|
+
|
54
|
+
##
|
55
|
+
|
56
|
+
|
57
|
+
class FnWait(Wait):
|
58
|
+
fn: ta.Callable[[], bool]
|
59
|
+
|
60
|
+
|
61
|
+
class FnWaiter(Waiter, dc.Frozen):
|
62
|
+
wait: FnWait
|
63
|
+
|
64
|
+
def do_wait(self) -> bool:
|
65
|
+
return self.wait.fn()
|
66
|
+
|
67
|
+
|
68
|
+
@waiter_for.register
|
69
|
+
def _(wait: FnWait) -> FnWaiter:
|
70
|
+
return FnWaiter(wait)
|
71
|
+
|
72
|
+
|
73
|
+
##
|
74
|
+
|
75
|
+
|
76
|
+
class ConnectWait(Wait):
|
77
|
+
address: ta.Any
|
78
|
+
|
79
|
+
|
80
|
+
class ConnectWaiter(Waiter, dc.Frozen):
|
81
|
+
wait: ConnectWait
|
82
|
+
|
83
|
+
def do_wait(self) -> bool:
|
84
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
85
|
+
try:
|
86
|
+
s.connect(self.wait.address)
|
87
|
+
except ConnectionRefusedError:
|
88
|
+
return False
|
89
|
+
else:
|
90
|
+
return True
|
91
|
+
|
92
|
+
|
93
|
+
@waiter_for.register
|
94
|
+
def _(wait: ConnectWait) -> ConnectWaiter:
|
95
|
+
return ConnectWaiter(wait)
|
omlish/lang/__init__.py
CHANGED
omlish/lang/contextmanagers.py
CHANGED
@@ -18,6 +18,11 @@ T = ta.TypeVar('T')
|
|
18
18
|
##
|
19
19
|
|
20
20
|
|
21
|
+
class _NOT_SET: # noqa
|
22
|
+
def __new__(cls, *args, **kwargs): # noqa
|
23
|
+
raise TypeError
|
24
|
+
|
25
|
+
|
21
26
|
class ContextManaged:
|
22
27
|
def __enter__(self) -> ta.Self:
|
23
28
|
return self
|
@@ -31,21 +36,21 @@ class ContextManaged:
|
|
31
36
|
return None
|
32
37
|
|
33
38
|
|
34
|
-
class
|
35
|
-
def
|
36
|
-
|
37
|
-
|
39
|
+
class NopContextManager(ContextManaged):
|
40
|
+
def __init__(self, /, value: ta.Any = _NOT_SET) -> None:
|
41
|
+
super().__init__()
|
38
42
|
|
39
|
-
|
43
|
+
self._value = value
|
40
44
|
|
45
|
+
def __enter__(self):
|
46
|
+
if (value := self._value) is _NOT_SET:
|
47
|
+
return self
|
48
|
+
else:
|
49
|
+
return value
|
41
50
|
|
42
|
-
class NopContextManager:
|
43
51
|
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
44
52
|
raise TypeError
|
45
53
|
|
46
|
-
def __call__(self, *args, **kwargs):
|
47
|
-
return NOP_CONTEXT_MANAGED
|
48
|
-
|
49
54
|
|
50
55
|
NOP_CONTEXT_MANAGER = NopContextManager()
|
51
56
|
|
@@ -350,7 +355,7 @@ def default_lock(value: DefaultLockable, default: DefaultLockable = None) -> Loc
|
|
350
355
|
return lambda: lock
|
351
356
|
|
352
357
|
elif value is False or value is None:
|
353
|
-
return NOP_CONTEXT_MANAGER
|
358
|
+
return lambda: NOP_CONTEXT_MANAGER
|
354
359
|
|
355
360
|
elif callable(value):
|
356
361
|
return value
|
omlish/libc.py
CHANGED
@@ -62,7 +62,6 @@ libc.free.argtypes = [ct.c_void_p]
|
|
62
62
|
|
63
63
|
|
64
64
|
class Malloc:
|
65
|
-
|
66
65
|
def __init__(self, sz: int) -> None:
|
67
66
|
super().__init__()
|
68
67
|
|
@@ -174,7 +173,6 @@ if LINUX:
|
|
174
173
|
|
175
174
|
|
176
175
|
class Mmap:
|
177
|
-
|
178
176
|
def __init__(
|
179
177
|
self,
|
180
178
|
length: int,
|
@@ -537,14 +535,14 @@ elif DARWIN:
|
|
537
535
|
|
538
536
|
|
539
537
|
if LINUX:
|
540
|
-
def gettid():
|
538
|
+
def gettid() -> int:
|
541
539
|
syscalls = {
|
542
540
|
'i386': 224, # unistd_32.h: #define __NR_gettid 224
|
543
541
|
'x86_64': 186, # unistd_64.h: #define __NR_gettid 186
|
544
542
|
'aarch64': 178, # asm-generic/unistd.h: #define __NR_gettid 178
|
545
543
|
}
|
546
544
|
try:
|
547
|
-
tid =
|
545
|
+
tid = libc.syscall(syscalls[platform.machine()])
|
548
546
|
except Exception: # noqa
|
549
547
|
tid = -1
|
550
548
|
return tid
|
omlish/lite/timeouts.py
CHANGED
@@ -8,7 +8,7 @@ import time
|
|
8
8
|
import typing as ta
|
9
9
|
|
10
10
|
|
11
|
-
TimeoutLike = ta.Union['Timeout', 'Timeout.Default', ta.Iterable['TimeoutLike'], float] # ta.TypeAlias
|
11
|
+
TimeoutLike = ta.Union['Timeout', ta.Type['Timeout.Default'], ta.Iterable['TimeoutLike'], float] # ta.TypeAlias
|
12
12
|
|
13
13
|
|
14
14
|
##
|
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:
|
@@ -1,5 +1,5 @@
|
|
1
1
|
omlish/.manifests.json,sha256=YGmAnUBszmosQQ_7Hh2wwtDiYdYZ4unNKYzOtALuels,7968
|
2
|
-
omlish/__about__.py,sha256=
|
2
|
+
omlish/__about__.py,sha256=ZBkJyvFWxZFNvvD7zkGGThXCoE7JjItJdlIadSjnLho,3380
|
3
3
|
omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
|
4
4
|
omlish/c3.py,sha256=ubu7lHwss5V4UznbejAI0qXhXahrU01MysuHOZI9C4U,8116
|
5
5
|
omlish/cached.py,sha256=UI-XTFBwA6YXWJJJeBn-WkwBkfzDjLBBaZf4nIJA9y0,510
|
@@ -7,7 +7,7 @@ omlish/check.py,sha256=THqm6jD1a0skAO5EC8SOVg58yq96Vk5wcuruBkCYxyU,2016
|
|
7
7
|
omlish/datetimes.py,sha256=HajeM1kBvwlTa-uR1TTZHmZ3zTPnnUr1uGGQhiO1XQ0,2152
|
8
8
|
omlish/defs.py,sha256=9uUjJuVIbCBL3g14fyzAp-9gH935MFofvlfOGwcBIaM,4913
|
9
9
|
omlish/dynamic.py,sha256=kIZokHHid8a0pIAPXMNiXrVJvJJyBnY49WP1a2m-HUQ,6525
|
10
|
-
omlish/libc.py,sha256=
|
10
|
+
omlish/libc.py,sha256=8K4c66YV1ziJerl5poAAYCmsV-VSsHkT3EHhPW04ufg,15639
|
11
11
|
omlish/outcome.py,sha256=ABIE0zjjTyTNtn-ZqQ_9_mUzLiBQ3sDAyqc9JVD8N2k,7852
|
12
12
|
omlish/runmodule.py,sha256=PWvuAaJ9wQQn6bx9ftEL3_d04DyotNn8dR_twm2pgw0,700
|
13
13
|
omlish/shlex.py,sha256=bsW2XUD8GiMTUTDefJejZ5AyqT1pTgWMPD0BMoF02jE,248
|
@@ -175,6 +175,12 @@ omlish/configs/processing/matching.py,sha256=R64RxpPB1uX5Ztvvk2dQ2xi_xwlaxkxQgZw
|
|
175
175
|
omlish/configs/processing/names.py,sha256=weHmaTclzgM9lUn3aBtw-kwZ3mc2N-CZlFg3Kd_UsKo,1093
|
176
176
|
omlish/configs/processing/rewriting.py,sha256=v7PfHtuTn5v_5Y6Au7oMN2Z0nxAMy1iYyO5CXnTvZhs,4226
|
177
177
|
omlish/configs/processing/strings.py,sha256=qFS2oh6z02IaM_q4lTKLdufzkJqAJ6J-Qjrz5S-QJoM,826
|
178
|
+
omlish/daemons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
179
|
+
omlish/daemons/daemon.py,sha256=rK5T_ZbQPwwF-yLgS03fmJ0cZsVGSjTF5M4xSq_YiM0,5534
|
180
|
+
omlish/daemons/reparent.py,sha256=UaG2X6VJHJPOlUwHPNRH3aWGgF0Fg771jjO9IRPLlyY,280
|
181
|
+
omlish/daemons/spawning.py,sha256=cx00xeqSrfhlFbjCtKqaBHvMuHwB9hdjuKNHzAAo_dw,4030
|
182
|
+
omlish/daemons/targets.py,sha256=scq6BYgzi0H2apfgD74U0D8_msQuYAel3Qiij74YWVo,1501
|
183
|
+
omlish/daemons/waiting.py,sha256=RfgD1L33QQVbD2431dkKZGE4w6DUcGvYeRXXi8puAP4,1676
|
178
184
|
omlish/dataclasses/__init__.py,sha256=D7I6ZJjEFeLN2re8oOZ_7JKWiG2plrPnaUFq3iEXYmQ,1553
|
179
185
|
omlish/dataclasses/utils.py,sha256=N2seT8cJtfOv-41D7F3E-q4us-FCTQmnxxPv3dt1OcI,3796
|
180
186
|
omlish/dataclasses/impl/LICENSE,sha256=Oy-B_iHRgcSZxZolbI4ZaEVdZonSaaqFNzv7avQdo78,13936
|
@@ -385,11 +391,11 @@ omlish/iterators/iterators.py,sha256=ghI4dO6WPyyFOLTIIMaHQ_IOy2xXaFpGPqveZ5YGIBU
|
|
385
391
|
omlish/iterators/recipes.py,sha256=53mkexitMhkwXQZbL6DrhpT0WePQ_56uXd5Jaw3DfzI,467
|
386
392
|
omlish/iterators/tools.py,sha256=Pi4ybXytUXVZ3xwK89xpPImQfYYId9p1vIFQvVqVLqA,2551
|
387
393
|
omlish/iterators/unique.py,sha256=0jAX3kwzVfRNhe0Tmh7kVP_Q2WBIn8POo_O-rgFV0rQ,1390
|
388
|
-
omlish/lang/__init__.py,sha256=
|
394
|
+
omlish/lang/__init__.py,sha256=bE8ra_QM5M4k8v3qFtreTjQswaZClIOaqXt9fE43MD0,4079
|
389
395
|
omlish/lang/cached.py,sha256=tQaqMu1LID0q4NSTk5vPXsgxIBWSFAmjs5AhQoEHoCQ,7833
|
390
396
|
omlish/lang/clsdct.py,sha256=sJYadm-fwzti-gsi98knR5qQUxriBmOqQE_qz3RopNk,1743
|
391
397
|
omlish/lang/cmp.py,sha256=5vbzWWbqdzDmNKAGL19z6ZfUKe5Ci49e-Oegf9f4BsE,1346
|
392
|
-
omlish/lang/contextmanagers.py,sha256=
|
398
|
+
omlish/lang/contextmanagers.py,sha256=Mrn8NJ3pP0Zxi-IoGqSjZDdWUctsyee2vrZ2FtZvNmo,10529
|
393
399
|
omlish/lang/datetimes.py,sha256=ehI_DhQRM-bDxAavnp470XcekbbXc4Gdw9y1KpHDJT0,223
|
394
400
|
omlish/lang/descriptors.py,sha256=njkYDS1gn5p4-3v1jr-s_srauC7tvvt571RjE7Q4LXE,6616
|
395
401
|
omlish/lang/exceptions.py,sha256=qJBo3NU1mOWWm-NhQUHCY5feYXR3arZVyEHinLsmRH4,47
|
@@ -434,7 +440,7 @@ omlish/lite/resources.py,sha256=YNSmX1Ohck1aoWRs55a-o5ChVbFJIQhtbqE-XwF55Oc,326
|
|
434
440
|
omlish/lite/runtime.py,sha256=XQo408zxTdJdppUZqOWHyeUR50VlCpNIExNGHz4U6O4,459
|
435
441
|
omlish/lite/secrets.py,sha256=3Mz3V2jf__XU9qNHcH56sBSw95L3U2UPL24bjvobG0c,816
|
436
442
|
omlish/lite/strings.py,sha256=QGxT1Yh4oI8ycsfeobxnjEhvDob_GiAKLeIhZwo1j24,1986
|
437
|
-
omlish/lite/timeouts.py,sha256=
|
443
|
+
omlish/lite/timeouts.py,sha256=lhXo0zwpLM7nr2-AliBRui2BO9jh7B8ALxetwOq6hYI,4968
|
438
444
|
omlish/lite/timing.py,sha256=aVu3hEDB_jyTF_ryZI7iU-xg4q8CNwqpp9Apfru_iwY,196
|
439
445
|
omlish/lite/types.py,sha256=fP5EMyBdEp2LmDxcHjUDtwAMdR06ISr9lKOL7smWfHM,140
|
440
446
|
omlish/lite/typing.py,sha256=U3-JaEnkDSYxK4tsu_MzUn3RP6qALBe5FXQXpD-licE,1090
|
@@ -520,7 +526,7 @@ omlish/os/deathpacts/heartbeatfile.py,sha256=OybdvhM2kxBTuoJWOJJ5LcX-0lg3jTOvvD2
|
|
520
526
|
omlish/os/deathpacts/pipe.py,sha256=ZH-l-fIKyurocCehqOgvaYRurxIEMWe8D7l2dsJeGws,3214
|
521
527
|
omlish/os/pidfiles/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
522
528
|
omlish/os/pidfiles/manager.py,sha256=qSEwNaWT1KOAnU0KxliwvU_uowme5jyf1FyIPsGwnTY,2391
|
523
|
-
omlish/os/pidfiles/pidfile.py,sha256=
|
529
|
+
omlish/os/pidfiles/pidfile.py,sha256=wrttUQB7rk2SJ7MqtaRlqVGii5Kg6LWm3eV2auHb3zA,4125
|
524
530
|
omlish/os/pidfiles/pinning.py,sha256=_AwYjJc1UGX7mdCOk4mItJJcsOJo3RW2ebBOm2noW5Y,6359
|
525
531
|
omlish/reflect/__init__.py,sha256=Er2yBHibVO16hFNA1szQF2_f43Y3HRCBWtS-fjsOIYc,798
|
526
532
|
omlish/reflect/inspect.py,sha256=WCo2YpBYauKw6k758FLlZ_H4Q05rgVPs96fEv9w6zHQ,1538
|
@@ -695,9 +701,9 @@ omlish/text/indent.py,sha256=YjtJEBYWuk8--b9JU_T6q4yxV85_TR7VEVr5ViRCFwk,1336
|
|
695
701
|
omlish/text/minja.py,sha256=jZC-fp3Xuhx48ppqsf2Sf1pHbC0t8XBB7UpUUoOk2Qw,5751
|
696
702
|
omlish/text/parts.py,sha256=JkNZpyR2tv2CNcTaWJJhpQ9E4F0yPR8P_YfDbZfMtwQ,6182
|
697
703
|
omlish/text/random.py,sha256=jNWpqiaKjKyTdMXC-pWAsSC10AAP-cmRRPVhm59ZWLk,194
|
698
|
-
omlish-0.0.0.
|
699
|
-
omlish-0.0.0.
|
700
|
-
omlish-0.0.0.
|
701
|
-
omlish-0.0.0.
|
702
|
-
omlish-0.0.0.
|
703
|
-
omlish-0.0.0.
|
704
|
+
omlish-0.0.0.dev231.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
|
705
|
+
omlish-0.0.0.dev231.dist-info/METADATA,sha256=glvhjWJ8XJlRwe1JuGQQYJBpwdsJhjPFHEWn7Z985oE,4176
|
706
|
+
omlish-0.0.0.dev231.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
707
|
+
omlish-0.0.0.dev231.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
|
708
|
+
omlish-0.0.0.dev231.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
|
709
|
+
omlish-0.0.0.dev231.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|