skip to content

In Laravel's Process facade, when do you use start(), Process::pool() or Process::pipe() instead of run(), and what does each give back?

level: middleimportance: should knowfreq 28%

answer

  1. start() returns an InvokedProcess
  2. running(), wait(), latestOutput()
  3. pool results keyed, as() names
  4. concurrently() = start then wait
  5. pipe stops at first failure

basics

~20 s

start() launches a command in the background and returns an InvokedProcess to poll or wait() on. Process::pool() starts several commands at once and wait() returns their results by key. Process::pipe() feeds each command's output into the next and returns one result.

solid answer

~40 s

`run()` blocks until one command ends. `start()` launches it and returns an `InvokedProcess` at once: check `running()`, read `latestOutput()`, send `signal()` or `stop()`, and call `wait()` for the `ProcessResult`; `ensureNotTimedOut()` inside a polling loop surfaces the timeout. `Process::pool(fn (Pool $pool) => ...)` defines several processes, `->start()` launches **all** of them together, and `->wait()` returns a `ProcessPoolResults` indexed by position or by `$pool->as('name')`; `Process::concurrently()` does start and wait in one call. Nothing limits how many pooled processes run at once. `Process::pipe()` runs commands one after another, feeding each one's stdout into the next one's stdin, and returns the last result — or the first failed one, since a failure stops the pipe.

code

php · 20 lines
php
<?php

use Illuminate\Process\Pool;
use Illuminate\Support\Facades\Process;

$sizes = ['small' => '150x150', 'medium' => '600x600', 'large' => '1200x1200'];

$results = Process::concurrently(function (Pool $pool) use ($sizes, $source) {
    foreach ($sizes as $name => $geometry) {
        $pool->as($name)->timeout(30)->command([
            'magick', $source, '-thumbnail', $geometry, storage_path("app/thumbs/{$name}.jpg"),
        ]);
    }
});

foreach ($sizes as $name => $geometry) {
    if ($results[$name]->failed()) {
        logger()->warning("Thumbnail {$name} failed", ['error' => $results[$name]->errorOutput()]);
    }
}

go deeper

for a junior

Remember that run() waits and start() does not, that pool() runs several commands together, and that pipe() chains one command's output into the next.

for a middle

Explain InvokedProcess methods, how pool results are keyed and aggregated, why concurrently() exists, and how a pipe behaves when a middle stage fails.

for a senior

Show you control resource use: pools have no cap, started processes need wait() or ensureNotTimedOut(), and heavy batches belong in chunks or queued jobs.

for a principal

Judge when parallel processes inside one request or job are worth it versus spreading the work across queue workers that you can scale and monitor separately.

