@@ -11,15 +11,26 @@ class ProcessManager
1111
1212 public function __construct (string $ binaryPath , bool $ debug = true )
1313 {
14- $ this ->binaryPath = $ binaryPath ;
14+ // Normalize path for Windows
15+ $ this ->binaryPath = str_replace ('/ ' , '\\' , $ binaryPath );
1516 $ this ->debug = $ debug ;
16- $ this ->debugLog ("ProcessManager initialized with binary: $ binaryPath " );
17+
18+ // Verify binary exists and is executable
19+ if (!file_exists ($ this ->binaryPath )) {
20+ throw new RuntimeException ("Binary not found at: {$ this ->binaryPath }" );
21+ }
22+
23+ if (!is_executable ($ this ->binaryPath )) {
24+ throw new RuntimeException ("Binary is not executable: {$ this ->binaryPath }" );
25+ }
26+
27+ $ this ->debugLog ("ProcessManager initialized with verified binary: {$ this ->binaryPath }" );
1728 }
1829
1930 private function debugLog (string $ message ): void
2031 {
2132 if ($ this ->debug ) {
22- echo "[DEBUG] " . date ('Y-m-d H:i:s ' ) . " - $ message \n" ;
33+ fwrite ( STDERR , "[DEBUG] " . date ('Y-m-d H:i:s ' ) . " - $ message \n" ) ;
2334 flush ();
2435 }
2536 }
@@ -28,78 +39,83 @@ public function execute(array $config, bool $streamOutput): string
2839 {
2940 $ this ->debugLog ("Starting execution " );
3041
31- // For Windows, ensure the path is properly quoted
32- $ cmd = PHP_OS_FAMILY === 'Windows '
33- ? '" ' . str_replace ('/ ' , '\\' , $ this ->binaryPath ) . '" '
34- : $ this ->binaryPath ;
35-
36- $ this ->debugLog ("Command to execute: $ cmd " );
37-
3842 // Create temporary file for input
3943 $ tmpfname = tempnam (sys_get_temp_dir (), 'volt_ ' );
4044 $ this ->debugLog ("Created temporary file: $ tmpfname " );
4145
46+ // Write config to temp file
4247 file_put_contents ($ tmpfname , json_encode ($ config , JSON_PRETTY_PRINT ));
43- $ this ->debugLog ("Written config to temporary file " );
4448
49+ // Build command with proper escaping
50+ $ cmd = escapeshellarg ($ this ->binaryPath );
51+ $ this ->debugLog ("Executing command: $ cmd " );
52+
53+ // Setup process
4554 $ descriptorspec = [
46- 0 => ['pipe ' , 'r ' ],
47- 1 => ['pipe ' , 'w ' ],
48- 2 => ['pipe ' , 'w ' ]
55+ 0 => ['pipe ' , 'r ' ], // stdin
56+ 1 => ['pipe ' , 'w ' ], // stdout
57+ 2 => ['pipe ' , 'w ' ] // stderr
4958 ];
5059
51- $ this ->debugLog ("Opening process " );
60+ $ cwd = dirname ($ this ->binaryPath );
61+ $ env = ['VOLT_TEST_DEBUG ' => '1 ' ];
5262
53- $ process = proc_open ($ cmd , $ descriptorspec , $ pipes , null , null , [
63+ $ this ->debugLog ("Opening process in directory: $ cwd " );
64+
65+ $ process = proc_open ($ cmd , $ descriptorspec , $ pipes , $ cwd , $ env , [
5466 'bypass_shell ' => true ,
5567 'create_process_group ' => true
5668 ]);
5769
5870 if (!is_resource ($ process )) {
5971 unlink ($ tmpfname );
60- throw new RuntimeException (' Failed to start process ' );
72+ throw new RuntimeException (" Failed to start process: $ cmd " );
6173 }
6274
6375 $ this ->currentProcess = $ process ;
6476 $ this ->debugLog ("Process started successfully " );
6577
6678 try {
67- // Write config to stdin
79+ // Write config to process
6880 $ this ->debugLog ("Writing config to process " );
69- fwrite ($ pipes [0 ], file_get_contents ($ tmpfname ));
81+ $ configContent = file_get_contents ($ tmpfname );
82+ fwrite ($ pipes [0 ], $ configContent );
7083 fclose ($ pipes [0 ]);
7184 unlink ($ tmpfname );
7285
73- $ this ->debugLog ("Starting to read output " );
74- $ output = '' ;
75-
76- // Set streams to non-blocking
86+ // Set up non-blocking reads
7787 stream_set_blocking ($ pipes [1 ], false );
7888 stream_set_blocking ($ pipes [2 ], false );
7989
80- while (true ) {
90+ $ output = '' ;
91+ $ processRunning = true ;
92+
93+ $ this ->debugLog ("Starting output reading loop " );
94+
95+ while ($ processRunning ) {
8196 $ status = proc_get_status ($ process );
97+ $ processRunning = $ status ['running ' ];
8298
83- if (! $ status [ ' running ' ]) {
84- $ this -> debugLog ( " Process has finished running " );
99+ $ read = array_filter ([ $ pipes [ 1 ], $ pipes [ 2 ]], ' is_resource ' );
100+ if ( empty ( $ read )) {
85101 break ;
86102 }
87103
88- $ read = [$ pipes [1 ], $ pipes [2 ]];
89104 $ write = null ;
90105 $ except = null ;
91106
92107 if (stream_select ($ read , $ write , $ except , 0 , 100000 )) {
93108 foreach ($ read as $ pipe ) {
94109 $ data = fread ($ pipe , 4096 );
110+
95111 if ($ data === false || $ data === '' ) {
96112 continue ;
97113 }
98114
99115 if ($ pipe === $ pipes [1 ]) {
100116 $ output .= $ data ;
101117 if ($ streamOutput ) {
102- echo $ data ;
118+ fwrite ( STDOUT , $ data) ;
103119 flush ();
104120 }
105121 } else {
@@ -108,9 +124,13 @@ public function execute(array $config, bool $streamOutput): string
108124 }
109125 }
110126 }
111- }
112127
113- $ this ->debugLog ("Finished reading output " );
128+ // Check if process has exited
129+ if (!$ processRunning ) {
130+ $ this ->debugLog ("Process has finished " );
131+ break ;
132+ }
133+ }
114134
115135 // Close remaining pipes
116136 foreach ($ pipes as $ pipe ) {
@@ -119,7 +139,7 @@ public function execute(array $config, bool $streamOutput): string
119139 }
120140 }
121141
122- // Get exit code
142+ // Get exit code and close process
123143 $ exitCode = proc_close ($ process );
124144 $ this ->debugLog ("Process closed with exit code: $ exitCode " );
125145
@@ -142,12 +162,8 @@ public function execute(array $config, bool $streamOutput): string
142162 if (is_resource ($ process )) {
143163 $ status = proc_get_status ($ process );
144164 if ($ status ['running ' ]) {
145- // Force kill on Windows
146- if (PHP_OS_FAMILY === 'Windows ' ) {
147- exec ('taskkill /F /T /PID ' . $ status ['pid ' ]);
148- } else {
149- proc_terminate ($ process );
150- }
165+ exec ("taskkill /F /T /PID {$ status ['pid ' ]} 2>&1 " , $ output , $ resultCode );
166+ $ this ->debugLog ("Taskkill result: " . implode ("\n" , $ output ) . " (code: $ resultCode) " );
151167 }
152168 proc_close ($ process );
153169 }
0 commit comments