A lightweight solution for running code concurrently in PHP

Overview

A lightweight solution for running PHP code concurrently

Latest Version on Packagist Tests GitHub Code Style Action Status Total Downloads

This package makes it easy to run PHP concurrently. Behind the scenes, concurrency is achieved by forking the main PHP process to one or more child tasks.

In this example, where we are going to call an imaginary slow API, all three closures will run at the same time.

use Spatie\Fork\Fork;

$results = Fork::new()
    ->run(
        fn () => (new Api)->fetchData(userId: 1),
        fn () => (new Api)->fetchData(userId: 2),
        fn () => (new Api)->fetchData(userId: 3),
    );

$results[0]; // fetched data of user 1
$results[1]; // fetched data of user 2
$results[2]; // fetched data of user 3

How it works under the hood

In this video on YouTube, we explain how the package works internally.

Support us

We invest a lot of resources into creating best in class open source packages. You can support us by buying one of our paid products.

We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on our contact page. We publish all received postcards on our virtual postcard wall.

Requirements

This package requires PHP 8 and the pcntl extensions which is installed in many Unix and Mac systems by default.

❗️ pcntl only works in CLI processes, not in a web context.

Installation

You can install the package via composer:

composer require spatie/fork

Usage

You can pass as many closures as you want to run. They will be run concurrently. The run function will return an array with the return values of the executed closures.

use Spatie\Fork\Fork;

$results = Fork::new()
    ->run(
        function ()  {
            sleep(1);

            return 'result from task 1';
        },
        function ()  {
            sleep(1);

            return 'result from task 2';
        },
        function ()  {
            sleep(1);

            return 'result from task 3';
        },
    );

// this code will be reached this point after 1 second
$results[0]; // contains 'result from task 1'
$results[1]; // contains 'result from task 2'
$results[2]; // contains 'result from task 3'

Running code before and after each closure

If you need to execute some code before or after each callable passed to run, you can pass a callable to before or after methods. This callable passed will be executed in the child process right before or after the execution of the callable passed to run.

Using before and after in the child task

Here's an example where we are going to get a value from the database using a Laravel Eloquent model. In order to let the child task use the DB, it is necessary to reconnect to the DB. The closure passed to before will run in both child tasks that are created for the closures passed to run.

use App\Models\User;
use Illuminate\Support\Facades\DB;
use Spatie\Fork\Fork;

 Fork::new()
    ->before(fn () => DB::connection('mysql')->reconnect())
    ->run(
        fn () => User::find(1)->someLongRunningFunction(),
        fn () => User::find(2)->someLongRunningFunction(),
    );

If you need to perform some cleanup in the child task after the callable has run, you can use the after method on a Spatie\Fork\Fork instance.

Using before and after in the parent task.

If you need to let the callable passed to before or after run in the parent task, then you need to pass that callable to the parent argument.

use App\Models\User;
use Illuminate\Support\Facades\DB;
use Spatie\Fork\Fork;

 Fork::new()
    ->before(
        parent: fn() => echo 'this runs in the parent task'
    )
    ->run(
        fn () => User::find(1)->someLongRunningFunction(),
        fn () => User::find(2)->someLongRunningFunction(),
    );

You can also pass different closures, to be run in the child and the parent task

use Spatie\Fork\Fork;

Fork::new()
    ->before(
        child: fn() => echo 'this runs in the child task',
        parent: fn() => echo 'this runs in the parent task',
    )
    ->run(
        fn () => User::find(1)->someLongRunningFunction(),
        fn () => User::find(2)->someLongRunningFunction(),
    );

Returning data

All output data is gathered in an array and available as soon as all children are done. In this example, $results will contain three items:

$results = Fork::new()
    ->run(
        fn () => (new Api)->fetchData(userId: 1),
        fn () => (new Api)->fetchData(userId: 2),
        fn () => (new Api)->fetchData(userId: 3),
    );

The output is also available in the after callbacks, which are called whenever a child is done and not at the very end:

