ominfra 0.0.0.dev137__py3-none-any.whl → 0.0.0.dev138__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.
- ominfra/manage/new/_manage.py +254 -157
- ominfra/manage/new/main.py +63 -13
- ominfra/pyremote.py +196 -145
- ominfra/scripts/supervisor.py +32 -31
- ominfra/supervisor/processimpl.py +32 -31
- {ominfra-0.0.0.dev137.dist-info → ominfra-0.0.0.dev138.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev137.dist-info → ominfra-0.0.0.dev138.dist-info}/RECORD +11 -11
- {ominfra-0.0.0.dev137.dist-info → ominfra-0.0.0.dev138.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev137.dist-info → ominfra-0.0.0.dev138.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev137.dist-info → ominfra-0.0.0.dev138.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev137.dist-info → ominfra-0.0.0.dev138.dist-info}/top_level.txt +0 -0
ominfra/pyremote.py
CHANGED
@@ -5,7 +5,6 @@ Basically this: https://mitogen.networkgenomics.com/howitworks.html
|
|
5
5
|
"""
|
6
6
|
import base64
|
7
7
|
import dataclasses as dc
|
8
|
-
import inspect
|
9
8
|
import json
|
10
9
|
import os
|
11
10
|
import platform
|
@@ -13,7 +12,6 @@ import pwd
|
|
13
12
|
import site
|
14
13
|
import struct
|
15
14
|
import sys
|
16
|
-
import textwrap
|
17
15
|
import typing as ta
|
18
16
|
import zlib
|
19
17
|
|
@@ -21,11 +19,120 @@ import zlib
|
|
21
19
|
##
|
22
20
|
|
23
21
|
|
22
|
+
@dc.dataclass(frozen=True)
|
23
|
+
class PyremoteBootstrapOptions:
|
24
|
+
debug: bool = False
|
25
|
+
|
26
|
+
|
27
|
+
##
|
28
|
+
|
29
|
+
|
30
|
+
@dc.dataclass(frozen=True)
|
31
|
+
class PyremoteEnvInfo:
|
32
|
+
sys_base_prefix: str
|
33
|
+
sys_byteorder: str
|
34
|
+
sys_defaultencoding: str
|
35
|
+
sys_exec_prefix: str
|
36
|
+
sys_executable: str
|
37
|
+
sys_implementation_name: str
|
38
|
+
sys_path: ta.List[str]
|
39
|
+
sys_platform: str
|
40
|
+
sys_prefix: str
|
41
|
+
sys_version: str
|
42
|
+
sys_version_info: ta.List[ta.Union[int, str]]
|
43
|
+
|
44
|
+
platform_architecture: ta.List[str]
|
45
|
+
platform_machine: str
|
46
|
+
platform_platform: str
|
47
|
+
platform_processor: str
|
48
|
+
platform_system: str
|
49
|
+
platform_release: str
|
50
|
+
platform_version: str
|
51
|
+
|
52
|
+
site_userbase: str
|
53
|
+
|
54
|
+
os_cwd: str
|
55
|
+
os_gid: int
|
56
|
+
os_loadavg: ta.List[float]
|
57
|
+
os_login: ta.Optional[str]
|
58
|
+
os_pgrp: int
|
59
|
+
os_pid: int
|
60
|
+
os_ppid: int
|
61
|
+
os_uid: int
|
62
|
+
|
63
|
+
pw_name: str
|
64
|
+
pw_uid: int
|
65
|
+
pw_gid: int
|
66
|
+
pw_gecos: str
|
67
|
+
pw_dir: str
|
68
|
+
pw_shell: str
|
69
|
+
|
70
|
+
env_path: ta.Optional[str]
|
71
|
+
|
72
|
+
|
73
|
+
def _get_pyremote_env_info() -> PyremoteEnvInfo:
|
74
|
+
os_uid = os.getuid()
|
75
|
+
|
76
|
+
pw = pwd.getpwuid(os_uid)
|
77
|
+
|
78
|
+
os_login: ta.Optional[str]
|
79
|
+
try:
|
80
|
+
os_login = os.getlogin()
|
81
|
+
except OSError:
|
82
|
+
os_login = None
|
83
|
+
|
84
|
+
return PyremoteEnvInfo(
|
85
|
+
sys_base_prefix=sys.base_prefix,
|
86
|
+
sys_byteorder=sys.byteorder,
|
87
|
+
sys_defaultencoding=sys.getdefaultencoding(),
|
88
|
+
sys_exec_prefix=sys.exec_prefix,
|
89
|
+
sys_executable=sys.executable,
|
90
|
+
sys_implementation_name=sys.implementation.name,
|
91
|
+
sys_path=sys.path,
|
92
|
+
sys_platform=sys.platform,
|
93
|
+
sys_prefix=sys.prefix,
|
94
|
+
sys_version=sys.version,
|
95
|
+
sys_version_info=list(sys.version_info),
|
96
|
+
|
97
|
+
platform_architecture=list(platform.architecture()),
|
98
|
+
platform_machine=platform.machine(),
|
99
|
+
platform_platform=platform.platform(),
|
100
|
+
platform_processor=platform.processor(),
|
101
|
+
platform_system=platform.system(),
|
102
|
+
platform_release=platform.release(),
|
103
|
+
platform_version=platform.version(),
|
104
|
+
|
105
|
+
site_userbase=site.getuserbase(),
|
106
|
+
|
107
|
+
os_cwd=os.getcwd(),
|
108
|
+
os_gid=os.getgid(),
|
109
|
+
os_loadavg=list(os.getloadavg()),
|
110
|
+
os_login=os_login,
|
111
|
+
os_pgrp=os.getpgrp(),
|
112
|
+
os_pid=os.getpid(),
|
113
|
+
os_ppid=os.getppid(),
|
114
|
+
os_uid=os_uid,
|
115
|
+
|
116
|
+
pw_name=pw.pw_name,
|
117
|
+
pw_uid=pw.pw_uid,
|
118
|
+
pw_gid=pw.pw_gid,
|
119
|
+
pw_gecos=pw.pw_gecos,
|
120
|
+
pw_dir=pw.pw_dir,
|
121
|
+
pw_shell=pw.pw_shell,
|
122
|
+
|
123
|
+
env_path=os.environ.get('PATH'),
|
124
|
+
)
|
125
|
+
|
126
|
+
|
127
|
+
##
|
128
|
+
|
129
|
+
|
24
130
|
_PYREMOTE_BOOTSTRAP_INPUT_FD = 100
|
25
131
|
_PYREMOTE_BOOTSTRAP_SRC_FD = 101
|
26
132
|
|
27
133
|
_PYREMOTE_BOOTSTRAP_CHILD_PID_VAR = '_OPYR_CHILD_PID'
|
28
134
|
_PYREMOTE_BOOTSTRAP_ARGV0_VAR = '_OPYR_ARGV0'
|
135
|
+
_PYREMOTE_BOOTSTRAP_OPTIONS_JSON_VAR = '_OPYR_OPTIONS_JSON'
|
29
136
|
|
30
137
|
_PYREMOTE_BOOTSTRAP_ACK0 = b'OPYR000\n'
|
31
138
|
_PYREMOTE_BOOTSTRAP_ACK1 = b'OPYR001\n'
|
@@ -87,7 +194,9 @@ def _pyremote_bootstrap_main(context_name: str) -> None:
|
|
87
194
|
|
88
195
|
# Read main src from stdin
|
89
196
|
main_z_len = struct.unpack('<I', os.read(0, 4))[0]
|
90
|
-
|
197
|
+
if len(main_z := os.fdopen(0, 'rb').read(main_z_len)) != main_z_len:
|
198
|
+
raise EOFError
|
199
|
+
main_src = zlib.decompress(main_z)
|
91
200
|
|
92
201
|
# Write both copies of main src. Must write to w0 (parent stdin) before w1 (copy pipe) as pipe will likely fill
|
93
202
|
# and block and need to be drained by pyremote_bootstrap_finalize running in parent.
|
@@ -110,6 +219,8 @@ def pyremote_build_bootstrap_cmd(context_name: str) -> str:
|
|
110
219
|
if any(c in context_name for c in '\'"'):
|
111
220
|
raise NameError(context_name)
|
112
221
|
|
222
|
+
import inspect
|
223
|
+
import textwrap
|
113
224
|
bs_src = textwrap.dedent(inspect.getsource(_pyremote_bootstrap_main))
|
114
225
|
|
115
226
|
for gl in [
|
@@ -136,9 +247,6 @@ def pyremote_build_bootstrap_cmd(context_name: str) -> str:
|
|
136
247
|
bs_z = zlib.compress(bs_src.encode('utf-8'))
|
137
248
|
bs_z64 = base64.encodebytes(bs_z).replace(b'\n', b'')
|
138
249
|
|
139
|
-
def dq_repr(o: ta.Any) -> str:
|
140
|
-
return '"' + repr(o)[1:-1] + '"'
|
141
|
-
|
142
250
|
stmts = [
|
143
251
|
f'import {", ".join(_PYREMOTE_BOOTSTRAP_IMPORTS)}',
|
144
252
|
f'exec(zlib.decompress(base64.decodebytes({bs_z64!r})))',
|
@@ -153,99 +261,83 @@ def pyremote_build_bootstrap_cmd(context_name: str) -> str:
|
|
153
261
|
|
154
262
|
|
155
263
|
@dc.dataclass(frozen=True)
|
156
|
-
class
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
sys_implementation_name: str
|
163
|
-
sys_path: ta.List[str]
|
164
|
-
sys_platform: str
|
165
|
-
sys_prefix: str
|
166
|
-
sys_version: str
|
167
|
-
sys_version_info: ta.List[ta.Union[int, str]]
|
168
|
-
|
169
|
-
platform_architecture: ta.List[str]
|
170
|
-
platform_machine: str
|
171
|
-
platform_platform: str
|
172
|
-
platform_processor: str
|
173
|
-
platform_system: str
|
174
|
-
platform_release: str
|
175
|
-
platform_version: str
|
176
|
-
|
177
|
-
site_userbase: str
|
178
|
-
|
179
|
-
os_cwd: str
|
180
|
-
os_gid: int
|
181
|
-
os_loadavg: ta.List[float]
|
182
|
-
os_login: ta.Optional[str]
|
183
|
-
os_pgrp: int
|
184
|
-
os_pid: int
|
185
|
-
os_ppid: int
|
186
|
-
os_uid: int
|
187
|
-
|
188
|
-
pw_name: str
|
189
|
-
pw_uid: int
|
190
|
-
pw_gid: int
|
191
|
-
pw_gecos: str
|
192
|
-
pw_dir: str
|
193
|
-
pw_shell: str
|
194
|
-
|
195
|
-
env_path: ta.Optional[str]
|
264
|
+
class PyremotePayloadRuntime:
|
265
|
+
input: ta.BinaryIO
|
266
|
+
output: ta.BinaryIO
|
267
|
+
main_src: str
|
268
|
+
options: PyremoteBootstrapOptions
|
269
|
+
env_info: PyremoteEnvInfo
|
196
270
|
|
197
271
|
|
198
|
-
def
|
199
|
-
|
272
|
+
def pyremote_bootstrap_finalize() -> PyremotePayloadRuntime:
|
273
|
+
# If json options var is not present we need to do initial finalization
|
274
|
+
if _PYREMOTE_BOOTSTRAP_OPTIONS_JSON_VAR not in os.environ:
|
275
|
+
# Read second copy of main src
|
276
|
+
r1 = os.fdopen(_PYREMOTE_BOOTSTRAP_SRC_FD, 'rb', 0)
|
277
|
+
main_src = r1.read().decode('utf-8')
|
278
|
+
r1.close()
|
279
|
+
|
280
|
+
# Reap boostrap child. Must be done after reading second copy of source because source may be too big to fit in
|
281
|
+
# a pipe at once.
|
282
|
+
os.waitpid(int(os.environ.pop(_PYREMOTE_BOOTSTRAP_CHILD_PID_VAR)), 0)
|
283
|
+
|
284
|
+
# Read options
|
285
|
+
options_json_len = struct.unpack('<I', os.read(_PYREMOTE_BOOTSTRAP_INPUT_FD, 4))[0]
|
286
|
+
if len(options_json := os.read(_PYREMOTE_BOOTSTRAP_INPUT_FD, options_json_len)) != options_json_len:
|
287
|
+
raise EOFError
|
288
|
+
options = PyremoteBootstrapOptions(**json.loads(options_json.decode('utf-8')))
|
289
|
+
|
290
|
+
# If debugging, re-exec as file
|
291
|
+
if options.debug:
|
292
|
+
# Write temp source file
|
293
|
+
import tempfile
|
294
|
+
tfd, tfn = tempfile.mkstemp('-pyremote.py')
|
295
|
+
os.write(tfd, main_src.encode('utf-8'))
|
296
|
+
os.close(tfd)
|
297
|
+
|
298
|
+
# Set json options var
|
299
|
+
os.environ[_PYREMOTE_BOOTSTRAP_OPTIONS_JSON_VAR] = options_json.decode('utf-8')
|
300
|
+
|
301
|
+
# Re-exec temp file
|
302
|
+
os.execl(os.environ[_PYREMOTE_BOOTSTRAP_ARGV0_VAR], sys.orig_argv[0], tfn)
|
200
303
|
|
201
|
-
|
304
|
+
else:
|
305
|
+
# Load options json var
|
306
|
+
options_json_str = os.environ.pop(_PYREMOTE_BOOTSTRAP_OPTIONS_JSON_VAR)
|
307
|
+
options = PyremoteBootstrapOptions(**json.loads(options_json_str))
|
202
308
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
except OSError:
|
207
|
-
os_login = None
|
309
|
+
# Read temp source file
|
310
|
+
with open(sys.orig_argv[1]) as sf:
|
311
|
+
main_src = sf.read()
|
208
312
|
|
209
|
-
|
210
|
-
|
211
|
-
sys_byteorder=sys.byteorder,
|
212
|
-
sys_defaultencoding=sys.getdefaultencoding(),
|
213
|
-
sys_exec_prefix=sys.exec_prefix,
|
214
|
-
sys_executable=sys.executable,
|
215
|
-
sys_implementation_name=sys.implementation.name,
|
216
|
-
sys_path=sys.path,
|
217
|
-
sys_platform=sys.platform,
|
218
|
-
sys_prefix=sys.prefix,
|
219
|
-
sys_version=sys.version,
|
220
|
-
sys_version_info=list(sys.version_info),
|
313
|
+
# Restore original argv0
|
314
|
+
sys.executable = os.environ.pop(_PYREMOTE_BOOTSTRAP_ARGV0_VAR)
|
221
315
|
|
222
|
-
|
223
|
-
|
224
|
-
platform_platform=platform.platform(),
|
225
|
-
platform_processor=platform.processor(),
|
226
|
-
platform_system=platform.system(),
|
227
|
-
platform_release=platform.release(),
|
228
|
-
platform_version=platform.version(),
|
316
|
+
# Write third ack
|
317
|
+
os.write(1, _PYREMOTE_BOOTSTRAP_ACK2)
|
229
318
|
|
230
|
-
|
319
|
+
# Write env info
|
320
|
+
env_info = _get_pyremote_env_info()
|
321
|
+
env_info_json = json.dumps(dc.asdict(env_info), indent=None, separators=(',', ':')) # noqa
|
322
|
+
os.write(1, struct.pack('<I', len(env_info_json)))
|
323
|
+
os.write(1, env_info_json.encode('utf-8'))
|
231
324
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
os_pid=os.getpid(),
|
238
|
-
os_ppid=os.getppid(),
|
239
|
-
os_uid=os_uid,
|
325
|
+
# Setup IO
|
326
|
+
input = os.fdopen(_PYREMOTE_BOOTSTRAP_INPUT_FD, 'rb', 0) # noqa
|
327
|
+
output = os.fdopen(os.dup(1), 'wb', 0) # noqa
|
328
|
+
os.dup2(nfd := os.open('/dev/null', os.O_WRONLY), 1)
|
329
|
+
os.close(nfd)
|
240
330
|
|
241
|
-
|
242
|
-
|
243
|
-
pw_gid=pw.pw_gid,
|
244
|
-
pw_gecos=pw.pw_gecos,
|
245
|
-
pw_dir=pw.pw_dir,
|
246
|
-
pw_shell=pw.pw_shell,
|
331
|
+
# Write fourth ack
|
332
|
+
output.write(_PYREMOTE_BOOTSTRAP_ACK3)
|
247
333
|
|
248
|
-
|
334
|
+
# Return
|
335
|
+
return PyremotePayloadRuntime(
|
336
|
+
input=input,
|
337
|
+
output=output,
|
338
|
+
main_src=main_src,
|
339
|
+
options=options,
|
340
|
+
env_info=env_info,
|
249
341
|
)
|
250
342
|
|
251
343
|
|
@@ -253,12 +345,15 @@ def _get_pyremote_env_info() -> PyremoteEnvInfo:
|
|
253
345
|
|
254
346
|
|
255
347
|
class PyremoteBootstrapDriver:
|
256
|
-
def __init__(self, main_src: str) -> None:
|
348
|
+
def __init__(self, main_src: str, options: PyremoteBootstrapOptions = PyremoteBootstrapOptions()) -> None:
|
257
349
|
super().__init__()
|
258
350
|
|
259
351
|
self._main_src = main_src
|
260
352
|
self._main_z = zlib.compress(main_src.encode('utf-8'))
|
261
353
|
|
354
|
+
self._options = options
|
355
|
+
self._options_json = json.dumps(dc.asdict(options), indent=None, separators=(',', ':')).encode('utf-8') # noqa
|
356
|
+
|
262
357
|
#
|
263
358
|
|
264
359
|
@dc.dataclass(frozen=True)
|
@@ -278,7 +373,7 @@ class PyremoteBootstrapDriver:
|
|
278
373
|
env_info: PyremoteEnvInfo
|
279
374
|
|
280
375
|
def gen(self) -> ta.Generator[ta.Union[Read, Write], ta.Optional[bytes], Result]:
|
281
|
-
# Read first ack
|
376
|
+
# Read first ack (after fork)
|
282
377
|
yield from self._expect(_PYREMOTE_BOOTSTRAP_ACK0)
|
283
378
|
|
284
379
|
# Read pid
|
@@ -289,8 +384,14 @@ class PyremoteBootstrapDriver:
|
|
289
384
|
yield from self._write(struct.pack('<I', len(self._main_z)))
|
290
385
|
yield from self._write(self._main_z)
|
291
386
|
|
292
|
-
# Read second
|
387
|
+
# Read second ack (after writing src copies)
|
293
388
|
yield from self._expect(_PYREMOTE_BOOTSTRAP_ACK1)
|
389
|
+
|
390
|
+
# Write options
|
391
|
+
yield from self._write(struct.pack('<I', len(self._options_json)))
|
392
|
+
yield from self._write(self._options_json)
|
393
|
+
|
394
|
+
# Read third ack (after reaping child process)
|
294
395
|
yield from self._expect(_PYREMOTE_BOOTSTRAP_ACK2)
|
295
396
|
|
296
397
|
# Read env info
|
@@ -300,7 +401,7 @@ class PyremoteBootstrapDriver:
|
|
300
401
|
env_info_json = d.decode('utf-8')
|
301
402
|
env_info = PyremoteEnvInfo(**json.loads(env_info_json))
|
302
403
|
|
303
|
-
# Read fourth ack
|
404
|
+
# Read fourth ack (after finalization completed)
|
304
405
|
yield from self._expect(_PYREMOTE_BOOTSTRAP_ACK3)
|
305
406
|
|
306
407
|
# Return
|
@@ -343,61 +444,11 @@ class PyremoteBootstrapDriver:
|
|
343
444
|
return e.value
|
344
445
|
|
345
446
|
if isinstance(go, self.Read):
|
346
|
-
gi
|
447
|
+
if len(gi := stdout.read(go.sz)) != go.sz:
|
448
|
+
raise EOFError
|
347
449
|
elif isinstance(go, self.Write):
|
348
450
|
gi = None
|
349
451
|
stdin.write(go.d)
|
350
452
|
stdin.flush()
|
351
453
|
else:
|
352
454
|
raise TypeError(go)
|
353
|
-
|
354
|
-
|
355
|
-
##
|
356
|
-
|
357
|
-
|
358
|
-
@dc.dataclass(frozen=True)
|
359
|
-
class PyremotePayloadRuntime:
|
360
|
-
input: ta.BinaryIO
|
361
|
-
output: ta.BinaryIO
|
362
|
-
main_src: str
|
363
|
-
env_info: PyremoteEnvInfo
|
364
|
-
|
365
|
-
|
366
|
-
def pyremote_bootstrap_finalize() -> PyremotePayloadRuntime:
|
367
|
-
# Restore original argv0
|
368
|
-
sys.executable = os.environ.pop(_PYREMOTE_BOOTSTRAP_ARGV0_VAR)
|
369
|
-
|
370
|
-
# Read second copy of main src
|
371
|
-
r1 = os.fdopen(_PYREMOTE_BOOTSTRAP_SRC_FD, 'rb', 0)
|
372
|
-
main_src = r1.read().decode('utf-8')
|
373
|
-
r1.close()
|
374
|
-
|
375
|
-
# Reap boostrap child. Must be done after reading second copy of source because source may be too big to fit in a
|
376
|
-
# pipe at once.
|
377
|
-
os.waitpid(int(os.environ.pop(_PYREMOTE_BOOTSTRAP_CHILD_PID_VAR)), 0)
|
378
|
-
|
379
|
-
# Write third ack
|
380
|
-
os.write(1, _PYREMOTE_BOOTSTRAP_ACK2)
|
381
|
-
|
382
|
-
# Write env info
|
383
|
-
env_info = _get_pyremote_env_info()
|
384
|
-
env_info_json = json.dumps(dc.asdict(env_info), indent=None, separators=(',', ':')) # noqa
|
385
|
-
os.write(1, struct.pack('<I', len(env_info_json)))
|
386
|
-
os.write(1, env_info_json.encode('utf-8'))
|
387
|
-
|
388
|
-
# Setup IO
|
389
|
-
input = os.fdopen(_PYREMOTE_BOOTSTRAP_INPUT_FD, 'rb', 0) # noqa
|
390
|
-
output = os.fdopen(os.dup(1), 'wb', 0) # noqa
|
391
|
-
os.dup2(nfd := os.open('/dev/null', os.O_WRONLY), 1)
|
392
|
-
os.close(nfd)
|
393
|
-
|
394
|
-
# Write fourth ack
|
395
|
-
output.write(_PYREMOTE_BOOTSTRAP_ACK3)
|
396
|
-
|
397
|
-
# Return
|
398
|
-
return PyremotePayloadRuntime(
|
399
|
-
input=input,
|
400
|
-
output=output,
|
401
|
-
main_src=main_src,
|
402
|
-
env_info=env_info,
|
403
|
-
)
|
ominfra/scripts/supervisor.py
CHANGED
@@ -7382,7 +7382,7 @@ class ProcessImpl(Process):
|
|
7382
7382
|
self._backoff = 0 # backoff counter (to start_retries)
|
7383
7383
|
|
7384
7384
|
self._exitstatus: ta.Optional[Rc] = None # status attached to dead process by finish()
|
7385
|
-
self._spawn_err: ta.Optional[str] = None # error message attached by
|
7385
|
+
self._spawn_err: ta.Optional[str] = None # error message attached by _spawn() if any
|
7386
7386
|
|
7387
7387
|
#
|
7388
7388
|
|
@@ -7419,12 +7419,12 @@ class ProcessImpl(Process):
|
|
7419
7419
|
|
7420
7420
|
#
|
7421
7421
|
|
7422
|
-
def
|
7422
|
+
def _spawn(self) -> ta.Optional[Pid]:
|
7423
7423
|
if self.pid:
|
7424
7424
|
log.warning('process \'%s\' already running', self.name)
|
7425
7425
|
return None
|
7426
7426
|
|
7427
|
-
self.
|
7427
|
+
self._check_in_state(
|
7428
7428
|
ProcessState.EXITED,
|
7429
7429
|
ProcessState.FATAL,
|
7430
7430
|
ProcessState.BACKOFF,
|
@@ -7439,15 +7439,15 @@ class ProcessImpl(Process):
|
|
7439
7439
|
|
7440
7440
|
self._last_start = time.time()
|
7441
7441
|
|
7442
|
-
self.
|
7442
|
+
self._change_state(ProcessState.STARTING)
|
7443
7443
|
|
7444
7444
|
try:
|
7445
7445
|
sp = self._spawning.spawn()
|
7446
7446
|
except ProcessSpawnError as err:
|
7447
7447
|
log.exception('Spawn error')
|
7448
7448
|
self._spawn_err = err.args[0]
|
7449
|
-
self.
|
7450
|
-
self.
|
7449
|
+
self._check_in_state(ProcessState.STARTING)
|
7450
|
+
self._change_state(ProcessState.BACKOFF)
|
7451
7451
|
return None
|
7452
7452
|
|
7453
7453
|
log.info("Spawned: '%s' with pid %s", self.name, sp.pid)
|
@@ -7480,7 +7480,7 @@ class ProcessImpl(Process):
|
|
7480
7480
|
|
7481
7481
|
#
|
7482
7482
|
|
7483
|
-
def
|
7483
|
+
def _change_state(self, new_state: ProcessState, expected: bool = True) -> bool:
|
7484
7484
|
old_state = self._state
|
7485
7485
|
if new_state is old_state:
|
7486
7486
|
return False
|
@@ -7498,7 +7498,7 @@ class ProcessImpl(Process):
|
|
7498
7498
|
|
7499
7499
|
return True
|
7500
7500
|
|
7501
|
-
def
|
7501
|
+
def _check_in_state(self, *states: ProcessState) -> None:
|
7502
7502
|
if self._state not in states:
|
7503
7503
|
raise ProcessStateError(
|
7504
7504
|
f'Check failed for {self._config.name}: '
|
@@ -7507,7 +7507,7 @@ class ProcessImpl(Process):
|
|
7507
7507
|
|
7508
7508
|
#
|
7509
7509
|
|
7510
|
-
def _check_and_adjust_for_system_clock_rollback(self, test_time):
|
7510
|
+
def _check_and_adjust_for_system_clock_rollback(self, test_time: float) -> None:
|
7511
7511
|
"""
|
7512
7512
|
Check if system clock has rolled backward beyond test_time. If so, set affected timestamps to test_time.
|
7513
7513
|
"""
|
@@ -7551,8 +7551,8 @@ class ProcessImpl(Process):
|
|
7551
7551
|
self._delay = 0
|
7552
7552
|
self._backoff = 0
|
7553
7553
|
self._system_stop = True
|
7554
|
-
self.
|
7555
|
-
self.
|
7554
|
+
self._check_in_state(ProcessState.BACKOFF)
|
7555
|
+
self._change_state(ProcessState.FATAL)
|
7556
7556
|
|
7557
7557
|
def kill(self, sig: int) -> ta.Optional[str]:
|
7558
7558
|
"""
|
@@ -7562,6 +7562,7 @@ class ProcessImpl(Process):
|
|
7562
7562
|
Return None if the signal was sent, or an error message string if an error occurred or if the subprocess is not
|
7563
7563
|
running.
|
7564
7564
|
"""
|
7565
|
+
|
7565
7566
|
now = time.time()
|
7566
7567
|
|
7567
7568
|
# If the process is in BACKOFF and we want to stop or kill it, then BACKOFF -> STOPPED. This is needed because
|
@@ -7569,7 +7570,7 @@ class ProcessImpl(Process):
|
|
7569
7570
|
# blocked for a long time waiting for the retries.
|
7570
7571
|
if self._state == ProcessState.BACKOFF:
|
7571
7572
|
log.debug('Attempted to kill %s, which is in BACKOFF state.', self.name)
|
7572
|
-
self.
|
7573
|
+
self._change_state(ProcessState.STOPPED)
|
7573
7574
|
return None
|
7574
7575
|
|
7575
7576
|
args: tuple
|
@@ -7594,8 +7595,8 @@ class ProcessImpl(Process):
|
|
7594
7595
|
self._killing = True
|
7595
7596
|
self._delay = now + self._config.stop_wait_secs
|
7596
7597
|
# we will already be in the STOPPING state if we're doing a SIGKILL as a result of overrunning stop_wait_secs
|
7597
|
-
self.
|
7598
|
-
self.
|
7598
|
+
self._check_in_state(ProcessState.RUNNING, ProcessState.STARTING, ProcessState.STOPPING)
|
7599
|
+
self._change_state(ProcessState.STOPPING)
|
7599
7600
|
|
7600
7601
|
kpid = int(self.pid)
|
7601
7602
|
if kill_as_group:
|
@@ -7616,7 +7617,7 @@ class ProcessImpl(Process):
|
|
7616
7617
|
tb = traceback.format_exc()
|
7617
7618
|
fmt, args = 'unknown problem killing %s (%s):%s', (self.name, self.pid, tb)
|
7618
7619
|
log.critical(fmt, *args)
|
7619
|
-
self.
|
7620
|
+
self._change_state(ProcessState.UNKNOWN)
|
7620
7621
|
self._killing = False
|
7621
7622
|
self._delay = 0
|
7622
7623
|
return fmt % args
|
@@ -7638,7 +7639,7 @@ class ProcessImpl(Process):
|
|
7638
7639
|
|
7639
7640
|
log.debug('sending %s (pid %s) sig %s', self.name, self.pid, sig_name(sig))
|
7640
7641
|
|
7641
|
-
self.
|
7642
|
+
self._check_in_state(ProcessState.RUNNING, ProcessState.STARTING, ProcessState.STOPPING)
|
7642
7643
|
|
7643
7644
|
try:
|
7644
7645
|
try:
|
@@ -7659,7 +7660,7 @@ class ProcessImpl(Process):
|
|
7659
7660
|
tb = traceback.format_exc()
|
7660
7661
|
fmt, args = 'unknown problem sending sig %s (%s):%s', (self.name, self.pid, tb)
|
7661
7662
|
log.critical(fmt, *args)
|
7662
|
-
self.
|
7663
|
+
self._change_state(ProcessState.UNKNOWN)
|
7663
7664
|
return fmt % args
|
7664
7665
|
|
7665
7666
|
return None
|
@@ -7697,8 +7698,8 @@ class ProcessImpl(Process):
|
|
7697
7698
|
self._exitstatus = Rc(es)
|
7698
7699
|
|
7699
7700
|
fmt, args = 'stopped: %s (%s)', (self.name, msg)
|
7700
|
-
self.
|
7701
|
-
self.
|
7701
|
+
self._check_in_state(ProcessState.STOPPING)
|
7702
|
+
self._change_state(ProcessState.STOPPED)
|
7702
7703
|
if exit_expected:
|
7703
7704
|
log.info(fmt, *args)
|
7704
7705
|
else:
|
@@ -7708,8 +7709,8 @@ class ProcessImpl(Process):
|
|
7708
7709
|
# the program did not stay up long enough to make it to RUNNING implies STARTING -> BACKOFF
|
7709
7710
|
self._exitstatus = None
|
7710
7711
|
self._spawn_err = 'Exited too quickly (process log may have details)'
|
7711
|
-
self.
|
7712
|
-
self.
|
7712
|
+
self._check_in_state(ProcessState.STARTING)
|
7713
|
+
self._change_state(ProcessState.BACKOFF)
|
7713
7714
|
log.warning('exited: %s (%s)', self.name, msg + '; not expected')
|
7714
7715
|
|
7715
7716
|
else:
|
@@ -7722,18 +7723,18 @@ class ProcessImpl(Process):
|
|
7722
7723
|
# if the process was STARTING but a system time change causes self.last_start to be in the future, the
|
7723
7724
|
# normal STARTING->RUNNING transition can be subverted so we perform the transition here.
|
7724
7725
|
if self._state == ProcessState.STARTING:
|
7725
|
-
self.
|
7726
|
+
self._change_state(ProcessState.RUNNING)
|
7726
7727
|
|
7727
|
-
self.
|
7728
|
+
self._check_in_state(ProcessState.RUNNING)
|
7728
7729
|
|
7729
7730
|
if exit_expected:
|
7730
7731
|
# expected exit code
|
7731
|
-
self.
|
7732
|
+
self._change_state(ProcessState.EXITED, expected=True)
|
7732
7733
|
log.info('exited: %s (%s)', self.name, msg + '; expected')
|
7733
7734
|
else:
|
7734
7735
|
# unexpected exit code
|
7735
7736
|
self._spawn_err = f'Bad exit code {es}'
|
7736
|
-
self.
|
7737
|
+
self._change_state(ProcessState.EXITED, expected=False)
|
7737
7738
|
log.warning('exited: %s (%s)', self.name, msg + '; not expected')
|
7738
7739
|
|
7739
7740
|
self._pid = Pid(0)
|
@@ -7753,21 +7754,21 @@ class ProcessImpl(Process):
|
|
7753
7754
|
if self._config.auto_restart:
|
7754
7755
|
if self._config.auto_restart is RestartUnconditionally:
|
7755
7756
|
# EXITED -> STARTING
|
7756
|
-
self.
|
7757
|
+
self._spawn()
|
7757
7758
|
elif self._exitstatus not in self._config.exitcodes:
|
7758
7759
|
# EXITED -> STARTING
|
7759
|
-
self.
|
7760
|
+
self._spawn()
|
7760
7761
|
|
7761
7762
|
elif state == ProcessState.STOPPED and not self._last_start:
|
7762
7763
|
if self._config.auto_start:
|
7763
7764
|
# STOPPED -> STARTING
|
7764
|
-
self.
|
7765
|
+
self._spawn()
|
7765
7766
|
|
7766
7767
|
elif state == ProcessState.BACKOFF:
|
7767
7768
|
if self._backoff <= self._config.start_retries:
|
7768
7769
|
if now > self._delay:
|
7769
7770
|
# BACKOFF -> STARTING
|
7770
|
-
self.
|
7771
|
+
self._spawn()
|
7771
7772
|
|
7772
7773
|
if state == ProcessState.STARTING:
|
7773
7774
|
if now - self._last_start > self._config.start_secs:
|
@@ -7775,8 +7776,8 @@ class ProcessImpl(Process):
|
|
7775
7776
|
# proc.config.start_secs,
|
7776
7777
|
self._delay = 0
|
7777
7778
|
self._backoff = 0
|
7778
|
-
self.
|
7779
|
-
self.
|
7779
|
+
self._check_in_state(ProcessState.STARTING)
|
7780
|
+
self._change_state(ProcessState.RUNNING)
|
7780
7781
|
msg = ('entered RUNNING state, process has stayed up for > than %s seconds (start_secs)' % self._config.start_secs) # noqa
|
7781
7782
|
log.info('success: %s %s', self.name, msg)
|
7782
7783
|
|