ladyrick 0.5.4__py3-none-any.whl → 0.5.6__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.
- ladyrick/cli/multi_ssh.py +55 -22
- {ladyrick-0.5.4.dist-info → ladyrick-0.5.6.dist-info}/METADATA +1 -1
- {ladyrick-0.5.4.dist-info → ladyrick-0.5.6.dist-info}/RECORD +7 -7
- {ladyrick-0.5.4.dist-info → ladyrick-0.5.6.dist-info}/WHEEL +0 -0
- {ladyrick-0.5.4.dist-info → ladyrick-0.5.6.dist-info}/entry_points.txt +0 -0
- {ladyrick-0.5.4.dist-info → ladyrick-0.5.6.dist-info}/licenses/LICENSE +0 -0
- {ladyrick-0.5.4.dist-info → ladyrick-0.5.6.dist-info}/top_level.txt +0 -0
ladyrick/cli/multi_ssh.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import json
|
2
2
|
import os
|
3
|
+
import re
|
3
4
|
import select
|
4
5
|
import signal
|
5
6
|
import subprocess
|
@@ -110,15 +111,20 @@ class Host:
|
|
110
111
|
|
111
112
|
|
112
113
|
class RemoteExecutor:
|
113
|
-
def __init__(self, host: Host, command: list[str]):
|
114
|
+
def __init__(self, host: Host, command: list[str], envs: dict | None = None):
|
114
115
|
self.host = host
|
115
116
|
self.command = command
|
116
117
|
self.envs = {}
|
118
|
+
if envs:
|
119
|
+
for k, v in envs.items():
|
120
|
+
if not re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", k):
|
121
|
+
raise ValueError(f"invalid env name: {k!r}")
|
122
|
+
self.envs[k] = v
|
117
123
|
self.process = None
|
118
124
|
|
119
125
|
@classmethod
|
120
126
|
def make_ssh_cmd(cls, host: Host, cmd: str):
|
121
|
-
opts = ["/usr/bin/env", "ssh", "-
|
127
|
+
opts = ["/usr/bin/env", "ssh", "-T", "-oStrictHostKeyChecking=no"]
|
122
128
|
if host.config_file is not None:
|
123
129
|
opts.append(f"-F{host.config_file}")
|
124
130
|
if host.User is not None:
|
@@ -134,6 +140,7 @@ class RemoteExecutor:
|
|
134
140
|
return opts
|
135
141
|
|
136
142
|
def start(self):
|
143
|
+
assert self.process is None
|
137
144
|
code = pathlib.Path(__file__).read_text().split("# ----- remote_head end ----- #")[0].strip()
|
138
145
|
remote_cmd = shlex.join(
|
139
146
|
[
|
@@ -185,18 +192,27 @@ class RemoteExecutor:
|
|
185
192
|
e.envs["RANK"] = str(i)
|
186
193
|
|
187
194
|
def send_signal(self, sig):
|
188
|
-
|
195
|
+
assert self.process is not None
|
196
|
+
if self.process.poll() is None and self.process.stdin and not self.process.stdin.closed:
|
189
197
|
sig_name = signal.Signals(sig).name
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
198
|
+
log(f"writing to stdin: SIGNAL {sig_name}")
|
199
|
+
try:
|
200
|
+
self.process.stdin.write(f"SIGNAL {sig_name}\n".encode())
|
201
|
+
self.process.stdin.flush()
|
202
|
+
except (BrokenPipeError, OSError) as e:
|
203
|
+
log(e)
|
204
|
+
|
205
|
+
def terminate(self):
|
206
|
+
if self.process is not None and self.process.poll() is None:
|
207
|
+
log("terminate RemoteExecutor")
|
208
|
+
self.process.terminate()
|
209
|
+
|
210
|
+
def poll(self):
|
211
|
+
assert self.process is not None
|
212
|
+
return self.process.poll()
|
197
213
|
|
198
214
|
|
199
|
-
def signal_repeat_checker(sig_to_check,
|
215
|
+
def signal_repeat_checker(sig_to_check, duration: float):
|
200
216
|
last_int_signal_time = []
|
201
217
|
|
202
218
|
def checker(sig: signal.Signals):
|
@@ -206,11 +222,8 @@ def signal_repeat_checker(sig_to_check, count, duration):
|
|
206
222
|
threadhold = cur_time - duration
|
207
223
|
last_int_signal_time = [t for t in last_int_signal_time if t >= threadhold]
|
208
224
|
last_int_signal_time.append(cur_time)
|
209
|
-
|
210
|
-
|
211
|
-
log(f"received {sig_to_check.name} for {count} times or more in {duration} second(s)")
|
212
|
-
return True
|
213
|
-
return False
|
225
|
+
return len(last_int_signal_time)
|
226
|
+
return 0
|
214
227
|
|
215
228
|
return checker
|
216
229
|
|
@@ -223,6 +236,7 @@ def main():
|
|
223
236
|
parser.add_argument("-l", type=str, help="ssh login User")
|
224
237
|
parser.add_argument("-o", type=str, action="append", help="ssh options")
|
225
238
|
parser.add_argument("-F", type=str, help="ssh config file")
|
239
|
+
parser.add_argument("-e", "--env", type=str, action="append", help="extra envs")
|
226
240
|
parser.add_argument("--hosts-config", type=str, action="append", help="hosts config string. order is 2")
|
227
241
|
parser.add_argument("--hosts-config-file", type=str, action="append", help="hosts config file. order is 3")
|
228
242
|
parser.add_argument("--help", action="help", default=argparse.SUPPRESS, help="show this help message and exit")
|
@@ -263,28 +277,47 @@ def main():
|
|
263
277
|
parser.print_help()
|
264
278
|
sys.exit(1)
|
265
279
|
|
266
|
-
|
280
|
+
envs = {}
|
281
|
+
if args.env:
|
282
|
+
for e in args.env:
|
283
|
+
p = e.split("=", 1)
|
284
|
+
if len(p) == 1:
|
285
|
+
p.append("")
|
286
|
+
envs[p[0]] = p[1]
|
287
|
+
|
288
|
+
executors = [RemoteExecutor(host, args.cmd, envs) for host in hosts]
|
267
289
|
|
268
290
|
RemoteExecutor.set_envs(executors)
|
269
291
|
|
270
292
|
for executor in executors:
|
271
293
|
executor.start()
|
272
294
|
|
273
|
-
|
295
|
+
import rich
|
296
|
+
|
297
|
+
checker = signal_repeat_checker(signal.SIGINT, duration=1)
|
274
298
|
|
275
299
|
def handle_signal(sig, frame):
|
276
|
-
|
300
|
+
log(f"received signal {sig}")
|
301
|
+
sig_count = checker(sig)
|
302
|
+
if sig_count >= 3:
|
277
303
|
sig = signal.SIGUSR2
|
278
|
-
|
304
|
+
if sig_count == 3:
|
305
|
+
rich.print("\n[bold magenta]Can't wait. Try to froce kill remote processes...[/bold magenta]")
|
279
306
|
else:
|
280
|
-
|
307
|
+
rich.print(
|
308
|
+
f"\n[bold green]Received {signal.Signals(sig).name}, forwarding to remote processes...[/bold green]"
|
309
|
+
)
|
281
310
|
for executor in executors:
|
282
311
|
executor.send_signal(sig)
|
312
|
+
if sig_count >= 4:
|
313
|
+
rich.print("\n[bold red]Really Can't wait!!! Froce kill local processes and exiting right now![/bold red]")
|
314
|
+
for executor in executors:
|
315
|
+
executor.terminate()
|
283
316
|
|
284
317
|
for sig in [signal.SIGHUP, signal.SIGINT, signal.SIGTERM, signal.SIGUSR1, signal.SIGUSR2]:
|
285
318
|
signal.signal(sig, handle_signal)
|
286
319
|
|
287
|
-
while any(e.
|
320
|
+
while any([e.poll() is None for e in executors if e.process]):
|
288
321
|
time.sleep(0.5)
|
289
322
|
log("finished")
|
290
323
|
|
@@ -10,7 +10,7 @@ ladyrick/typing.py,sha256=YQeApe63dk7yL4NS5ytlR6v3dLCii2-qsXNlUvjK-zw,203
|
|
10
10
|
ladyrick/utils.py,sha256=jRRaqC6kNbCJPGeE0YisFgis-wiuINLik1mcUQtytow,608
|
11
11
|
ladyrick/vars.py,sha256=VbFh2u7XybUaBuiYEXBa4sOmoS99vc2AIXdYLBh8vjk,3763
|
12
12
|
ladyrick/cli/calc.py,sha256=5_UhSSaL_K9FBCHg3zuCk41CHJXy3QAh__qCabbffQY,1438
|
13
|
-
ladyrick/cli/multi_ssh.py,sha256=
|
13
|
+
ladyrick/cli/multi_ssh.py,sha256=d_7zPvmfVUQZVcxhCFj37ktTOb0-AzxSEr0z6ipMY9U,10403
|
14
14
|
ladyrick/cli/psf.py,sha256=JLk3gbPn7E3uuPBbzGvLgJmFQlilA6zg_Xlg7xW5jik,1146
|
15
15
|
ladyrick/cli/tee.py,sha256=UMJxSJLOEfbV43auVKRTIJ5ZAMAkAfj8byiFLk5PUHE,3579
|
16
16
|
ladyrick/cli/test_args.py,sha256=f5sUPDlcf6nbNf6UfLwZQI5g5LN8wlFBQZ10GLw22cg,212
|
@@ -21,9 +21,9 @@ ladyrick/patch/rich_print.py,sha256=z3Ea1VCunXZvNvEDFHpoyWc8ydINmh-gOIJ1ssscs6s,
|
|
21
21
|
ladyrick/patch/python/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
22
|
ladyrick/patch/python/__main__.py,sha256=BAGy1phd26WWcGM9TKHbqIpeZliVofopBndtMIPtDQ0,651
|
23
23
|
ladyrick/patch/python/usercustomize.py,sha256=8mYpcZ8p-l41fiSJue727n8cAmcEmUktObDYZDdLJfs,218
|
24
|
-
ladyrick-0.5.
|
25
|
-
ladyrick-0.5.
|
26
|
-
ladyrick-0.5.
|
27
|
-
ladyrick-0.5.
|
28
|
-
ladyrick-0.5.
|
29
|
-
ladyrick-0.5.
|
24
|
+
ladyrick-0.5.6.dist-info/licenses/LICENSE,sha256=EeNAFxYAOYEmo2YEM7Zk5Oknq4RI0XMAbk4Rgoem6fs,1065
|
25
|
+
ladyrick-0.5.6.dist-info/METADATA,sha256=s-vKvkA34C7DS3PXt2QygfVzhmBxSJC42bawDe_QC8Q,883
|
26
|
+
ladyrick-0.5.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
27
|
+
ladyrick-0.5.6.dist-info/entry_points.txt,sha256=28kidI1OCOAi7S1aHBr8veak49VNZ0tWFvf-Ty8UmSU,225
|
28
|
+
ladyrick-0.5.6.dist-info/top_level.txt,sha256=RIC3-Jty2qzLYXSOr7fOu1loTwlMU9cF6MFeGIROxWU,9
|
29
|
+
ladyrick-0.5.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|