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.
- omlish/__about__.py +2 -2
- omlish/asyncs/asyncio/subprocesses.py +19 -18
- omlish/asyncs/asyncio/timeouts.py +5 -2
- omlish/asyncs/asyncs.py +0 -1
- omlish/bootstrap/sys.py +2 -2
- omlish/dataclasses/impl/metaclass.py +5 -0
- omlish/diag/lslocks.py +64 -0
- omlish/diag/lsof.py +264 -0
- omlish/diag/ps.py +40 -21
- omlish/http/coro/server.py +5 -54
- omlish/http/coro/simple.py +1 -1
- omlish/http/coro/sockets.py +59 -0
- omlish/lang/__init__.py +8 -8
- omlish/lang/imports.py +22 -0
- omlish/libc.py +10 -0
- omlish/lite/dataclasses.py +23 -0
- omlish/lite/timeouts.py +202 -0
- omlish/multiprocessing/__init__.py +0 -7
- omlish/os/fcntl.py +59 -0
- omlish/os/pidfiles/__init__.py +0 -0
- omlish/os/pidfiles/manager.py +111 -0
- omlish/os/pidfiles/pidfile.py +152 -0
- omlish/secrets/crypto.py +1 -2
- omlish/secrets/openssl.py +1 -1
- omlish/secrets/tempssl.py +4 -7
- omlish/sockets/handlers.py +4 -0
- omlish/sockets/server/handlers.py +22 -0
- omlish/subprocesses/__init__.py +0 -0
- omlish/subprocesses/async_.py +97 -0
- omlish/subprocesses/base.py +221 -0
- omlish/subprocesses/run.py +138 -0
- omlish/subprocesses/sync.py +153 -0
- omlish/subprocesses/utils.py +22 -0
- omlish/subprocesses/wrap.py +23 -0
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev226.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev226.dist-info}/RECORD +41 -29
- omlish/lang/timeouts.py +0 -53
- omlish/os/pidfile.py +0 -69
- omlish/subprocesses.py +0 -510
- /omlish/{multiprocessing → os}/death.py +0 -0
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev226.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev226.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev226.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev224.dist-info → omlish-0.0.0.dev226.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
@@ -10,9 +10,10 @@ import sys
|
|
10
10
|
import typing as ta
|
11
11
|
|
12
12
|
from ...lite.check import check
|
13
|
-
from ...
|
14
|
-
from ...subprocesses import
|
15
|
-
from ...subprocesses import
|
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[
|
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[
|
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[
|
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[
|
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
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
|
5
|
+
import typing as ta
|
4
6
|
|
5
|
-
from .. import
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
31
|
-
|
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()
|
omlish/http/coro/server.py
CHANGED
@@ -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
|
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
|
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
|
omlish/http/coro/simple.py
CHANGED
@@ -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 .
|
24
|
+
from .sockets import CoroHttpServerSocketHandler
|
25
25
|
|
26
26
|
|
27
27
|
if ta.TYPE_CHECKING:
|