Skip to content

Commit 7322573

Browse files
Child Processes (#53)
* wip * style * add php & artisan processes * add first code sample above the fold * added front-end listener example * replaced -n50 with -f * remove recursion in code sample * restructure events * add reference to persistent processes in 'quit' section * moved input & output sections together * Copy tweaks --------- Co-authored-by: gwleuverink <[email protected]>
1 parent adb71e0 commit 7322573

File tree

1 file changed

+356
-0
lines changed

1 file changed

+356
-0
lines changed
Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
---
2+
title: Child Processes
3+
order: 700
4+
---
5+
6+
# Child Processes
7+
8+
Child Processes allow your application to spin up managed processes, forked from your app's main process. This is great
9+
for long-running processes that you want to interact with repeatedly during the life of your application.
10+
11+
Child Processes can be managed from your application using a straightforward API. When your app quits, these processes
12+
get shut down gracefully.
13+
14+
"Spawning" a Child Process is like running a command from the CLI. Any command you can run in the terminal can be a
15+
Child Process.
16+
17+
```php
18+
ChildProcess::start(
19+
cmd: 'tail -f storage/logs/laravel.log',
20+
alias: 'tail'
21+
);
22+
```
23+
24+
Any process invoked using the ChildProcess facade will be non-blocking and keep running in the background. Even if the request that triggered it has finished.
25+
26+
**Bear in mind that your Child Process ("command line") arguments may need to differ depending on which platform your
27+
application is running on (Mac/Linux vs Windows).**
28+
29+
**Where possible, you should explicitly reference binaries by their full path name, unless you can reliably assume that
30+
the executable you're trying to spawn is available in the user's `PATH`.**
31+
32+
Child Processes are managed by the runtime (Electron/Tauri) but are fully accessible to the Laravel side of your
33+
application.
34+
35+
---
36+
37+
## Alternatives
38+
39+
Before deciding to use a Child Process, consider the alternatives available to you. You should pick the most
40+
appropriate for the problem you're trying to solve:
41+
42+
### Queues
43+
44+
The [queue runner](queues) is useful for very simply offloading _Laravel_ tasks to the background. Each task must be a
45+
Laravel queued [Job](https://laravel.com/docs/queues#creating-jobs).
46+
47+
Any queued jobs that don't get processed before your app is quit, will get processed when your application (and the
48+
queue runner) starts again.
49+
50+
### Scheduler
51+
52+
The Laravel scheduler runs as normal (every minute) inside a NativePHP application. You can add
53+
[scheduled tasks](https://laravel.com/docs/scheduling) to your application just as you normally would, to have them run
54+
on a regular schedule.
55+
56+
Any scheduled tasks that would have run while your application isn't running will be skipped.
57+
58+
**The queue runner and the scheduler are tied to your _application_, not the operating system, so they will
59+
only be able to run while your application is running.**
60+
61+
### `shell_exec`, `proc_open` etc
62+
63+
PHP has good built-in support for running arbitrary programs in separate processes. For example:
64+
65+
- [`shell_exec`](https://www.php.net/manual/en/function.shell-exec.php) allows you to run commands and return their
66+
output to your application.
67+
- [`proc_open`](https://www.php.net/manual/en/function.proc-open.php) allows you to spin up a command with more control
68+
over how its input and output streams are handled.
69+
70+
While these can be used in your NativePHP application, consider that they:
71+
72+
- May block the script that is executing them until the sub-process has finished.
73+
- May become orphaned from your application, allowing them to continue running after your app has quit.
74+
75+
Runaway orphaned processes could negatively impact your user's system and can become tricky to manage without user
76+
intervention. You should be cautious about starting processes this way.
77+
78+
---
79+
80+
## Starting a Child Process
81+
82+
Each Child Process must have a unique alias. This is the name you will use to reference and interact with this process
83+
throughout your application.
84+
85+
You may start a process using the `ChildProcess` facade:
86+
87+
```php
88+
use Native\Laravel\Facades\ChildProcess;
89+
90+
ChildProcess::start(
91+
cmd: 'tail -f storage/logs/laravel.log',
92+
alias: 'tail'
93+
);
94+
```
95+
96+
The `start` method will return a `Native\Laravel\ChildProcess` instance, which represents the process. You may interact
97+
directly with this instance to make changes to that process, but this does not necessarily mean that the
98+
process was started.
99+
100+
The timing of process initilization is controlled by the user's operating system and spawning
101+
may fail for a number of reasons.
102+
103+
**To determine if the process has started successfully, you should listen for the
104+
[`ProcessSpawned` event](#codeprocessspawnedcode).**
105+
106+
### Current Working Directory
107+
108+
By default, the child process will use the working directory of your application as it's "current working directory"
109+
(`cwd`). However, you can explicitly change this if needed by passing a string path to the `$cwd` parameter of the
110+
`start` method:
111+
112+
```php
113+
ChildProcess::start(
114+
cmd: ['tail', '-f', 'logs/laravel.log'],
115+
alias: 'tail',
116+
cwd: storage_path()
117+
);
118+
```
119+
120+
### Persistent Processes
121+
122+
You may mark a process as `persistent` to indicate that the runtime should make sure that once it has been started it
123+
is always running. This works similarly to tools like [`supervisord`](http://supervisord.org/), ensuring that the
124+
process gets booted up again in case it crashes.
125+
126+
```php
127+
ChildProcess::start(
128+
cmd: ['tail', '-f', 'logs/laravel.log'],
129+
alias: 'tail',
130+
persistent: true
131+
);
132+
```
133+
134+
**The only way to stop a persistent process is for your application to quit.**
135+
136+
### PHP scripts
137+
138+
For your convenience, NativePHP provides a simple method to execute PHP scripts in the background using NativePHP's packaged PHP binary:
139+
140+
```php
141+
ChildProcess::php('path/to/script.php', alias: 'script');
142+
```
143+
144+
### Artisan commands
145+
146+
NativePHP provides a similar method convenience for Artisan commands:
147+
148+
```php
149+
ChildProcess::artisan('smtp:serve', alias: 'smtp-server');
150+
```
151+
152+
## Getting running processes
153+
154+
### Getting a single process
155+
156+
You can use the `ChildProcess` facade's `get` method to get a running process with a given alias:
157+
158+
```php
159+
$tail = ChildProcess::get('tail');
160+
```
161+
162+
This will return a `Native\Laravel\ChildProcess` instance.
163+
164+
### Getting all processes
165+
166+
You can use the `ChildProcess` facade's `all` method to get all running processes:
167+
168+
```php
169+
$processes = ChildProcess::all();
170+
```
171+
172+
This will return an array of `Native\Laravel\ChildProcess` instances.
173+
174+
## Stopping a Child Process
175+
176+
Your child processes will shut down when your application exits. However, you may also choose to stop them manually or
177+
provide this control to your user.
178+
179+
If you have a `Native\Laravel\ChildProcess` instance, you may call the `stop` method on it:
180+
181+
```php
182+
$tail->stop();
183+
```
184+
185+
Alternatively, you may use the `ChildProcess` facade to stop a process via its alias:
186+
187+
```php
188+
ChildProcess::stop('tail');
189+
```
190+
191+
This will attempt to stop the process gracefully. The [`ProcessExited`](#codeprocessexitedcode) event will be
192+
dispatched if the process exits.
193+
194+
Note that [persistent processes](/docs/1/digging-deeper/child-process#persistent-processes) will restart even when you
195+
stop them manually. The only way to stop a persistent process is by quitting the application.
196+
197+
## Restarting a Child Process
198+
199+
As a convenience, you may simply restart a Child Process using the `restart` method. This may be useful in cases where
200+
the program has become unresponsive and you simply need to "reboot" it.
201+
202+
If you have a `Native\Laravel\ChildProcess` instance, you may call the `restart` method on it:
203+
204+
```php
205+
$tail->stop();
206+
```
207+
208+
Alternatively, you may use the `ChildProcess` facade to restart a process via its alias:
209+
210+
```php
211+
ChildProcess::restart('tail');
212+
```
213+
214+
## Sending input
215+
216+
There are multiple ways to provide input to your Child Process:
217+
218+
- The environment.
219+
- Arguments to the command.
220+
- Its standard input stream (`STDIN`).
221+
- A custom interface, e.g. a network socket.
222+
223+
Which you use will depend on what the program is capable of handling.
224+
225+
### Environment
226+
227+
Child Processes will inherit the environment available to your application by default. If needed, you can provide extra
228+
environment variables when starting the process via the `$env` parameter of the `start` method:
229+
230+
```php
231+
ChildProcess::start(
232+
cmd: 'tail ...',
233+
alias: 'tail',
234+
env: [
235+
'CUSTOM_ENV_VAR' => 'custom value',
236+
]
237+
);
238+
```
239+
240+
### Command line arguments
241+
242+
You can pass arguments to the program via the `$cmd` parameter of the `start` method. This accepts a `string` or an
243+
`array`, whichever you prefer to use:
244+
245+
```php
246+
ChildProcess::start(
247+
cmd: ['tail', '-f', 'storage/logs/laravel.log'],
248+
alias: 'tail'
249+
);
250+
```
251+
252+
### Messaging a Child Process
253+
254+
You may send messages to a running child process's standard input stream (`STDIN`) using the `message` method:
255+
256+
```php
257+
$tail->message('Hello, world!');
258+
```
259+
260+
Alternatively, you may use the `ChildProcess` facade to message a process via its alias:
261+
262+
```php
263+
ChildProcess::message('Hello, world!', 'tail');
264+
```
265+
266+
The message format and how they are handled will be determined by the program you're running.
267+
268+
## Handling output
269+
270+
A Child Process may send output via any of the following interfaces:
271+
272+
- Its standard output stream (`STDOUT`).
273+
- Its standard error stream (`STDERR`).
274+
- A custom interface, e.g. a network socket.
275+
- Broadcasting a Custom Event
276+
277+
`STDOUT`, `STDERR` & [Custom Events](/docs/1/digging-deeper/broadcasting#custom-events) are dispatched using
278+
Laravel's event system.
279+
280+
You may listen to these events by registering a listener in your app service provider, or on the front end
281+
using the [Native helper](/docs/1/digging-deeper/broadcasting#listening-with-javascript).
282+
283+
Please see the [Events](#events) section for a full list of events.
284+
285+
### Listening for Output (`STDOUT`)
286+
287+
You may receive standard output for a process by registering an event listener for the
288+
[`MessageReceived`](#codemessagereceivedcode) event:
289+
290+
### Listening for Errors (`STDERR`)
291+
292+
You may receive standard errors for a process by registering an event listener for the
293+
[`ErrorReceived`](#codeerrorreceivedcode) event:
294+
295+
## Events
296+
297+
NativePHP provides a simple way to listen for Child Process events.
298+
299+
All events get dispatched as regular Laravel events, so you may use your `AppServiceProvider` to register listeners.
300+
301+
```php
302+
use Illuminate\Support\Facades\Event;
303+
use Native\Laravel\Events\ChildProcess\MessageReceived;
304+
305+
/**
306+
* Bootstrap any application services.
307+
*/
308+
public function boot(): void
309+
{
310+
Event::listen(MessageReceived::class, function(MessageReceived $event) {
311+
if ($event->alias === 'tail') {
312+
//
313+
}
314+
});
315+
}
316+
317+
```
318+
319+
Sometimes you may want to listen and react to these events in real-time, which is why NativePHP also broadcasts all
320+
Child Process events to the `nativephp` broadcast channel. Any events broadcasted this way also get dispatched over IPC, enabling you to react to them on the front-end without using websockets.
321+
322+
```js
323+
Native.on("Native\\Laravel\\Events\\ChildProcess\\MessageReceived", (event) => {
324+
if (event.alias === "tail") {
325+
container.append(event.data);
326+
}
327+
});
328+
```
329+
330+
To learn more about NativePHP's broadcasting capabilities, please refer to the
331+
[Broadcasting](/docs/digging-deeper/broadcasting) section.
332+
333+
### `ProcessSpawned`
334+
335+
This `Native\Laravel\Events\ChildProcess\ProcessSpawned` event will be dispatched when a Child Process has successfully
336+
been spawned. The payload of the event contains the `$alias` and the `$pid` of the process.
337+
338+
**In Electron, the `$pid` here will be the Process ID of an Electron Helper process which spawns the underlying
339+
process.**
340+
341+
### `ProcessExited`
342+
343+
This `Native\Laravel\Events\ChildProcess\ProcessExited` event will be dispatched when a Child Process exits. The
344+
payload of the event contains the `$alias` of the process and its exit `$code`.
345+
346+
### `MessageReceived`
347+
348+
This `Native\Laravel\Events\ChildProcess\MessageReceived` event will be dispatched when the Child Process emits some
349+
output via its standard output stream (`STDOUT`). The payload of the event contains the `$alias` of the process and the
350+
message `$data`.
351+
352+
### `ErrorReceived`
353+
354+
This `Native\Laravel\Events\ChildProcess\ErrorReceived` event will be dispatched when the Child Process emits an error
355+
via its standard error stream (`STDERR`). The payload of the event contains the `$alias` of the process and the
356+
error `$data`.

0 commit comments

Comments
 (0)