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
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev224'
2
- __revision__ = '0980d15b9ba0f06f68cf7bdb68fbc8626696075f'
1
+ __version__ = '0.0.0.dev226'
2
+ __revision__ = '1f3419dac98e9db37df48a9a952f780d17bf31ed'
3
3
 
4
4
 
5
5
  #
@@ -10,9 +10,10 @@ 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 ...lite.timeouts import TimeoutLike
14
+ from ...subprocesses.async_ import AbstractAsyncSubprocesses
15
+ from ...subprocesses.run import SubprocessRun
16
+ from ...subprocesses.run import SubprocessRunOutput
16
17
  from .timeouts import asyncio_maybe_timeout
17
18
 
18
19
 
@@ -130,7 +131,7 @@ class AsyncioProcessCommunicator:
130
131
  async def communicate(
131
132
  self,
132
133
  input: ta.Any = None, # noqa
133
- timeout: ta.Optional[float] = None,
134
+ timeout: ta.Optional[TimeoutLike] = None,
134
135
  ) -> Communication:
135
136
  return await asyncio_maybe_timeout(self._communicate(input), timeout)
136
137
 
@@ -143,7 +144,7 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
143
144
  self,
144
145
  proc: asyncio.subprocess.Process,
145
146
  input: ta.Any = None, # noqa
146
- timeout: ta.Optional[float] = None,
147
+ timeout: ta.Optional[TimeoutLike] = None,
147
148
  ) -> ta.Tuple[ta.Optional[bytes], ta.Optional[bytes]]:
148
149
  return await AsyncioProcessCommunicator(proc).communicate(input, timeout) # noqa
149
150
 
@@ -154,22 +155,22 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
154
155
  self,
155
156
  *cmd: str,
156
157
  shell: bool = False,
157
- timeout: ta.Optional[float] = None,
158
+ timeout: ta.Optional[TimeoutLike] = None,
158
159
  **kwargs: ta.Any,
159
160
  ) -> 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
161
  with self.prepare_and_wrap( *cmd, shell=shell, **kwargs) as (cmd, kwargs): # noqa
162
+ fac: ta.Any
163
+ if shell:
164
+ fac = functools.partial(
165
+ asyncio.create_subprocess_shell,
166
+ check.single(cmd),
167
+ )
168
+ else:
169
+ fac = functools.partial(
170
+ asyncio.create_subprocess_exec,
171
+ *cmd,
172
+ )
173
+
173
174
  proc: asyncio.subprocess.Process = await fac(**kwargs)
174
175
  try:
175
176
  yield proc
@@ -3,14 +3,17 @@
3
3
  import asyncio
4
4
  import typing as ta
5
5
 
6
+ from ...lite.timeouts import Timeout
7
+ from ...lite.timeouts import TimeoutLike
8
+
6
9
 
7
10
  AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
8
11
 
9
12
 
10
13
  def asyncio_maybe_timeout(
11
14
  fut: AwaitableT,
12
- timeout: ta.Optional[float] = None,
15
+ timeout: ta.Optional[TimeoutLike] = None,
13
16
  ) -> AwaitableT:
14
17
  if timeout is not None:
15
- fut = asyncio.wait_for(fut, timeout) # type: ignore
18
+ fut = asyncio.wait_for(fut, Timeout.of(timeout)()) # type: ignore
16
19
  return fut
omlish/asyncs/asyncs.py CHANGED
@@ -55,7 +55,6 @@ async def async_list(fn: ta.Callable[..., ta.AsyncIterator[T]], *args, **kwargs)
55
55
 
56
56
 
57
57
  class SyncableIterable(ta.Generic[T]):
58
-
59
58
  def __init__(self, obj) -> None:
60
59
  super().__init__()
