half-sample 0.1.8__tar.gz → 0.1.10__tar.gz
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.
Potentially problematic release.
This version of half-sample might be problematic. Click here for more details.
- {half-sample-0.1.8 → half-sample-0.1.10}/PKG-INFO +1 -1
- {half-sample-0.1.8 → half-sample-0.1.10}/cpp_build/sample.exe +0 -0
- {half-sample-0.1.8 → half-sample-0.1.10}/half_sample.egg-info/PKG-INFO +1 -1
- {half-sample-0.1.8 → half-sample-0.1.10}/half_sample.egg-info/SOURCES.txt +1 -0
- {half-sample-0.1.8 → half-sample-0.1.10}/sample/__init__.py +1 -0
- half-sample-0.1.10/sample/process.py +154 -0
- {half-sample-0.1.8 → half-sample-0.1.10}/sample/sample.py +6 -4
- {half-sample-0.1.8 → half-sample-0.1.10}/setup.py +1 -1
- {half-sample-0.1.8 → half-sample-0.1.10}/README.md +0 -0
- {half-sample-0.1.8 → half-sample-0.1.10}/half_sample.egg-info/dependency_links.txt +0 -0
- {half-sample-0.1.8 → half-sample-0.1.10}/half_sample.egg-info/top_level.txt +0 -0
- {half-sample-0.1.8 → half-sample-0.1.10}/sample/result.py +0 -0
- {half-sample-0.1.8 → half-sample-0.1.10}/setup.cfg +0 -0
|
Binary file
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
import threading
|
|
5
|
+
import subprocess
|
|
6
|
+
import multiprocessing
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger('library')
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def process(target, args=(), finished=None):
|
|
13
|
+
p = multiprocessing.Process(target=target, args=args)
|
|
14
|
+
p.start()
|
|
15
|
+
|
|
16
|
+
def check_finished():
|
|
17
|
+
p.join()
|
|
18
|
+
finished()
|
|
19
|
+
|
|
20
|
+
threading.Thread(target=check_finished).start()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Process:
|
|
24
|
+
def __init__(self, cmd):
|
|
25
|
+
self.cmd = cmd
|
|
26
|
+
self.p = None
|
|
27
|
+
self._max_retries = 1
|
|
28
|
+
self._start_process()
|
|
29
|
+
|
|
30
|
+
def set_max_retry(self, max_retries):
|
|
31
|
+
self._max_retries = max_retries
|
|
32
|
+
|
|
33
|
+
def get_max_retry(self):
|
|
34
|
+
return self._max_retries
|
|
35
|
+
|
|
36
|
+
def _start_process(self):
|
|
37
|
+
"""启动或重启子进程"""
|
|
38
|
+
if self.p is not None:
|
|
39
|
+
# 如果旧进程还在运行,先终止它
|
|
40
|
+
try:
|
|
41
|
+
if self.p.poll() is None: # 进程仍在运行
|
|
42
|
+
self.p.terminate()
|
|
43
|
+
self.p.wait(timeout=3)
|
|
44
|
+
except Exception as e:
|
|
45
|
+
logger.warning(f"failed to poll: {e}")
|
|
46
|
+
try:
|
|
47
|
+
self.p.kill()
|
|
48
|
+
self.p.wait()
|
|
49
|
+
except Exception as e:
|
|
50
|
+
logger.warning(f"failed to kill: {e}")
|
|
51
|
+
pass
|
|
52
|
+
# 启动新进程
|
|
53
|
+
self.p = subprocess.Popen(self.cmd,
|
|
54
|
+
shell=False,
|
|
55
|
+
creationflags=0x00000008 if sys.platform == "win32" else 0,
|
|
56
|
+
stdin=subprocess.PIPE,
|
|
57
|
+
stdout=subprocess.PIPE,
|
|
58
|
+
stderr=subprocess.PIPE)
|
|
59
|
+
|
|
60
|
+
def _is_process_alive(self):
|
|
61
|
+
"""检查进程是否存活"""
|
|
62
|
+
if self.p is None:
|
|
63
|
+
return False
|
|
64
|
+
return self.p.poll() is None
|
|
65
|
+
|
|
66
|
+
def _ensure_process_alive(self):
|
|
67
|
+
"""确保进程存活,如果崩溃则重启"""
|
|
68
|
+
if not self._is_process_alive():
|
|
69
|
+
logger.warning(f"subprocess [{self.cmd}] crash, restarting...")
|
|
70
|
+
self._start_process()
|
|
71
|
+
logger.warning(f"subprocess [{self.cmd}] restart completed")
|
|
72
|
+
|
|
73
|
+
def write_line(self, line: str):
|
|
74
|
+
"""写入一行数据,自动处理进程重启"""
|
|
75
|
+
for attempt in range(self._max_retries):
|
|
76
|
+
try:
|
|
77
|
+
self._ensure_process_alive()
|
|
78
|
+
self.p.stdin.write(line.encode())
|
|
79
|
+
self.p.stdin.write('\n'.encode())
|
|
80
|
+
self.p.stdin.flush()
|
|
81
|
+
return # 成功写入,退出重试循环
|
|
82
|
+
except (BrokenPipeError, OSError) as e:
|
|
83
|
+
if attempt < self._max_retries - 1:
|
|
84
|
+
logger.warning(f"write line failed (attempted {attempt + 1}/{self._max_retries}): {e}")
|
|
85
|
+
self._start_process() # 重启进程
|
|
86
|
+
time.sleep(0.1) # 短暂等待新进程启动
|
|
87
|
+
else:
|
|
88
|
+
raise RuntimeError(f"无法向子进程写入数据,已重试{self._max_retries}次: {e}")
|
|
89
|
+
|
|
90
|
+
def read_line(self) -> str:
|
|
91
|
+
"""读取一行数据,自动处理进程重启"""
|
|
92
|
+
for attempt in range(self._max_retries):
|
|
93
|
+
try:
|
|
94
|
+
self._ensure_process_alive()
|
|
95
|
+
line = self.p.stdout.readline()
|
|
96
|
+
if not line: # EOF,进程可能已终止
|
|
97
|
+
if not self._is_process_alive():
|
|
98
|
+
raise RuntimeError("子进程已终止")
|
|
99
|
+
return ""
|
|
100
|
+
return line.decode().replace('\r\n', '').replace('\n', '')
|
|
101
|
+
except (OSError, RuntimeError) as e:
|
|
102
|
+
if attempt < self._max_retries - 1:
|
|
103
|
+
logger.warning(f"read line failed (attempted {attempt + 1}/{self._max_retries}): {e}")
|
|
104
|
+
self._start_process() # 重启进程
|
|
105
|
+
time.sleep(0.1) # 短暂等待新进程启动
|
|
106
|
+
else:
|
|
107
|
+
raise RuntimeError(f"无法从子进程读取数据,已重试{self._max_retries}次: {e}")
|
|
108
|
+
|
|
109
|
+
def read_until(self, until):
|
|
110
|
+
"""读取直到指定字符串,自动处理进程重启"""
|
|
111
|
+
lines = []
|
|
112
|
+
|
|
113
|
+
for attempt in range(self._max_retries):
|
|
114
|
+
try:
|
|
115
|
+
while True:
|
|
116
|
+
line = self.read_line()
|
|
117
|
+
if line == until:
|
|
118
|
+
return '\n'.join(lines)
|
|
119
|
+
elif not line:
|
|
120
|
+
break
|
|
121
|
+
else:
|
|
122
|
+
lines.append(line)
|
|
123
|
+
except (RuntimeError, OSError) as e:
|
|
124
|
+
if attempt < self._max_retries - 1:
|
|
125
|
+
logger.warning(f"read_until failed (attempted {attempt + 1}/{self._max_retries}): {e}")
|
|
126
|
+
self._start_process() # 重启进程
|
|
127
|
+
lines = [] # 清空已读取的行
|
|
128
|
+
time.sleep(0.1)
|
|
129
|
+
else:
|
|
130
|
+
raise RuntimeError(f"read_until 失败,已重试{self._max_retries}次: {e}")
|
|
131
|
+
|
|
132
|
+
return '\n'.join(lines)
|
|
133
|
+
|
|
134
|
+
def terminate(self):
|
|
135
|
+
"""终止子进程"""
|
|
136
|
+
if self.p is not None and self.p.poll() is None:
|
|
137
|
+
try:
|
|
138
|
+
self.p.terminate()
|
|
139
|
+
self.p.wait(timeout=3)
|
|
140
|
+
except subprocess.TimeoutExpired as e:
|
|
141
|
+
logger.warning(f"Timeout expired while trying to terminate [{self.cmd}]: {e}")
|
|
142
|
+
try:
|
|
143
|
+
self.p.kill()
|
|
144
|
+
self.p.wait()
|
|
145
|
+
except Exception as e:
|
|
146
|
+
logger.warning(f"Failed to kill [{self.cmd}]: {e}")
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logger.warning(f"Failed to terminate [{self.cmd}]: {e}")
|
|
149
|
+
|
|
150
|
+
def get_return_code(self):
|
|
151
|
+
"""获取进程返回码"""
|
|
152
|
+
if self.p is not None:
|
|
153
|
+
return self.p.poll()
|
|
154
|
+
return None
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import shutil
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
from . import Process
|
|
5
5
|
from . import Result
|
|
6
6
|
|
|
7
7
|
|
|
@@ -29,16 +29,16 @@ class Sampler:
|
|
|
29
29
|
raise self.Error('Sample Driver Not Found')
|
|
30
30
|
|
|
31
31
|
@property
|
|
32
|
-
def p(self) ->
|
|
32
|
+
def p(self) -> Process:
|
|
33
33
|
p = getattr(self, 'p_', None)
|
|
34
34
|
if p is not None:
|
|
35
35
|
return p
|
|
36
36
|
|
|
37
|
-
p =
|
|
37
|
+
p = Process(self.execution_path)
|
|
38
38
|
setattr(self, 'p_', p)
|
|
39
39
|
return p
|
|
40
40
|
|
|
41
|
-
def communicate(self, command: str, executor:
|
|
41
|
+
def communicate(self, command: str, executor: Process = None) -> Result:
|
|
42
42
|
result = Result()
|
|
43
43
|
try:
|
|
44
44
|
executor = executor or self.p
|
|
@@ -78,6 +78,8 @@ class Sampler:
|
|
|
78
78
|
def query(self) -> Result:
|
|
79
79
|
result = self.communicate("to_query")
|
|
80
80
|
result.process()
|
|
81
|
+
if not result.success and result.message == "success":
|
|
82
|
+
result.message = "error_undefined"
|
|
81
83
|
return result
|
|
82
84
|
|
|
83
85
|
class Error(RuntimeError):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|