pyconstrobe 0.1__tar.gz → 1.0.2__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.
- {pyconstrobe-0.1 → pyconstrobe-1.0.2}/PKG-INFO +3 -2
- pyconstrobe-1.0.2/pyconstrobe/__init__.py +8 -0
- pyconstrobe-1.0.2/pyconstrobe/hlaconstrobe.py +144 -0
- pyconstrobe-1.0.2/pyconstrobe/process_manager.py +133 -0
- {pyconstrobe-0.1 → pyconstrobe-1.0.2}/pyconstrobe.egg-info/PKG-INFO +3 -2
- {pyconstrobe-0.1 → pyconstrobe-1.0.2}/pyconstrobe.egg-info/SOURCES.txt +1 -2
- {pyconstrobe-0.1 → pyconstrobe-1.0.2}/pyconstrobe.egg-info/top_level.txt +0 -1
- {pyconstrobe-0.1 → pyconstrobe-1.0.2}/setup.py +8 -1
- pyconstrobe-0.1/csPy/__init__.py +0 -6
- pyconstrobe-0.1/csPy/process_manager.py +0 -67
- pyconstrobe-0.1/pyconstrobe/__init__.py +0 -6
- pyconstrobe-0.1/pyconstrobe/process_manager.py +0 -67
- {pyconstrobe-0.1 → pyconstrobe-1.0.2}/LICENSE +0 -0
- {pyconstrobe-0.1 → pyconstrobe-1.0.2}/README.md +0 -0
- {pyconstrobe-0.1 → pyconstrobe-1.0.2}/pyconstrobe.egg-info/dependency_links.txt +0 -0
- {pyconstrobe-0.1 → pyconstrobe-1.0.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pyconstrobe
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: This package is used to automate the DES application ConStrobe.
|
|
5
5
|
Author: Joseph Louis
|
|
6
6
|
Author-email: joseph.louis@oregonstate.edu
|
|
@@ -11,6 +11,7 @@ Dynamic: author
|
|
|
11
11
|
Dynamic: author-email
|
|
12
12
|
Dynamic: description
|
|
13
13
|
Dynamic: description-content-type
|
|
14
|
+
Dynamic: license-file
|
|
14
15
|
Dynamic: requires-python
|
|
15
16
|
Dynamic: summary
|
|
16
17
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Importing the ProcessManager class for easy access
|
|
2
|
+
from .process_manager import ProcessManager
|
|
3
|
+
from .hlaconstrobe import run_federation, send_control, graceful_shutdown, get_results
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# You can add any other initializations or imports here
|
|
7
|
+
__version__ = "1.0.2"
|
|
8
|
+
__all__ = ['ProcessManager', 'run_federation','send_control', 'graceful_shutdown', 'get_results']
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import threading
|
|
3
|
+
import time
|
|
4
|
+
import os
|
|
5
|
+
import signal
|
|
6
|
+
import queue
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
from pyconstrobe import ProcessManager
|
|
10
|
+
|
|
11
|
+
FEDERATION_NAME = "SimulationFederation"
|
|
12
|
+
rtig_proc = None
|
|
13
|
+
listener_proc = None
|
|
14
|
+
listener_thread = None
|
|
15
|
+
shutdown_now = threading.Event()
|
|
16
|
+
fedhandles = {}
|
|
17
|
+
build_mode = os.getenv('BUILD_MODE', 'Release')
|
|
18
|
+
if build_mode=='Debug':
|
|
19
|
+
LISTENER_PATH = "C:\\dev\\tools\\certi\\ListenerFederate\\x64\\Debug\\ListenerFederate.exe"
|
|
20
|
+
else:
|
|
21
|
+
LISTENER_PATH = "C:\\Program Files\\constrobe\\ListenerFederate.exe";
|
|
22
|
+
#LISTENER_PATH = "C:\\dev\\source\\constrobe\\deploy\\ListenerFederate.exe"
|
|
23
|
+
def start_rtig():
|
|
24
|
+
global rtig_proc
|
|
25
|
+
print("Starting rtig...")
|
|
26
|
+
rtig_path = "C:\\Program Files\\constrobe\\rtig.exe"
|
|
27
|
+
subprocess.run(
|
|
28
|
+
["taskkill", "/F", "/IM", "rtig.exe"],
|
|
29
|
+
stdout=subprocess.DEVNULL,
|
|
30
|
+
stderr=subprocess.DEVNULL
|
|
31
|
+
)
|
|
32
|
+
time.sleep(0.5)
|
|
33
|
+
rtig_proc = subprocess.Popen(
|
|
34
|
+
[rtig_path, "-v", "2", "-l", "127.0.0.1"],
|
|
35
|
+
stdout=subprocess.PIPE,
|
|
36
|
+
stderr=subprocess.STDOUT,
|
|
37
|
+
text=True,
|
|
38
|
+
encoding="utf-8",
|
|
39
|
+
bufsize=1
|
|
40
|
+
)
|
|
41
|
+
while True:
|
|
42
|
+
line = rtig_proc.stdout.readline()
|
|
43
|
+
if not line:
|
|
44
|
+
raise RuntimeError("RTIG exited before becoming ready.")
|
|
45
|
+
print("[RTIG]", line.rstrip())
|
|
46
|
+
if "RTIG up and running" in line:
|
|
47
|
+
break
|
|
48
|
+
if ("aborted" in line or
|
|
49
|
+
"exiting" in line or
|
|
50
|
+
"Network Error" in line):
|
|
51
|
+
raise RuntimeError("RTIG failed to start (see output above).")
|
|
52
|
+
def launch_listener(num_feds,handle_new_line,logging = True):
|
|
53
|
+
global listener_proc, listener_thread
|
|
54
|
+
listener_proc = subprocess.Popen(
|
|
55
|
+
[LISTENER_PATH, str(num_feds),
|
|
56
|
+
"true" if logging else "false"],
|
|
57
|
+
stdin=subprocess.PIPE,
|
|
58
|
+
stdout=subprocess.PIPE,
|
|
59
|
+
stderr=subprocess.STDOUT,
|
|
60
|
+
text=True,
|
|
61
|
+
encoding="utf-8",
|
|
62
|
+
bufsize=1,
|
|
63
|
+
)
|
|
64
|
+
listener_thread = threading.Thread(target=read_stdout, args=(listener_proc, handle_new_line, logging), daemon=True)
|
|
65
|
+
listener_thread.start()
|
|
66
|
+
def read_stdout(proc, callback, logging):
|
|
67
|
+
for line in proc.stdout:
|
|
68
|
+
if line:
|
|
69
|
+
if "|" in line:
|
|
70
|
+
print("[HLA]",line.strip())
|
|
71
|
+
fed, msg = line.split("|", 1)
|
|
72
|
+
callback(fed,msg.strip())
|
|
73
|
+
else:
|
|
74
|
+
if logging:
|
|
75
|
+
print("[LOG]",line.strip())
|
|
76
|
+
def send_control(line):
|
|
77
|
+
try:
|
|
78
|
+
if listener_proc.stdin:
|
|
79
|
+
listener_proc.stdin.write(line + "\n")
|
|
80
|
+
listener_proc.stdin.flush()
|
|
81
|
+
except Exception:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
def startSimulation(fedName, modelName,pythoncallback=None,logging=None):
|
|
85
|
+
manager = ProcessManager(pythoncallback,fedName,logging)
|
|
86
|
+
full_path=os.path.join(os.getcwd(),modelName)
|
|
87
|
+
message = f"LOAD {full_path};"
|
|
88
|
+
manager.write_message(message)
|
|
89
|
+
message = f"STARTFEDERATE {fedName};"
|
|
90
|
+
manager.write_message(message)
|
|
91
|
+
return manager
|
|
92
|
+
def run_federation(simfederates,listenerCallback,logging=True):
|
|
93
|
+
start_rtig()
|
|
94
|
+
launch_listener(len(simfederates),listenerCallback, logging)
|
|
95
|
+
for fedName, modelName, callback in simfederates:
|
|
96
|
+
h = startSimulation(fedName, modelName, callback, logging)
|
|
97
|
+
message = f"RUNMODEL;"
|
|
98
|
+
h.write_message(message)
|
|
99
|
+
fedhandles[fedName] = h
|
|
100
|
+
try:
|
|
101
|
+
while not shutdown_now.is_set():
|
|
102
|
+
time.sleep(0.1)
|
|
103
|
+
finally:
|
|
104
|
+
print("All processes stopped. Exiting...")
|
|
105
|
+
def get_results():
|
|
106
|
+
for fedName, h in fedhandles.items():
|
|
107
|
+
h.write_message("GETRESULTS;")
|
|
108
|
+
def graceful_shutdown():
|
|
109
|
+
print("Graceful shutdown…")
|
|
110
|
+
send_control("CONTROLSIGNAL ALL ENDFEDERATE")
|
|
111
|
+
for h in fedhandles.values():
|
|
112
|
+
try:
|
|
113
|
+
h.write_message("CLOSE;")
|
|
114
|
+
except Exception:
|
|
115
|
+
pass
|
|
116
|
+
time.sleep(2)
|
|
117
|
+
for h in fedhandles.values():
|
|
118
|
+
try:
|
|
119
|
+
h.stop()
|
|
120
|
+
except Exception:
|
|
121
|
+
pass
|
|
122
|
+
global listener_proc
|
|
123
|
+
if listener_proc is not None:
|
|
124
|
+
try:
|
|
125
|
+
listener_proc.terminate()
|
|
126
|
+
listener_proc.wait(timeout=2)
|
|
127
|
+
except subprocess.TimeoutExpired:
|
|
128
|
+
listener_proc.kill()
|
|
129
|
+
listener_proc.wait()
|
|
130
|
+
except Exception:
|
|
131
|
+
pass
|
|
132
|
+
listener_proc = None
|
|
133
|
+
global rtig_proc
|
|
134
|
+
if rtig_proc is not None:
|
|
135
|
+
rtig_proc.terminate()
|
|
136
|
+
try:
|
|
137
|
+
rtig_proc.wait(timeout=2)
|
|
138
|
+
except subprocess.TimeoutExpired:
|
|
139
|
+
rtig_proc.kill()
|
|
140
|
+
rtig_proc.wait()
|
|
141
|
+
rtig_proc = None
|
|
142
|
+
shutdown_now.set()
|
|
143
|
+
|
|
144
|
+
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import threading
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
INSTALL_DIR = Path(r"C:\Program Files\ConStrobe")
|
|
9
|
+
|
|
10
|
+
def debug(msg,senderName = "Sim"):
|
|
11
|
+
build_mode = os.getenv('BUILD_MODE', 'Release')
|
|
12
|
+
if build_mode=='Debug':
|
|
13
|
+
sys.stderr.write(f"[PYCONSTROBE - {senderName}] {msg}\n")
|
|
14
|
+
sys.stderr.flush()
|
|
15
|
+
|
|
16
|
+
class ProcessManager:
|
|
17
|
+
def __init__(self, callback=None, name = "Sim", logging = True):
|
|
18
|
+
self.name = name
|
|
19
|
+
self.callback = callback
|
|
20
|
+
self.logging = logging
|
|
21
|
+
build_mode = os.getenv('BUILD_MODE', 'Release') # Default to 'release' if not set
|
|
22
|
+
if build_mode=='Debug':
|
|
23
|
+
path_to_exe = r"C:\\dev\\source\\constrobe\\csApp\\Debug\\constrobe.exe"
|
|
24
|
+
else:
|
|
25
|
+
path_to_exe = r"C:\\Program Files\\constrobe\\constrobe.exe"
|
|
26
|
+
#path_to_exe = r"C:\\dev\\source\\constrobe\\deploy\\constrobe.exe"
|
|
27
|
+
self.process = subprocess.Popen(
|
|
28
|
+
[path_to_exe, '--from-python'],
|
|
29
|
+
stdin=subprocess.PIPE,
|
|
30
|
+
stdout=subprocess.PIPE,
|
|
31
|
+
stderr=subprocess.STDOUT,#PIPE,
|
|
32
|
+
text=True,
|
|
33
|
+
creationflags=subprocess.CREATE_NEW_CONSOLE
|
|
34
|
+
)
|
|
35
|
+
self.keep_reading = True # Flag to control the reading loop
|
|
36
|
+
self.finishRunFlag = False
|
|
37
|
+
self.gotTraceFlag = False
|
|
38
|
+
self.gotResultsFlag = False
|
|
39
|
+
self.reader_thread = threading.Thread(target=self.read_messages)
|
|
40
|
+
self.reader_thread.daemon = True # Allow thread to exit when main program exits
|
|
41
|
+
self.reader_thread.start()
|
|
42
|
+
def write_message(self, message):
|
|
43
|
+
#if self.logging:
|
|
44
|
+
# debug(f"Sending message: {message}",self.name)
|
|
45
|
+
self.process.stdin.write(message + "\n")
|
|
46
|
+
self.process.stdin.flush()
|
|
47
|
+
def read_messages(self):
|
|
48
|
+
while self.keep_reading:
|
|
49
|
+
response = self.process.stdout.readline().strip()
|
|
50
|
+
#if self.logging:
|
|
51
|
+
# debug(f"Received message: {response}",self.name)
|
|
52
|
+
if response:
|
|
53
|
+
parts = response.split(" ",1)
|
|
54
|
+
type = parts[0]
|
|
55
|
+
message = parts[1] if len(parts)>1 else ""
|
|
56
|
+
if type == "FINISHED_RUN":
|
|
57
|
+
self.finishRunFlag = True
|
|
58
|
+
elif type == "CLOSED_CONSTROBE":
|
|
59
|
+
self.keep_reading = False # Stop reading messages
|
|
60
|
+
break
|
|
61
|
+
elif self.callback:
|
|
62
|
+
if type == "TRACE":
|
|
63
|
+
message=message.replace("|","\n")
|
|
64
|
+
self.gotTraceFlag = True
|
|
65
|
+
if type == "RESULTS":
|
|
66
|
+
self.gotResultsFlag = True
|
|
67
|
+
|
|
68
|
+
response_value = self.callback(type,message)
|
|
69
|
+
|
|
70
|
+
if type == "GET":
|
|
71
|
+
response_message = f"RESPONSE_TO_GET {response_value}"
|
|
72
|
+
self.write_message(response_message)
|
|
73
|
+
def cleanup(self):
|
|
74
|
+
print("Cleaning up resources...")
|
|
75
|
+
self.keep_reading = False # Signal the thread to stop reading
|
|
76
|
+
self.running = False
|
|
77
|
+
self.reader_thread.join(timeout = 2) # Wait for the thread to finish
|
|
78
|
+
self.process.stdin.close()
|
|
79
|
+
self.process.stdout.close()
|
|
80
|
+
self.process.stderr.close()
|
|
81
|
+
if self.process:
|
|
82
|
+
self.process.terminate() # Try to terminate the process gracefully
|
|
83
|
+
try:
|
|
84
|
+
return self.process.wait(timeout=5) # Wait up to 5 seconds for it to finish
|
|
85
|
+
except subprocess.TimeoutExpired:
|
|
86
|
+
debug("Process did not terminate, force killing it...",self.name)
|
|
87
|
+
self.process.kill() # Force kill the process if it doesn't terminate in time
|
|
88
|
+
return self.process.wait() # Ensure it finishes
|
|
89
|
+
def stop(self, graceful=True, timeout=5):
|
|
90
|
+
"""Stop the process and cleanup."""
|
|
91
|
+
self.keep_reading = False
|
|
92
|
+
|
|
93
|
+
if not getattr(self, "process", None):
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
# If already stopped, nothing to do
|
|
97
|
+
if self.process.poll() is not None:
|
|
98
|
+
print(f"[{self.name}] Process already stopped.")
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
if graceful:
|
|
102
|
+
try:
|
|
103
|
+
if self.process.stdin:
|
|
104
|
+
self.process.stdin.write("EXIT\n")
|
|
105
|
+
self.process.stdin.flush()
|
|
106
|
+
# closing stdin often helps the child exit cleanly
|
|
107
|
+
try:
|
|
108
|
+
self.process.stdin.close()
|
|
109
|
+
except Exception:
|
|
110
|
+
pass
|
|
111
|
+
except Exception:
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
# Ask OS to terminate
|
|
115
|
+
try:
|
|
116
|
+
self.process.terminate()
|
|
117
|
+
except Exception:
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
# Wait, then force kill if needed
|
|
121
|
+
try:
|
|
122
|
+
self.process.wait(timeout=timeout)
|
|
123
|
+
except subprocess.TimeoutExpired:
|
|
124
|
+
try:
|
|
125
|
+
self.process.kill()
|
|
126
|
+
except Exception:
|
|
127
|
+
pass
|
|
128
|
+
try:
|
|
129
|
+
self.process.wait()
|
|
130
|
+
except Exception:
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
print(f"[{self.name}] Process stopped.")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pyconstrobe
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: This package is used to automate the DES application ConStrobe.
|
|
5
5
|
Author: Joseph Louis
|
|
6
6
|
Author-email: joseph.louis@oregonstate.edu
|
|
@@ -11,6 +11,7 @@ Dynamic: author
|
|
|
11
11
|
Dynamic: author-email
|
|
12
12
|
Dynamic: description
|
|
13
13
|
Dynamic: description-content-type
|
|
14
|
+
Dynamic: license-file
|
|
14
15
|
Dynamic: requires-python
|
|
15
16
|
Dynamic: summary
|
|
16
17
|
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
from setuptools import setup, find_packages
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
def get_version():
|
|
5
|
+
version_file = Path(__file__).parent / "pyconstrobe" / "__init__.py"
|
|
6
|
+
for line in version_file.read_text().splitlines():
|
|
7
|
+
if line.startswith("__version__"):
|
|
8
|
+
return line.split("=")[-1].strip().strip('"').strip("'")
|
|
2
9
|
|
|
3
10
|
setup(
|
|
4
11
|
name="pyconstrobe", # Replace with your package name
|
|
5
|
-
version=
|
|
12
|
+
version=get_version(), # Version number
|
|
6
13
|
packages=find_packages(), # Automatically find and include all packages
|
|
7
14
|
author="Joseph Louis",
|
|
8
15
|
author_email="joseph.louis@oregonstate.edu",
|
pyconstrobe-0.1/csPy/__init__.py
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import subprocess
|
|
2
|
-
import threading
|
|
3
|
-
import os
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class ProcessManager:
|
|
8
|
-
def __init__(self, callback=None):
|
|
9
|
-
build_mode = os.getenv('BUILD_MODE', 'Release') # Default to 'release' if not set
|
|
10
|
-
print(build_mode)
|
|
11
|
-
if build_mode=='Debug':
|
|
12
|
-
path_to_exe = r"C:\source\constrobe\csApp\Debug\constrobe.exe"
|
|
13
|
-
else:
|
|
14
|
-
path_to_exe = r"C:\Program Files\constrobe\constrobe\constrobe.exe"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
self.process = subprocess.Popen(
|
|
19
|
-
[path_to_exe, '--from-python'],
|
|
20
|
-
stdin=subprocess.PIPE,
|
|
21
|
-
stdout=subprocess.PIPE,
|
|
22
|
-
stderr=subprocess.PIPE,
|
|
23
|
-
text=True,
|
|
24
|
-
creationflags=subprocess.CREATE_NEW_CONSOLE
|
|
25
|
-
)
|
|
26
|
-
self.callback = callback # Custom callback for processing messages
|
|
27
|
-
self.keep_reading = True # Flag to control the reading loop
|
|
28
|
-
self.reader_thread = threading.Thread(target=self.read_messages)
|
|
29
|
-
self.reader_thread.daemon = True # Allow thread to exit when main program exits
|
|
30
|
-
self.reader_thread.start()
|
|
31
|
-
|
|
32
|
-
def write_message(self, message):
|
|
33
|
-
"""Write a message to the process's stdin."""
|
|
34
|
-
self.process.stdin.write(message + "\n")
|
|
35
|
-
self.process.stdin.flush()
|
|
36
|
-
|
|
37
|
-
def read_messages(self):
|
|
38
|
-
"""Read messages from the process's stdout."""
|
|
39
|
-
while self.keep_reading:
|
|
40
|
-
response = self.process.stdout.readline().strip()
|
|
41
|
-
if response:
|
|
42
|
-
if response == "COMPLETE":
|
|
43
|
-
print("Process completed.")
|
|
44
|
-
self.keep_reading = False # Stop reading messages
|
|
45
|
-
break
|
|
46
|
-
if self.callback:
|
|
47
|
-
response_message = self.callback(response)
|
|
48
|
-
if response_message:
|
|
49
|
-
self.write_message(response_message)
|
|
50
|
-
|
|
51
|
-
def cleanup(self):
|
|
52
|
-
"""Clean up the process and close streams."""
|
|
53
|
-
print("Cleaning up resources...")
|
|
54
|
-
self.keep_reading = False # Signal the thread to stop reading
|
|
55
|
-
self.running = False
|
|
56
|
-
self.reader_thread.join() # Wait for the thread to finish
|
|
57
|
-
self.process.stdin.close()
|
|
58
|
-
self.process.stdout.close()
|
|
59
|
-
self.process.stderr.close()
|
|
60
|
-
if self.process:
|
|
61
|
-
self.process.terminate() # Try to terminate the process gracefully
|
|
62
|
-
try:
|
|
63
|
-
return self.process.wait(timeout=5) # Wait up to 5 seconds for it to finish
|
|
64
|
-
except subprocess.TimeoutExpired:
|
|
65
|
-
print("Process did not terminate, force killing it...")
|
|
66
|
-
self.process.kill() # Force kill the process if it doesn't terminate in time
|
|
67
|
-
return self.process.wait() # Ensure it finishes
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import subprocess
|
|
2
|
-
import threading
|
|
3
|
-
import os
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class ProcessManager:
|
|
8
|
-
def __init__(self, callback=None):
|
|
9
|
-
build_mode = os.getenv('BUILD_MODE', 'Release') # Default to 'release' if not set
|
|
10
|
-
print(build_mode)
|
|
11
|
-
if build_mode=='Debug':
|
|
12
|
-
path_to_exe = r"C:\source\constrobe\csApp\Debug\constrobe.exe"
|
|
13
|
-
else:
|
|
14
|
-
path_to_exe = r"C:\Program Files\constrobe\constrobe\constrobe.exe"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
self.process = subprocess.Popen(
|
|
19
|
-
[path_to_exe, '--from-python'],
|
|
20
|
-
stdin=subprocess.PIPE,
|
|
21
|
-
stdout=subprocess.PIPE,
|
|
22
|
-
stderr=subprocess.PIPE,
|
|
23
|
-
text=True,
|
|
24
|
-
creationflags=subprocess.CREATE_NEW_CONSOLE
|
|
25
|
-
)
|
|
26
|
-
self.callback = callback # Custom callback for processing messages
|
|
27
|
-
self.keep_reading = True # Flag to control the reading loop
|
|
28
|
-
self.reader_thread = threading.Thread(target=self.read_messages)
|
|
29
|
-
self.reader_thread.daemon = True # Allow thread to exit when main program exits
|
|
30
|
-
self.reader_thread.start()
|
|
31
|
-
|
|
32
|
-
def write_message(self, message):
|
|
33
|
-
"""Write a message to the process's stdin."""
|
|
34
|
-
self.process.stdin.write(message + "\n")
|
|
35
|
-
self.process.stdin.flush()
|
|
36
|
-
|
|
37
|
-
def read_messages(self):
|
|
38
|
-
"""Read messages from the process's stdout."""
|
|
39
|
-
while self.keep_reading:
|
|
40
|
-
response = self.process.stdout.readline().strip()
|
|
41
|
-
if response:
|
|
42
|
-
if response == "COMPLETE":
|
|
43
|
-
print("Process completed.")
|
|
44
|
-
self.keep_reading = False # Stop reading messages
|
|
45
|
-
break
|
|
46
|
-
if self.callback:
|
|
47
|
-
response_message = self.callback(response)
|
|
48
|
-
if response_message:
|
|
49
|
-
self.write_message(response_message)
|
|
50
|
-
|
|
51
|
-
def cleanup(self):
|
|
52
|
-
"""Clean up the process and close streams."""
|
|
53
|
-
print("Cleaning up resources...")
|
|
54
|
-
self.keep_reading = False # Signal the thread to stop reading
|
|
55
|
-
self.running = False
|
|
56
|
-
self.reader_thread.join() # Wait for the thread to finish
|
|
57
|
-
self.process.stdin.close()
|
|
58
|
-
self.process.stdout.close()
|
|
59
|
-
self.process.stderr.close()
|
|
60
|
-
if self.process:
|
|
61
|
-
self.process.terminate() # Try to terminate the process gracefully
|
|
62
|
-
try:
|
|
63
|
-
return self.process.wait(timeout=5) # Wait up to 5 seconds for it to finish
|
|
64
|
-
except subprocess.TimeoutExpired:
|
|
65
|
-
print("Process did not terminate, force killing it...")
|
|
66
|
-
self.process.kill() # Force kill the process if it doesn't terminate in time
|
|
67
|
-
return self.process.wait() # Ensure it finishes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|