omlish 0.0.0.dev224__py3-none-any.whl → 0.0.0.dev226__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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