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.
Files changed (44) hide show
  1. omlish/__about__.py +2 -2
  2. omlish/asyncs/asyncio/subprocesses.py +19 -18
  3. omlish/asyncs/asyncio/timeouts.py +5 -2
  4. omlish/asyncs/asyncs.py +0 -1
  5. omlish/bootstrap/sys.py +2 -2
  6. omlish/dataclasses/impl/metaclass.py +5 -0
  7. omlish/diag/lslocks.py +64 -0
  8. omlish/diag/lsof.py +264 -0
  9. omlish/diag/ps.py +40 -21
  10. omlish/http/coro/server.py +5 -54
  11. omlish/http/coro/simple.py +1 -1
  12. omlish/http/coro/sockets.py +59 -0
  13. omlish/lang/__init__.py +8 -8
  14. omlish/lang/imports.py +22 -0
  15. omlish/libc.py +10 -0
  16. omlish/lite/dataclasses.py +23 -0
  17. omlish/lite/timeouts.py +202 -0
  18. omlish/multiprocessing/__init__.py +0 -7
  19. omlish/os/fcntl.py +59 -0
  20. omlish/os/pidfiles/__init__.py +0 -0
  21. omlish/os/pidfiles/manager.py +111 -0
  22. omlish/os/pidfiles/pidfile.py +152 -0
  23. omlish/secrets/crypto.py +1 -2
  24. omlish/secrets/openssl.py +1 -1
  25. omlish/secrets/tempssl.py +4 -7
  26. omlish/sockets/handlers.py +4 -0
  27. omlish/sockets/server/handlers.py +22 -0
  28. omlish/subprocesses/__init__.py +0 -0
  29. omlish/subprocesses/async_.py +97 -0
  30. omlish/subprocesses/base.py +221 -0
  31. omlish/subprocesses/run.py +138 -0
  32. omlish/subprocesses/sync.py +153 -0
  33. omlish/subprocesses/utils.py +22 -0
  34. omlish/subprocesses/wrap.py +23 -0
  35. {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev226.dist-info}/METADATA +1 -1
  36. {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev226.dist-info}/RECORD +41 -29
  37. omlish/lang/timeouts.py +0 -53
  38. omlish/os/pidfile.py +0 -69
  39. omlish/subprocesses.py +0 -510
  40. /omlish/{multiprocessing → os}/death.py +0 -0
  41. {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev226.dist-info}/LICENSE +0 -0
  42. {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev226.dist-info}/WHEEL +0 -0
  43. {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev226.dist-info}/entry_points.txt +0 -0
  44. {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
 
@@ -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))
@@ -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()
@@ -4,13 +4,6 @@ import typing as _ta
4
4
  from .. import lang as _lang
5
5
 
6
6
 
7
- from .death import ( # noqa
8
- BaseDeathpact,
9
- Deathpact,
10
- NopDeathpact,
11
- PipeDeathpact,
12
- )
13
-
14
7
  from .proxies import ( # noqa
15
8
  DummyValueProxy,
16
9
  ValueProxy,
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