Download the PHP package shawty81/multiprocessor without Composer

On this page you can find all versions of the php package shawty81/multiprocessor. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package multiprocessor

MultiProcessor

Process a large dataset in parallel by handing chunks of it to forked child processes.

The parent keeps a pool of children busy, hands each one a chunk of your data, and reaps them as they finish. Because every chunk is processed in a process of its own, a fatal error, a segfault or an out of memory kill in one chunk cannot take the run down: the parent notices the child died, retries the chunk, and carries on with the rest.

This is a command line tool. It forks with pcntl_fork() and signals with posix_kill(), so it needs ext-pcntl and ext-posix and must not be run under a web server, PHP-FPM or any other environment where the process is not yours to fork.

Requirements

Installation

Quick start

Two interfaces make up everything you write: an iterator that hands out chunks of work, and a child processor that does the work for one chunk. ArrayIterator ships with the library, so this runs as it stands.

Run it and you get four children resizing five images each, so the twenty quarter-second jobs take about a second and a quarter instead of five seconds:

The usleep() is there because forking is only worth it when a record costs real time. Forking is not free puts a number on that.

ArrayIterator accepts any array. A plain list, an associative array, rows keyed by their database id, the output of array_filter(), an array with gaps left by unset(), one holding null values: all of them hand out every record they contain.

What it does not carry through is the keys. A chunk holds values, in the array's own order, so rows keyed by their database id arrive without those ids. When a key means something, put it inside the value as well and the child processor will have it.

The two interfaces you implement

IteratorInterface

Lives entirely in the parent, except for dropConnections(). It is the single source of work: the parent asks it for a chunk, forks a child for it, and asks for the next one.

Method Runs in What it is for
init(): void parent Called once before any forking. Open your connections, seek to the start of your file, prepare your query.
getChunk(int $size): Chunk parent Return the next $size records wrapped in a Chunk. Return a Chunk with an empty data array to say there is nothing left; that is what ends the run.
getNumberOfChunks(int $chunkSize): int parent Called once, after init(). The number is logged so a run can be followed. Nothing depends on it being exact, so it is fine to estimate it, or to return 0 when counting would be expensive.
dropConnections(): void child Called in the freshly forked child, before the chunk is processed. See below.
finish(): void parent Called once after the last chunk has been processed. Close what init() opened.

dropConnections() exists because a fork copies the process, file descriptors and all. A database handle that was open when the fork happened is now referred to by two processes that both think they own it, and they will interleave on the same socket: results arrive at the wrong process, and the connection is torn down for both when either one closes it. The fix is for the child to throw the inherited handle away and, if it needs one, connect again for itself. That is what this method is the place for.

ChildProcessorInterface

Method Runs in What it is for
init(): void parent Called once before any forking, alongside the iterator's init().
process(Chunk $chunk): void child Does the actual work for one chunk. Anything it opens belongs to that child alone and disappears with it.
finish(): void parent Called once after the last chunk has been processed.

process() should return when it is done and throw when it cannot finish. Do not call exit() yourself: the library gives the child its exit status, and it uses that status to tell the parent whether the chunk succeeded.

Chunk

A final readonly value object carrying one unit of work.

Property Type What it is
data mixed[] The records this chunk is made of, exactly as your iterator returned them.
retries int How many times this chunk has been handed to a child again after a failure. 0 on the first attempt.

Settings

Settings is a final readonly value object. Build it with named arguments; only the first two have no default.

Setting Type Default What it does
iterator IteratorInterface required Where the chunks come from.
childProcessor ChildProcessorInterface required What a child does with a chunk.
logger ?LoggerInterface null The PSR-3 logger the MultiProcessor itself reports to. With null it stays silent.
maxChildren int 1 How many children may run at once. The default of 1 means no parallelism at all, so this is the setting to raise first. Must be at least 1.
chunkSize int 10 How many records one child gets. Passed straight to getChunk(). Must be at least 1.
retryOnFatal bool true Whether a chunk whose child died is handed out again.
maxRetries int 1 How many extra attempts a chunk gets. The default gives every chunk two attempts in total. Must be 0 or more.
exitOnFatal bool false Whether the first dead child aborts the whole run.