61
60
  self._obj = obj
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/diag/lslocks.py ADDED
@@ -0,0 +1,64 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ """
4
+ https://github.com/util-linux/util-linux/blob/a4436c7bf07f98a6381c7dfa2ab3f9a415f9c479/misc-utils/lslocks.c
5
+ """
6
+ import dataclasses as dc
7
+ import json
8
+ import typing as ta
9
+
10
+ from ..lite.check import check
11
+ from ..lite.marshal import OBJ_MARSHALER_FIELD_KEY
12
+ from ..lite.marshal import unmarshal_obj
13
+ from ..subprocesses.run import SubprocessRun
14
+ from ..subprocesses.run import SubprocessRunnable
15
+ from ..subprocesses.run import SubprocessRunOutput
16
+
17
+
18
+ ##
19
+
20
+
21
+ @dc.dataclass(frozen=True)
22
+ class LsLocksItem:
23
+ """https://manpages.ubuntu.com/manpages/lunar/man8/lslocks.8.html"""
24
+
25
+ command: str
26
+ pid: int
27
+ type: str # POSIX | FLOCK | OFDLCK
28
+ size: ta.Optional[int]
29
+ mode: str # READ | WRITE
30
+ mandatory: bool = dc.field(metadata={OBJ_MARSHALER_FIELD_KEY: 'm'})
31
+ start: int
32
+ end: int
33
+ path: str
34
+ blocker: ta.Optional[int] = None
35
+
36
+
37
+ ##
38
+
39
+
40
+ @dc.dataclass(frozen=True)
41
+ class LsLocksCommand(SubprocessRunnable):
42
+ pid: ta.Optional[int] = None
43
+ no_inaccessible: bool = False
44
+
45
+ def make_run(self) -> SubprocessRun:
46
+ return SubprocessRun.of(
47
+ 'lslocks',
48
+ '--json',
49
+ '--bytes',
50
+ '--notruncate',
51
+ *(['--pid', str(self.pid)] if self.pid is not None else []),
52
+ *(['--noinaccessible'] if self.no_inaccessible else []),
53
+
54
+ check=True,
55
+ stdout='pipe',
56
+ stderr='devnull',
57
+ )
58
+
59
+ def handle_run_output(self, output: SubprocessRunOutput) -> ta.List[LsLocksItem]:
60
+ buf = check.not_none(output.stdout).decode().strip()
61
+ if not buf:
62
+ return []
63
+ obj = json.loads(buf)
64
+ return unmarshal_obj(obj['locks'], ta.List[LsLocksItem])
omlish/diag/lsof.py ADDED
@@ -0,0 +1,264 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ """
4
+ https://man7.org/linux/man-pages/man8/lsof.8.html
5
+
6
+ Included in the process set are fields that identify the command, the process group IDentification (PGID) number, the
7
+ task (thread) ID (TID), and the user ID (UID) number or login name.
8
+ """
9
+ import dataclasses as dc
10
+ import enum
11
+ import typing as ta
12
+
13
+ from ..lite.check import check
14
+ from ..lite.dataclasses import dataclass_repr_omit_falsey
15
+ from ..lite.marshal import OBJ_MARSHALER_OMIT_IF_NONE
16
+ from ..subprocesses.run import SubprocessRun
17
+ from ..subprocesses.run import SubprocessRunnable
18
+ from ..subprocesses.run import SubprocessRunOutput
19
+
20
+
21
+ ##
22
+
23
+
24
+ # FIXME: ??
25
+ # https://unix.stackexchange.com/a/86011
26
+ class LsofItemMode(enum.Enum):
27
+ SOLARIS_NFS_LOCK = 'N'
28
+ READ_LOCK_PARTIAL = 'r'
29
+ READ_LOCK_FULL = 'R'
30
+ WRITE_LOCK_PARTIAL = 'w'
31
+ WRITE_LOCK_FULL = 'W'
32
+ READ_WRITE_LOCK = 'u'
33
+ UNKNOWN_LOCK_TYPE = 'U'
34
+ XENIX_LOCK_PARTIAL = 'x'
35
+ XENIX_LOCK_FULL = 'X'
36
+
37
+
38
+ @dc.dataclass(frozen=True)
39
+ class _LsofFieldMeta:
40
+ prefix: str
41
+ process: bool = False
42
+ variadic: bool = False
43
+
44
+ @classmethod
45
+ def make(cls, *args, **kwargs):
46
+ return {
47
+ cls: cls(*args, **kwargs),
48
+ OBJ_MARSHALER_OMIT_IF_NONE: True,
49
+ }
50
+
51
+
52
+ @dc.dataclass(frozen=True)
53
+ class LsofItem:
54
+ __repr__ = dataclass_repr_omit_falsey
55
+
56
+ class _PREFIX:
57
+ def __new__(cls, *args, **kwargs): # noqa
58
+ raise TypeError
59
+
60
+ class _PROCESS:
61
+ def __new__(cls, *args, **kwargs): # noqa
62
+ raise TypeError
63
+
64
+ pid: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('p', process=True))
65
+ pgid: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('g', process=True))
66
+ ppid: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('R', process=True))
67
+ cmd: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('c', process=True))
68
+
69
+ uid: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('u', process=True))
70
+ login_name: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('L', process=True))
71
+
72
+ # FD is the File Descriptor number of the file or:
73
+ # cwd current working directory;
74
+ # Lnn library references (AIX);
75
+ # ctty character tty;
76
+ # DEL deleted file;
77
+ # err FD information error (see NAME column);
78
+ # fp. Fileport (Darwin);
79
+ # jld jail directory (FreeBSD);
80
+ # ltx shared library text (code and data);
81
+ # Mxx hex memory-mapped type number xx.
82
+ # m86 DOS Merge mapped file;
83
+ # mem memory-mapped file;
84
+ # mmap memory-mapped device;
85
+ # NOFD for a Linux /proc/<PID>/fd directory that can't be opened -- the directory path appears in the NAME column,
86
+ # followed by an error message;
87
+ # pd parent directory;
88
+ # Rnn unknown pregion number (HP-UX);
89
+ # rtd root directory;
90
+ # twd per task current working directory;
91
+ # txt program text (code and data);
92
+ # v86 VP/ix mapped file;
93
+ #
94
+ # FD is followed by one of these characters, describing the mode under which the file is open:
95
+ # r for read access;
96
+ # w for write access;
97
+ # u for read and write access;
98
+ # space if mode unknown and no lock
99
+ # character follows;
100
+ # `-' if mode unknown and lock
101
+ # character follows.
102
+ #
103
+ # The mode character is followed by one of these lock characters, describing the type of lock applied to the file:
104
+ # N for a Solaris NFS lock of unknown type;
105
+ # r for read lock on part of the file;
106
+ # R for a read lock on the entire file;
107
+ # w for a write lock on part of the file;
108
+ # W for a write lock on the entire file;
109
+ # u for a read and write lock of any length;
110
+ # U for a lock of unknown type;
111
+ # x for an SCO OpenServer Xenix lock on part of the file;
112
+ # X for an SCO OpenServer Xenix lock on the entire file;
113
+ # space if there is no lock.
114
+ #
115
+ fd: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('f'))
116
+
117
+ inode: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('i'))
118
+ name: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('n'))
119
+
120
+ # r = read; w = write; u = read/write
121
+ access: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('a'))
122
+
123
+ # r/R = read; w/W = write; u = read/write
124
+ lock: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('l'))
125
+
126
+ file_flags: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('G'))
127
+ file_type: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('t'))
128
+ file_size: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('s'))
129
+ file_offset: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('o')) # as 0t<dec> or 0x<hex>
130
+
131
+ device_character_code: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('d'))
132
+ device_number: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('D')) # as 0x<hex>
133
+ raw_device_number: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('r')) # as 0x<hex>
134
+
135
+ share_count: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('C'))
136
+ link_count: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('k'))
137
+
138
+ stream_info: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('S'))
139
+ protocol_name: ta.Optional[str] = dc.field(default=None, metadata=_LsofFieldMeta.make('P'))
140
+
141
+ tcp_tpi_info: ta.Optional[ta.Sequence[str]] = dc.field(
142
+ default=None,
143
+ metadata=_LsofFieldMeta.make('T', variadic=True),
144
+ )
145
+
146
+ # 'K': 'task_id', # unknown field
147
+ # 'M': 'task_command_name', # unknown field
148
+
149
+ _FIELDS_BY_PREFIX: ta.ClassVar[ta.Mapping[str, dc.Field]]
150
+ _DEFAULT_PREFIXES: ta.ClassVar[str]
151
+
152
+ @classmethod
153
+ def from_prefixes(cls, dct: ta.Mapping[str, ta.Any]) -> 'LsofItem':
154
+ print(dct)
155
+ kw: ta.Dict[str, ta.Any] = {
156
+ fld.name: val
157
+ for pfx, val in dct.items()
158
+ if (fld := cls._FIELDS_BY_PREFIX.get(pfx)) is not None
159
+ }
160
+
161
+ return cls(**kw)
162
+
163
+ @classmethod
164
+ def from_prefix_lines(cls, lines: ta.Iterable[str]) -> ta.List['LsofItem']:
165
+ proc_dct: ta.Dict[str, ta.Any] = {}
166
+ file_dct: ta.Dict[str, ta.Any] = {}
167
+ out: ta.List[LsofItem] = []
168
+
169
+ def emit() -> None:
170
+ if file_dct:
171
+ out.append(cls.from_prefixes({**proc_dct, **file_dct}))
172
+
173
+ for line in lines:
174
+ pfx, val = line[0], line[1:]
175
+ try:
176
+ fld = cls._FIELDS_BY_PREFIX[pfx]
177
+ except KeyError:
178
+ continue
179
+ meta: _LsofFieldMeta = fld.metadata[_LsofFieldMeta]
180
+
181
+ if pfx == 'p':
182
+ emit()
183
+ proc_dct = {}
184
+ file_dct = {}
185
+ elif pfx == 'f':
186
+ emit()
187
+ file_dct = {}
188
+
189
+ if meta.process:
190
+ dct = proc_dct
191
+ else:
192
+ dct = file_dct
193
+
194
+ if meta.variadic:
195
+ dct.setdefault(pfx, []).append(val)
196
+ else:
197
+ if pfx in dct:
198
+ raise KeyError(pfx)
199
+ dct[pfx] = val
200
+
201
+ emit()
202
+
203
+ return out
204
+
205
+
206
+ LsofItem._FIELDS_BY_PREFIX = { # noqa
207
+ meta.prefix: fld
208
+ for fld in dc.fields(LsofItem) # noqa
209
+ if (meta := fld.metadata.get(_LsofFieldMeta)) is not None # noqa
210
+ }
211
+
212
+ LsofItem._DEFAULT_PREFIXES = ''.join(LsofItem._FIELDS_BY_PREFIX) # noqa
213
+
214
+
215
+ ##
216
+
217
+
218
+ @dc.dataclass(frozen=True)
219
+ class LsofCommand(SubprocessRunnable[ta.List[LsofItem]]):
220
+ pid: ta.Optional[int] = None
221
+ file: ta.Optional[str] = None
222
+
223
+ prefixes: ta.Optional[ta.Sequence[str]] = None
224
+
225
+ def make_run(self) -> SubprocessRun:
226
+ if (prefixes := self.prefixes) is None:
227
+ prefixes = LsofItem._DEFAULT_PREFIXES # noqa
228
+
229
+ return SubprocessRun.of(
230
+ 'lsof',
231
+ '-F', ''.join(prefixes),
232
+ *(['-p', str(self.pid)] if self.pid is not None else []),
233
+ *([self.file] if self.file is not None else []),
234
+
235
+ stdout='pipe',
236
+ stderr='devnull',
237
+ check=True,
238
+ )
239
+
240
+ def handle_run_output(self, output: SubprocessRunOutput) -> ta.List[LsofItem]:
241
+ lines = [s for l in check.not_none(output.stdout).decode().splitlines() if (s := l.strip())]
242
+ return LsofItem.from_prefix_lines(lines)
243
+
244
+
245
+ if __name__ == '__main__':
246
+ def _main() -> None:
247
+ argparse = __import__('argparse')
248
+ parser = argparse.ArgumentParser()
249
+ parser.add_argument('--pid', '-p', type=int)
250
+ parser.add_argument('file', nargs='?')
251
+ args = parser.parse_args()
252
+
253
+ importlib = __import__('importlib')
254
+ subprocesses = importlib.import_module('..subprocesses.sync', package=__package__).subprocesses
255
+ items = LsofCommand(
256
+ pid=args.pid,
257
+ file=args.file,
258
+ ).run(subprocesses)
259
+
260
+ json = __import__('json')
261
+ marshal_obj = importlib.import_module('..lite.marshal', package=__package__).marshal_obj
262
+ print(json.dumps(marshal_obj(items), indent=2))
263
+
264
+ _main()
omlish/diag/ps.py CHANGED
@@ -1,8 +1,15 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
1
3
  import dataclasses as dc
