11<?php
22
33namespace VoltTest ;
4-
54use RuntimeException ;
65
76class ProcessManager
87{
98 private string $ binaryPath ;
10-
119 private $ currentProcess = null ;
10+ private $ isWindows ;
1211
1312 public function __construct (string $ binaryPath )
1413 {
1514 $ this ->binaryPath = $ binaryPath ;
16- if (function_exists ('pcntl_async_signals ' )) {
15+ $ this ->isWindows = PHP_OS_FAMILY === 'Windows ' ;
16+
17+ // Only register signal handlers on non-Windows systems
18+ if (!$ this ->isWindows && function_exists ('pcntl_async_signals ' )) {
1719 pcntl_async_signals (true );
1820 pcntl_signal (SIGINT , [$ this , 'handleSignal ' ]);
1921 pcntl_signal (SIGTERM , [$ this , 'handleSignal ' ]);
@@ -34,23 +36,15 @@ public function execute(array $config, bool $streamOutput): string
3436 [$ success , $ process , $ pipes ] = $ this ->openProcess ();
3537 $ this ->currentProcess = $ process ;
3638
37- if (! $ success || ! is_array ($ pipes )) {
39+ if (!$ success || !is_array ($ pipes )) {
3840 throw new RuntimeException ('Failed to start process of volt test ' );
3941 }
4042
4143 try {
4244 $ this ->writeInput ($ pipes [0 ], json_encode ($ config , JSON_PRETTY_PRINT ));
4345 fclose ($ pipes [0 ]);
44-
45- $ output = $ this ->handleProcess ($ pipes , $ streamOutput );
46-
47- // Store stderr content before closing
48- $ stderrContent = '' ;
49- if (isset ($ pipes [2 ]) && is_resource ($ pipes [2 ])) {
50- rewind ($ pipes [2 ]);
51- $ stderrContent = stream_get_contents ($ pipes [2 ]);
52- }
53-
46+ return $ this ->handleProcess ($ pipes , $ streamOutput );
47+ } finally {
5448 // Clean up pipes
5549 foreach ($ pipes as $ pipe ) {
5650 if (is_resource ($ pipe )) {
@@ -62,43 +56,38 @@ public function execute(array $config, bool $streamOutput): string
6256 $ exitCode = $ this ->closeProcess ($ process );
6357 $ this ->currentProcess = null ;
6458 if ($ exitCode !== 0 ) {
65- echo "\nError: " . trim ($ stderrContent ) . "\n" ;
66-
67- return '' ;
68- }
69- }
70-
71- return $ output ;
72- } finally {
73- foreach ($ pipes as $ pipe ) {
74- if (is_resource ($ pipe )) {
75- fclose ($ pipe );
59+ throw new RuntimeException ('Process failed with exit code ' . $ exitCode );
7660 }
7761 }
78- if (is_resource ($ process )) {
79- $ this ->closeProcess ($ process );
80- $ this ->currentProcess = null ;
81- }
8262 }
8363 }
8464
8565 protected function openProcess (): array
8666 {
87- $ pipes = [];
67+ $ descriptorspec = [
68+ 0 => ['pipe ' , 'r ' ], // stdin
69+ 1 => ['pipe ' , 'w ' ], // stdout
70+ 2 => ['pipe ' , 'w ' ] // stderr
71+ ];
72+
73+ // Windows-specific process options
74+ $ options = $ this ->isWindows ? [
75+ 'bypass_shell ' => true ,
76+ 'create_process_group ' => true // Important for Windows process management
77+ ] : [
78+ 'bypass_shell ' => true
79+ ];
80+
8881 $ process = proc_open (
8982 $ this ->binaryPath ,
90- [
91- 0 => ['pipe ' , 'r ' ],
92- 1 => ['pipe ' , 'w ' ],
93- 2 => ['pipe ' , 'w ' ],
94- ],
83+ $ descriptorspec ,
9584 $ pipes ,
9685 null ,
9786 null ,
98- [ ' bypass_shell ' => true ]
87+ $ options
9988 );
10089
101- if (! is_resource ($ process )) {
90+ if (!is_resource ($ process )) {
10291 return [false , null , []];
10392 }
10493
@@ -108,17 +97,36 @@ protected function openProcess(): array
10897 private function handleProcess (array $ pipes , bool $ streamOutput ): string
10998 {
11099 $ output = '' ;
100+ $ running = true ;
111101
112- while (true ) {
102+ // Set streams to non-blocking mode
103+ foreach ($ pipes as $ pipe ) {
104+ if (is_resource ($ pipe )) {
105+ stream_set_blocking ($ pipe , false );
106+ }
107+ }
108+
109+ while ($ running ) {
113110 $ read = array_filter ($ pipes , 'is_resource ' );
114111 if (empty ($ read )) {
115112 break ;
116113 }
117114
115+ // Windows-specific: Check process status
116+ if ($ this ->isWindows ) {
117+ $ status = proc_get_status ($ this ->currentProcess );
118+ if (!$ status ['running ' ]) {
119+ $ running = false ;
120+ }
121+ }
122+
118123 $ write = null ;
119124 $ except = null ;
120125
121- if (stream_select ($ read , $ write , $ except , 1 ) === false ) {
126+ // Use a shorter timeout on Windows
127+ $ timeout = $ this ->isWindows ? 0.1 : 1 ;
128+
129+ if (stream_select ($ read , $ write , $ except , 0 , $ timeout * 1000000 ) === false ) {
122130 break ;
123131 }
124132
@@ -130,7 +138,6 @@ private function handleProcess(array $pipes, bool $streamOutput): string
130138 if (feof ($ pipe )) {
131139 fclose ($ pipe );
132140 unset($ pipes [$ type ]);
133-
134141 continue ;
135142 }
136143 }
@@ -139,9 +146,15 @@ private function handleProcess(array $pipes, bool $streamOutput): string
139146 $ output .= $ data ;
140147 if ($ streamOutput ) {
141148 echo $ data ;
149+ if ($ this ->isWindows ) {
150+ flush (); // Ensure output is displayed immediately on Windows
151+ }
142152 }
143153 } elseif ($ type === 2 && $ streamOutput ) { // stderr
144154 fwrite (STDERR , $ data );
155+ if ($ this ->isWindows ) {
156+ flush ();
157+ }
145158 }
146159 }
147160 }
@@ -153,21 +166,28 @@ protected function writeInput($pipe, string $input): void
153166 {
154167 if (is_resource ($ pipe )) {
155168 fwrite ($ pipe , $ input );
169+ if ($ this ->isWindows ) {
170+ fflush ($ pipe ); // Ensure data is written immediately on Windows
171+ }
156172 }
157173 }
158174
159175 protected function closeProcess ($ process ): int
160176 {
161- if (! is_resource ($ process )) {
177+ if (!is_resource ($ process )) {
162178 return -1 ;
163179 }
164180
165181 $ status = proc_get_status ($ process );
166182 if ($ status ['running ' ]) {
167- proc_terminate ($ process );
183+ // Windows-specific process termination
184+ if ($ this ->isWindows ) {
185+ exec ('taskkill /F /T /PID ' . $ status ['pid ' ]);
186+ } else {
187+ proc_terminate ($ process );
188+ }
168189 }
169190
170-
171191 return proc_close ($ process );
172192 }
173- }
193+ }
0 commit comments