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
27from typing import Union , Callable , Any
38import numpy as np
49
@@ -13,7 +18,7 @@ def is_private(varname):
1318
1419
1520class 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
220251def 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