$results = Fork::new()
    ->after(
        child: fn (int $i) => echo $i, // 1, 2 and 3
        parent: fn (int $i) => echo $i, // 1, 2 and 3
    )
    ->run(
        fn () => 1,
        fn () => 2,
        fn () => 3,
    );

Finally, return values from child tasks are serialized using PHP's built-in serialize method. This means that you can return anything you can normally serialize in PHP, including objects:

$result = Fork::new()
    ->run(
        fn () => new DateTime('2021-01-01'),
        fn () => new DateTime('2021-01-02'),
    );

Configuring concurrency

By default, all callables will be run in parallel. You can however configure a maximum amount of concurrent processes:

$results = Fork::new()
    ->concurrent(2)
    ->run(
        fn () => 1,
        fn () => 2,
        fn () => 3,
    );

In this case, the first two functions will be run immediately and as soon as one of them finishes, the last one will start as well.

Testing

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.

Comments
  • Endless loop outside of tests

    Endless loop outside of tests

    Sorry to bother you guys. I'm actively looking for a fix for this and trying a bunch of stuff but a problem shared...

    So, I'm using a MacBook Pro 2020 Intel variant. I run Laravel locally using Valet. My test suite is obviously using PHP to run as you'd expect. I'm on the latest version of valet and I'm using PHP 8.0.5.

    When I run my test suite, I get the expected result:

    $test = Fork::new()->run(fn() => 'Hello', fn() => 'World');
    dd($test);
    
    Screenshot 2021-04-30 at 22 07 36

    So far, so breezy. However, if I switch to the browser and hit a route that calls this code, the dd never takes place.

    I've done some debugging (mainly by placing ray calls at various points in the Fork class), and the issue seems to stem from the call to pcntl_waitpid in Task on line 133. For whatever reason, in the browser, this always returns a status code of 0. The output on line 131 gets set correctly, but it still doesn't return the PID to say it has completed the closure.

    Thanks for all your hard work! I'll keep looking into this from my end.

    opened by lukeraymonddowning 7
  • Allow limiting the number of concurrent processes

    Allow limiting the number of concurrent processes

    Love this package. However, when testing it for speeding up some of our tooling, I noticed that with performance degraded quickly with a larger number of processes. This makes sense, since forking a process hundreds or thousands of times is an expensive operation.

    The feature allows limiting that concurrency, so fewer concurrent processes are forked. This drastically reduces CPU usage, since it doesn't have to manage so many processes and sockets.

    During my testing, running 1000 concurrent tasks (that simply returned foo), took ~20 seconds. Limiting it to 100 concurrent processes reduced that to 14 seconds.

    Possible Improvements

    As implemented, it waits until all tasks of a concurrent group are completed before it starts the next group. It may be better to kick off a group, and as each task completes, start the next task until all are complete. However, this complicates things, and I am not convinced it would be any faster. Though, that is a gut feeling, not backed by any tests.

    Open to suggestions.

    opened by dhrrgn 6
  • [ErrorException] unserialize(): Error at offset 219249 of 219250 bytes

    [ErrorException] unserialize(): Error at offset 219249 of 219250 bytes

    I'm trying to return an key-val array with UTF-8 characters. When running without, it outputs the array fine. Running with 2 files (0.556048 mb) it works fine.

    However, when running with 3+ files, 0.78592 mb, I get the unserialize error.

    Run Code
    
    // Get Files
    $files = Storage::files();
    
    // Returns a key-val array 
    $results = Fork::new()
        >run(
             fn() => $this->filesToWordFreq(array_slice($files, 0, 3)),
          );
    

    With the debug below.

       ErrorException 
    
      unserialize(): Error at offset 219249 of 219250 bytes
    
      at vendor/spatie/fork/src/Task.php:115
        111▕         $output = $this->output;
        112▕ 
        113▕         if (str_starts_with($output, self::SERIALIZATION_TOKEN)) {
        114▕             $output = unserialize(
      ➜ 115▕                 substr($output, strlen(self::SERIALIZATION_TOKEN))
        116▕             );
        117▕         }
        118▕ 
        119▕         return $output;
    
    

    I've already increased my php memory limit, with ini_set('memory_limit', '16384M'); earlier.

    Is there a hard limit to output size when using spatie/fork? As I was planning to run this with 27k files, not 3 just 3.

    EDIT

    When dumping the $output in output(), the value returned has been cut off. Does the output concatenation have some form of memory limit?

            foreach ($this->connection->read() as $output) {
                $this->output .= $output;
            }
    

    Dumping the variable in execute() has the entire string, so something goes wrong after the execute() function.

    opened by MeikyuuTrader 4
  • fixes socket_select interrupted exception

    fixes socket_select interrupted exception

    As described in #31, when using pcntl_signal, socket_select may get interrupted which will cause an ErrorException being thrown.

    The message is: socket_select(): unable to select [4]: Interrupted system call.

    With this fix, the ErrorException is catched, and when the error number is 4 (the interrupted system call), the while loop is continued and the socket_select is run again.

    I have no tests, as this is very difficult to test as this appears to be occurring randomly. I have tested it manually however and the error seems to be gone. Also the existing unit tests still succeeds.

    opened by henzeb 1
  • improve README.md

    improve README.md

    This PR:

    • removes trailing and additional whitespaces
    • improves wording
    • fixes typo

    In special, the actual README says The closures to run shouldn't return objects, only primitives and arrays are allowed. However, it also says Finally, return values from child tasks are serialized using PHP's built-in serialize method. This means that you can return anything you can normally serialize in PHP, including objects.

    So I guess we can remove that first statement, right?

    opened by chapeupreto 1
  • Undefined array key 51266

    Undefined array key 51266

    Undefined array key 51266 vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php or this , Undefined array key 37790 at vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php : i get this errors when i try to excute from get api request but when i make a laravel consol command its works fine why is it ?

    opened by mahmoud-adel44 0
  • using fork with pcntl_signal/pcntl_alarm

    using fork with pcntl_signal/pcntl_alarm

    When using Fork together with pcntl calls like pcntl_signal (SIGTERM/SIGINT) or pcntl_alarm, I occasionally get the following error:

    socket_select(): Unable to select [4]: Interrupted system call

    This is a known issue and the common solution is to check for this error and retry again.

    opened by henzeb 0
  • Bug with unserialize big json from api with 200 items

    Bug with unserialize big json from api with 200 items

    I'm using the following code:

    
    use Curl\Curl;
    use Spatie\Fork\Fork;
    
    $output = Fork::new()->run(
        function () {
            $curl = new Curl();
            return $curl->get('https://jsonplaceholder.typicode.com/todos');
        }
    );
    
    var_dump($output);
    
    

    When getting all the todo's from the api it will throw an exception: Notice: unserialize(): Error at offset 8178 of 8178 bytes .... /src/Task.php line 115

    I already have increased the buffer size(tried increasing until the max) but still got this problem/bug. When i slice the array of items to 60 instead of 200 that come from that response.

    Is there a solution for this problem/bug?

    opened by RemcoSmitsDev 0