A value outside those bounds throws InvalidSettingsException from the constructor, so a misconfigured run fails before anything is forked.

Forking is not free

Every chunk costs one pcntl_fork() and one reap, and that cost is roughly fixed however much or little the chunk goes on to do. On this machine it is about 390 microseconds per chunk, measured by running 20000 records of trivial work (one multiplication each) through the library at maxChildren: 8 and varying only chunkSize:

chunkSize Total Per record Per chunk
1 7.74 s 387 us 387 us
10 0.78 s 38.8 us 388 us
100 0.08 s 4.0 us 398 us
1000 0.01 s 0.5 us 520 us

The same 20000 records in a plain foreach take 0.0001 s in total, about 0.007 us each.

Measured on 16 cores under the php:8.5-cli image, so treat the numbers as indicative of the shape rather than as a guarantee: the constant differs per machine, kernel and container runtime, but it is a constant either way.

What it implies:

What happens when a child fails

A child fails in one of two ways, and the parent treats them the same.

Either way the chunk that child was holding is then handled like this:

  1. If exitOnFatal is true, the run stops immediately: every remaining child is killed with SIGKILL and the parent exits. finish() is not called and no summary is logged.
  2. Otherwise, if retryOnFatal is false, the chunk is dropped and the run continues.
  3. Otherwise, if the chunk has already been retried maxRetries times, the parent logs Chunk from Child (pid: ...) failed N times, giving up on it., drops it, and continues.
  4. Otherwise the chunk goes back on the queue with its retries count raised by one, and is handed to the next free child.

Retried chunks are taken from the queue before new ones are read from the iterator, so a failure is retried promptly rather than at the end of the run.

Note the order: exitOnFatal is checked first, so setting it to true disables retrying whatever retryOnFatal says.

A run that a child cannot be forked for at all is not recoverable, and the parent throws ForkFailedException.

Signals

The parent installs handlers for SIGINT, SIGTERM and SIGCHLD.

SIGINT (Ctrl-C) or SIGTERM on the parent starts an orderly shutdown: it logs what it is doing, sends SIGKILL to every child still in the pool, and exits. It does not wait for children to finish their chunk, and it does not call finish() or log a summary. The handlers are installed so that they interrupt the blocking wait the parent spends nearly all of its time in, so the shutdown happens at once rather than when a child next exits.

Children inherit those handlers across the fork but have no business shutting the run down on the parent's behalf. So the first thing a child does with an inherited signal is put the default handler back and raise the signal on itself again, which leaves it dying exactly as it would have without the fork. A child of your own that you fork inside process() is unaffected by any of this.

Logging

Anything implementing PSR-3's LoggerInterface will do. Pass it as the logger setting, or set it afterwards, since MultiProcessor implements LoggerAwareInterface:

That logger is the MultiProcessor's own. Your iterator and child processor log through whatever you give them; the quick start passes the same instance to both.

CommandLineLogger is bundled for when you just want to see the run on your terminal. It writes one line per message, prefixed with the time and the first letter of the level, and fills in the PSR-3 {placeholders} from the context:

Exceptions

Every exception this library throws implements MultiProcessor\Exception\ExceptionInterface, so one catch covers all of it:

Exception Extends Thrown when
InvalidSettingsException InvalidArgumentException Settings is given a chunkSize or maxChildren below 1, or a negative maxRetries.
ForkFailedException RuntimeException pcntl_fork() failed, so no child could be started.

Exceptions your own code throws are not caught by the library in the parent. In a child they are caught, logged and turned into a failed chunk, as described above.

License

MIT. See LICENSE.


All versions of multiprocessor with dependencies

PHP Build Version
Package Version
Requires psr/log Version ^1.1
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package shawty81/multiprocessor contains the following files

Loading the files please wait ...