## run() versus start() Both methods live on the pending process that the `Process` facade builds, and both accept the same options (`timeout()`, `path()`, `env()`, `input()`). | Method | Blocks? | Returns | Use it for | |---|---|---|---| | `run($cmd)` | yes, until exit | `ProcessResult` | a quick command whose result you need now | | `start($cmd)` | no | `InvokedProcess` | doing other work while the command runs | | `Process::pool($cb)->start()` | no | `InvokedProcessPool` | several independent commands at once | | `Process::concurrently($cb)` | yes, until all exit | `ProcessPoolResults` | the same, when you only need the results | | `Process::pipe($cb or [...])` | yes | `ProcessResult` | chaining stdout of one command into the next | ## Working with a started process `start()` returns an `Illuminate\Process\InvokedProcess` immediately. While it runs you can: - check `running()`; - read `output()` and `errorOutput()` for everything so far, or `latestOutput()` and `latestErrorOutput()` for what arrived since the last read; - get the operating-system process id with `id()`, send a signal with `signal(SIGTERM)`, or `stop()` it; - call `wait()` to block until it ends and receive the `ProcessResult`, or `waitUntil($callback)` to stop waiting once the output matches something, such as a "Ready" line. Timeouts for started processes are raised as `ProcessTimedOutException` from `wait()`, `waitUntil()` or `ensureNotTimedOut()`. That is why the documentation places `ensureNotTimedOut()` inside a `while ($process->running())` loop. Both `run()` and `start()` also accept a closure as their second argument; it receives the output type (`stdout` or `stderr`) and each chunk as it arrives, which is handy for streaming progress into a console command. ## Pools: several processes at once `Process::pool()` takes a closure receiving an `Illuminate\Process\Pool`. Each `$pool->command(...)`, or `$pool->as('small')->timeout(30)->command(...)`, registers a pending process. 1. `->start()` launches **every** registered process at the same moment and returns an `InvokedProcessPool`; its `running()` gives a collection of the processes still alive. 2. `->wait()` waits for each one and returns an `Illuminate\Process\ProcessPoolResults`, which is array-accessible by position or by the `as()` name. 3. `$results->successful()` is true only when every process succeeded; a non-zero exit in one member does not throw, although a member that times out still raises `ProcessTimedOutException` from `wait()`. 4. `Process::concurrently($callback)` is shorthand for pool, start and wait in one call, and pairs well with array destructuring. There is **no concurrency cap**: a pool built from 200 images starts 200 processes. For large batches, split the work into chunks or into queued jobs. ## Pipes `Process::pipe()` accepts either a closure receiving an `Illuminate\Process\Pipe` or a plain array of command strings. It runs the commands **one after another**, handing each command's stdout to the next as stdin, and returns the final `ProcessResult`. If a command in the middle fails, the remaining commands are skipped and **that failed result** is returned, so `failed()` on the pipe's result tells you the chain broke somewhere. `$pipe->as('resize')` names a stage so an output closure can tell stages apart. ## Thumbnails in three sizes A pool fits "make three thumbnail sizes of one upload": the conversions are independent, CPU-heavy, and each can run in its own process. Register one `magick` command per size under `as('small')`, `as('medium')` and `as('large')`, give each its own `timeout()`, call `Process::concurrently()`, and read `$results['small']`, `$results['medium']` and `$results['large']` by name, checking `failed()` and `errorOutput()` on each. ## Choosing between them - One short command whose result decides what happens next: `run()`. - One long command while the PHP code keeps working, reports progress or watches for a "ready" line: `start()` with `latestOutput()` or `waitUntil()`. - Several independent commands that may overlap, such as one conversion per image size: `Process::pool()` or `Process::concurrently()`. - A chain where each command consumes the previous one's output, and you want to know which stage failed: `Process::pipe()`. - Hundreds of commands, or work that must survive a crash: not a pool at all, but queued jobs, so a fixed number of workers bounds the load. ## Pitfalls - Starting a process and never calling `wait()`: you get no result and no timeout exception. - Assuming a pool throttles itself; it starts everything it holds. - Expecting a pool to throw when one member fails — inspect each result or `successful()`. - Using a shell string with `|` when `Process::pipe()` would give you per-stage results without a shell.

  • How do you stop a started Laravel process that is taking too long?
    Keep the `InvokedProcess` from `start()` and call `stop()`, which ends it (with an optional grace period and signal), or `signal(SIGTERM)` to ask it to exit. With a timeout set, calling `ensureNotTimedOut()` while polling raises `ProcessTimedOutException` once the limit passes.
  • Why might Process::pipe() return a result from the middle of the chain?
    The pipe skips the remaining commands as soon as one fails and returns that failed `ProcessResult`. Its `command()`, `exitCode()` and `errorOutput()` tell you which stage broke.
  • Does Process::pool() limit how many processes run at the same time?
    No. `start()` launches every process registered in the pool immediately. To bound CPU use, split the work into several smaller pools or dispatch it as queued jobs processed by a fixed number of workers.

saying these in an interview costs you the question

  • start() blocks until the command finishes, just like run().
  • Process::pool() runs a few processes at a time and queues the rest.
  • A pool throws as soon as any one of its processes fails.
  • Process::pipe() runs all its commands at the same time.
  • A started process needs no wait() call to report its timeout.