Releases(1.1.2)
Owner
Spatie
We create products and courses for the developer community
Spatie
The package contains a bootstrap for running Yii3 web application.

Yii Web Runner The package contains a bootstrap for running Yii3 web application. Requirements PHP 8.0 or higher. Installation The package could be in

Yii Software 4 Oct 15, 2022
Mind is the PHP code framework designed for developers. It offers a variety of solutions for creating design patterns, applications and code frameworks.

Mind Mind is the PHP code framework designed for developers. It offers a variety of solutions for creating design patterns, applications and code fram

null 0 Dec 13, 2021
Woski is a fast and simple lightweight PHP Framework for building applications in the realm of the web.

Woski is a simple fast PHP framework for the Realm The Project Installation Clone the repository $ composer create-project clintonnzedimma/woski myApp

Clinton Nzedimma 19 Aug 15, 2022
TrailLamp is a lightweight, easy-to-use Php MVC framework that can be used to build web applications and REST APIs.

TrailLamp Introduction TrailLamp is a lightweight, easy-to-use Php MVC framework that can be used to build web applications and REST APIs. Installatio

Etorojah Okon 14 Jun 10, 2022
A Faster Lightweight Full-Stack PHP Framework

A Faster Lightweight Full-Stack PHP Framework 中文版  Docker env Just one command to build all env for the easy-php How to build a PHP framework by ourse