2
4
  import os
3
- import subprocess
5
+ import typing as ta
4
6
 
5
- from .. import lang
7
+ from ..lite.check import check
8
+ from ..lite.timeouts import Timeout
9
+ from ..subprocesses.run import SubprocessRun
10
+ from ..subprocesses.run import SubprocessRunnable
11
+ from ..subprocesses.run import SubprocessRunOutput
12
+ from ..subprocesses.sync import subprocesses
6
13
 
7
14
 
8
15
  @dc.dataclass(frozen=True)
@@ -12,23 +19,36 @@ class PsItem:
12
19
  cmd: str
13
20
 
14
21
 
15
- def get_ps_item(pid: int, timeout: lang.Timeout | None = None) -> PsItem:
16
- timeout = lang.timeout(timeout)
17
- out = subprocess.check_output(
18
- ['ps', '-o', 'pid=,ppid=,command=', str(int(pid))],
19
- timeout=timeout.or_(None),
20
- ).decode().strip()
21
- opid, _, rest = out.partition(' ')
22
- ppid, _, cmd = rest.strip().partition(' ')
23
- return PsItem(
24
- int(opid),
25
- int(ppid),
26
- cmd.strip(),
27
- )
22
+ @dc.dataclass(frozen=True)
23
+ class PsCommand(SubprocessRunnable):
24
+ pid: ta.Optional[int] = None
25
+
26
+ def make_run(self) -> SubprocessRun:
27
+ return SubprocessRun.of(
28
+ 'ps',
29
+ '-o', 'pid=,ppid=,command=',
30
+ *([str(int(self.pid))] if self.pid is not None else []),
31
+
32
+ check=True,
33
+ stdout='pipe',
34
+ stderr='devnull',
35
+ )
36
+
37
+ def handle_run_output(self, output: SubprocessRunOutput) -> PsItem:
38
+ opid, ppid, cmd = check.not_none(output.stdout).decode().split(maxsplit=2)
39
+ return PsItem(
40
+ int(opid),
41
+ int(ppid),
42
+ cmd.strip(),
43
+ )
44
+
28
45
 
