Skip to content

Commit c07d15d

Browse files
committed
Kill all child processes via a monitor command #20 #16
1 parent c4d4bcc commit c07d15d

File tree

4 files changed

+126
-0
lines changed

4 files changed

+126
-0
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
> [!WARNING]
1212
> This is still beta software. Use with caution.
1313
14+
> This package require ext-pcntl
15+
1416
## About
1517

1618
Solo for Laravel is a package to run multiple commands at once, to aid in local development. After installing, you can open the SoloServiceProvider to add or remove commands.

src/Console/Commands/Monitor.php

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
namespace AaronFrancis\Solo\Console\Commands;
4+
5+
use Illuminate\Console\Command;
6+
use RuntimeException;
7+
8+
class Monitor extends Command
9+
{
10+
protected $signature = 'solo:monitor {pid}';
11+
12+
protected $description = 'Watch for the stray processes and clean them up.';
13+
14+
public function handle()
15+
{
16+
$parent = $this->argument('pid');
17+
$children = [];
18+
19+
$this->info("Monitoring parent process PID: {$parent}");
20+
21+
while (true) {
22+
$children = array_unique([
23+
...$children,
24+
...$this->getChildProcesses($parent)
25+
]);
26+
27+
sleep(1);
28+
29+
if ($this->isProcessRunning($parent)) {
30+
continue;
31+
}
32+
33+
$this->warn("Parent process {$parent} has died.");
34+
35+
// Give them a chance to die on their own.
36+
sleep(2);
37+
38+
// Recursively kill all tracked child processes
39+
foreach ($children as $child) {
40+
$this->killProcess($child);
41+
}
42+
43+
$this->info('All tracked child processes cleaned up. Exiting.');
44+
45+
break;
46+
}
47+
}
48+
49+
public function isProcessRunning($pid)
50+
{
51+
// Check if the process with the given PID exists
52+
$output = [];
53+
exec("ps -p {$pid}", $output);
54+
return count($output) > 1; // If the output has more than the header line, the process is running
55+
}
56+
57+
public function getChildProcesses($pid)
58+
{
59+
// Detect the operating system
60+
$os = PHP_OS_FAMILY;
61+
62+
// Get the list of all processes with their PID and PPID
63+
$output = [];
64+
if ($os === 'Darwin') { // macOS
65+
exec("ps -eo pid,ppid | tail -n +2", $output);
66+
} elseif ($os === 'Linux') { // Linux
67+
exec("ps -eo pid,ppid --no-headers", $output);
68+
} else {
69+
throw new RuntimeException("Unsupported operating system: $os");
70+
}
71+
72+
// Parse the output into an array of processes
73+
$processes = [];
74+
foreach ($output as $line) {
75+
list($childPid, $parentPid) = preg_split('/\s+/', trim($line));
76+
$processes[] = ['pid' => $childPid, 'ppid' => $parentPid];
77+
}
78+
79+
// Recursive function to find children of the given PID
80+
$children = [];
81+
foreach ($processes as $process) {
82+
if ($process['ppid'] == $pid) {
83+
$children[] = $process['pid'];
84+
// Recurse to find the children of this child
85+
$children = array_merge($children, $this->getChildProcesses($process['pid']));
86+
}
87+
}
88+
89+
return $children;
90+
}
91+
92+
public function killProcess($pid)
93+
{
94+
if ($pid === getmypid()) {
95+
return;
96+
}
97+
98+
if ($this->isProcessRunning($pid)) {
99+
exec("kill -9 {$pid}");
100+
}
101+
102+
$this->warn("Killed process PID: {$pid}");
103+
}
104+
}

src/Console/Commands/Solo.php

+18
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
use AaronFrancis\Solo\Prompt\Dashboard;
1111
use Illuminate\Console\Command;
12+
use Symfony\Component\Process\Process;
1213

1314
class Solo extends Command
1415
{
@@ -18,6 +19,23 @@ class Solo extends Command
1819

1920
public function handle(): void
2021
{
22+
$this->monitor();
23+
2124
Dashboard::start();
2225
}
26+
27+
protected function monitor()
28+
{
29+
$process = new Process(['php', 'artisan', 'solo:monitor', getmypid()]);
30+
31+
// Ensure the process runs in the background and doesn't tie to the parent
32+
$process->setOptions([
33+
'create_new_console' => true,
34+
'create_process_group' => true,
35+
]);
36+
37+
$process->disableOutput();
38+
39+
$process->start();
40+
}
2341
}

src/Providers/SoloServiceProvider.php

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace AaronFrancis\Solo\Providers;
99

1010
use AaronFrancis\Solo\Console\Commands\About;
11+
use AaronFrancis\Solo\Console\Commands\Monitor;
1112
use AaronFrancis\Solo\Console\Commands\Install;
1213
use AaronFrancis\Solo\Console\Commands\Solo;
1314
use AaronFrancis\Solo\Manager;
@@ -34,6 +35,7 @@ public function boot()
3435
protected function registerCommands()
3536
{
3637
$this->commands([
38+
Monitor::class,
3739
Solo::class,
3840
Install::class,
3941
About::class

0 commit comments

Comments
 (0)