Zhan Shi 769 Dec 8, 2022
A super fast, customizable and lightweight PHP MVC Starter Framework to extend for your own...

PHPMVC A super fast, customizable and lightweight PHP MVC Starter Framework to extend for your own... How to Start Clone this repo - git clone https:/

Maniruzzaman Akash 9 Dec 11, 2022
A modern, ultra lightweight and rocket fast Content Management System

Redaxscript A modern, ultra lightweight and rocket fast Content Management System for SQLite, MSSQL, MySQL and PostgreSQL. Installation Clone the repo

redaxscript 247 Nov 12, 2022
Kit is a lightweight, high-performance and event-driven web services framework that provides core components such as config, container, http, log and route.

Kit What is it Kit is a lightweight, high-performance and event-driven web services framework that provides core components such as config, container,

null 2 Sep 23, 2022
Low-code Framework for Web Apps in PHP

Agile UI - User Interface framework for Agile Toolkit Agile Toolkit is a Low Code framework written in PHP. Agile UI implement server side rendering e

Agile Toolkit 404 Jan 8, 2023
Source Code for 'Pro PHP 8 MVC' by Christopher Pitt

Apress Source Code This repository accompanies Pro PHP 8 MVC by Christopher Pitt (Apress, 2021). Download the files as a zip using the green button, o

Apress 27 Dec 25, 2022
Source code of Ice framework

Ice framework Simple and fast PHP framework delivered as C-extension. Stage How to contribute? Fork the ice/framework repository. Create a new branch

ice framework 340 Nov 15, 2022
An intelligent code generator for Laravel framework that will save you time

An intelligent code generator for Laravel framework that will save you time! This awesome tool will help you generate resources like views, controllers, routes, migrations, languages and/or form-requests! It is extremely flexible and customizable to cover many of the use cases. It is shipped with cross-browsers compatible template, along with a client-side validation to modernize your application.

CrestApps 621 Jan 8, 2023
This repository holds the code and script for the Symfony5 Tutorials on SymfonyCasts.

Tutorials, Friendship & Symfony5 Well hi there! This repository holds the code and script for the Symfony5 Tutorials on SymfonyCasts. Setup If you've

null 1 Nov 20, 2021
Code generation with logic-less templates for Yii

Caviar Code generation with logic-less templates for Yii. Caviar vs Gii You might be wondering why you should use Caviar instead of Gii, so let us tak

Christoffer Niska 10 Dec 19, 2015
PHP Kafka client is used in PHP-FPM and Swoole. PHP Kafka client supports 50 APIs, which might be one that supports the most message types ever.

longlang/phpkafka Introduction English | 简体中文 PHP Kafka client is used in PHP-FPM and Swoole. The communication protocol is based on the JSON file in

Swoole Project 235 Dec 31, 2022
FuelPHP v1.x is a simple, flexible, community driven PHP 5.3+ framework, based on the best ideas of other frameworks, with a fresh start! FuelPHP is fully PHP 7 compatible.

FuelPHP Version: 1.8.2 Website Release Documentation Release API browser Development branch Documentation Development branch API browser Support Forum

Fuel 1.5k Dec 28, 2022
An asynchronous event driven PHP socket framework. Supports HTTP, Websocket, SSL and other custom protocols. PHP>=5.3.

Workerman What is it Workerman is an asynchronous event-driven PHP framework with high performance to build fast and scalable network applications. Wo

walkor 10.2k Dec 31, 2022
A multithreaded application server for PHP, written in PHP.

appserver.io, a PHP application server This is the main repository for the appserver.io project. What is appserver.io appserver.io is a multithreaded

appserver.io 951 Dec 25, 2022
Fast php framework written in c, built in php extension

Yaf - Yet Another Framework PHP framework written in c and built as a PHP extension. Requirement PHP 7.0+ (master branch)) PHP 5.2+ (php5 branch) Inst

Xinchen Hui 4.5k Dec 28, 2022