Skip to content

Commit 5aec1c5

Browse files
committed
Add a few things:
- A variable to tell you what strings have been send to and from MAD-NG - Allow debug to not just go to the stdout - Change how the ctrl-c is handled. Previous way led to the process hanging.
1 parent b065395 commit 5aec1c5

File tree

2 files changed

+65
-17
lines changed

2 files changed

+65
-17
lines changed

src/pymadng/madp_object.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import numpy as np # For arrays (Works well with multiprocessing and mmap)
22
from typing import Any, Iterable, Union, List # To make stuff look nicer
33

4-
import os, platform
4+
import os
5+
import platform
56

67
bin_path = os.path.dirname(os.path.abspath(__file__)) + "/bin"
78

@@ -49,7 +50,7 @@ def __init__(
4950
self,
5051
mad_path: str = None,
5152
py_name: str = "py",
52-
debug: bool = False,
53+
debug: Union[int, str, bool] = False,
5354
num_temp_vars: int = 8,
5455
ipython_use_jedi: bool = False,
5556
):
@@ -394,6 +395,21 @@ def globals(self) -> List[str]:
394395
A list of strings indicating the globals variables and modules within the MAD-NG environment
395396
"""
396397
return dir(self.__process.recv_vars(f"{self.py_name}._env"))
398+
399+
def history(self) -> str:
400+
"""Retrieve the history of strings that have been sent to MAD-NG
401+
402+
Returns:
403+
A string containing the history of commands that have been sent to MAD-NG
404+
"""
405+
# delete all lines that start py:__err and end with __err(false)\n
406+
history = self.__process.history
407+
history = history.split("\n")
408+
history = [
409+
x for x in history[2:] if not "py:__err" in x
410+
]
411+
return "\n".join(history)
412+
397413

398414
# -------------------------------For use with the "with" statement-----------------------------------#
399415
def __enter__(self):

src/pymadng/madp_pymad.py

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import struct, os, subprocess, sys, select
1+
import struct
2+
import os
3+
import subprocess
4+
import sys
5+
import select
6+
import signal
27
from typing import Union, Callable, Any
38
import numpy as np
49

@@ -13,7 +18,7 @@ def is_private(varname):
1318

1419

1520
class mad_process:
16-
def __init__(self, mad_path: str, py_name: str = "py", debug: bool = False) -> None:
21+
def __init__(self, mad_path: str, py_name: str = "py", debug: Union[int, str, bool] = False) -> None:
1722
self.py_name = py_name
1823

1924
# Create the pipes for communication
@@ -23,21 +28,38 @@ def __init__(self, mad_path: str, py_name: str = "py", debug: bool = False) -> N
2328
# Open the pipes for communication to MAD (the stdin of MAD)
2429
self.fto_mad = os.fdopen(self.to_mad, "wb", buffering=0)
2530

31+
if isinstance(debug, str):
32+
debug_file = open(debug, "w")
33+
stdout = debug_file.fileno()
34+
elif isinstance(debug, bool):
35+
stdout = sys.stdout.fileno()
36+
elif isinstance(debug, int):
37+
stdout = debug
38+
else:
39+
raise TypeError("Debug must be a file name, file descriptor or a boolean")
40+
2641
# Create a chunk of code to start the process
2742
startupChunk = (
28-
f"MAD.pymad '{py_name}' {{_dbg = {str(debug).lower()}}} :__ini({mad_write})"
43+
f"MAD.pymad '{py_name}' {{_dbg = {str(bool(debug)).lower()}}} :__ini({mad_write})"
2944
)
45+
original_sigint_handler = signal.getsignal(signal.SIGINT)
46+
def delete_process(sig, frame):
47+
self.process.terminate() # In case user left mad waiting
48+
self.fto_mad.close()
49+
self.process.wait()
50+
signal.signal(signal.SIGINT, original_sigint_handler)
51+
raise KeyboardInterrupt("MAD process was interrupted, and has been deleted")
52+
signal.signal(signal.SIGINT, delete_process) # Delete the process if interrupted
3053

3154
# Start the process
3255
self.process = subprocess.Popen(
3356
[mad_path, "-q", "-e", startupChunk],
3457
bufsize=0,
3558
stdin=mad_read, # Set the stdin of MAD to the read end of the pipe
36-
stdout=sys.stdout.fileno(), # Forward stdout
37-
preexec_fn=os.setpgrp, # Don't forward signals
59+
stdout=stdout, # Forward stdout
3860
pass_fds=[
3961
mad_write,
40-
sys.stdout.fileno(),
62+
stdout,
4163
sys.stderr.fileno(),
4264
], # Don't close these (python closes all fds by default)
4365
)
@@ -51,17 +73,19 @@ def __init__(self, mad_path: str, py_name: str = "py", debug: bool = False) -> N
5173

5274
# Open the pipe from MAD (this is where MAD will no longer hang)
5375
self.ffrom_mad = os.fdopen(self.from_mad, "rb")
76+
self.history = "" # Begin the recording of the history
5477

5578
# stdout should be line buffered by default, but for jupyter notebook,
5679
# stdout is redirected and not line buffered by default
57-
self.send(
58-
f"""io.stdout:setvbuf('line')
59-
{self.py_name}:send(1)"""
60-
)
80+
self.send("io.stdout:setvbuf('line')")
81+
82+
# Check if MAD started successfully
83+
self.send(f"{self.py_name}:send(1)")
84+
checker = select.select([self.ffrom_mad], [], [], 10) # May not work on windows
6185

6286
# Check if MAD started successfully using select
63-
checker = select.select([self.ffrom_mad], [], [], 1) # May not work on windows
6487
if not checker[0] or self.recv() != 1: # Need to check number?
88+
del self
6589
raise OSError(f"Unsuccessful starting of {mad_path} process")
6690

6791
def send_rng(self, start: float, stop: float, size: int):
@@ -140,10 +164,17 @@ def recv_vars(self, *names):
140164
# -------------------------------------------------------------------------- #
141165

142166
def __del__(self):
143-
self.send(f"{self.py_name}:__fin()")
144-
self.ffrom_mad.close()
145-
self.process.terminate() # In case user left mad waiting
146-
self.fto_mad.close()
167+
if self.process.poll() is None: # If process is still running
168+
self.send(f"{self.py_name}:__fin()") # Tell the mad side to finish
169+
self.process.terminate() # Terminate the process on the python side
170+
171+
# Close the pipes
172+
if not self.ffrom_mad.closed:
173+
self.ffrom_mad.close()
174+
if not self.fto_mad.closed:
175+
self.fto_mad.close()
176+
177+
# Wait for the process to finish
147178
self.process.wait()
148179

149180

@@ -218,6 +249,7 @@ def recv_int(self: mad_process) -> int:
218249

219250

220251
def send_str(self: mad_process, input: str):
252+
self.history += input + "\n"
221253
send_int(self, len(input))
222254
self.fto_mad.write(input.encode("utf-8"))
223255

0 commit comments

Comments
 (0)