46
+ def get_ps_item(pid: int, timeout: ta.Optional[Timeout] = None) -> PsItem:
47
+ return PsCommand(pid).run(subprocesses, timeout=timeout)
29
48
 
30
- def get_ps_lineage(pid: int, timeout: lang.Timeout | None = None) -> list[PsItem]:
31
- timeout = lang.timeout(timeout)
49
+
50
+ def get_ps_lineage(pid: int, timeout: ta.Optional[Timeout] = None) -> ta.List[PsItem]:
51
+ timeout = Timeout.of(timeout)
32
52
  ret: list[PsItem] = []
33
53
  while True:
34
54
  cur = get_ps_item(pid, timeout)
@@ -39,9 +59,8 @@ def get_ps_lineage(pid: int, timeout: lang.Timeout | None = None) -> list[PsItem
39
59
  return ret
40
60
 
41
61
 
42
- def _main() -> None:
43
- print(get_ps_lineage(os.getpid()))
44
-
45
-
46
62
  if __name__ == '__main__':
63
+ def _main() -> None:
64
+ print(get_ps_lineage(os.getpid()))
65
+
47
66
  _main()
@@ -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 EOFError:
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 EOFError # noqa
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
@@ -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 .server import CoroHttpServerSocketHandler
24
+ from .sockets import CoroHttpServerSocketHandler
25
25
 
26
26
 
27
27
  if ta.TYPE_CHECKING: