LaravelS is an out-of-the-box adapter between Swoole and Laravel/Lumen.

Overview
 _                               _  _____ 
| |                             | |/ ____|
| |     __ _ _ __ __ ___   _____| | (___  
| |    / _` | '__/ _` \ \ / / _ \ |\___ \ 
| |___| (_| | | | (_| |\ V /  __/ |____) |
|______\__,_|_|  \__,_| \_/ \___|_|_____/ 
                                           

🚀 LaravelS is an out-of-the-box adapter between Swoole and Laravel/Lumen.

Please Watch this repository to get the latest updates.

Latest Release PHP Version Swoole Version Total Downloads License Build Status Code Intelligence Status

中文文档

Table of Contents

Features

Benchmark

Requirements

Dependency Requirement
PHP >= 5.5.9 Recommend PHP7+
Swoole >= 1.7.19 No longer support PHP5 since 2.0.12 Recommend 4.5.0+
Laravel/Lumen >= 5.1 Recommend 8.0+

Install

1.Require package via Composer(packagist).

composer require "hhxsv5/laravel-s:~3.7.0" -vvv
# Make sure that your composer.lock file is under the VCS

2.Register service provider(pick one of two).

  • Laravel: in config/app.php file, Laravel 5.5+ supports package discovery automatically, you should skip this step

    'providers' => [
        //...
        Hhxsv5\LaravelS\Illuminate\LaravelSServiceProvider::class,
    ],
  • Lumen: in bootstrap/app.php file

    $app->register(Hhxsv5\LaravelS\Illuminate\LaravelSServiceProvider::class);

3.Publish configuration and binaries.

After upgrading LaravelS, you need to republish; click here to see the change notes of each version.

php artisan laravels publish
# Configuration: config/laravels.php
# Binary: bin/laravels bin/fswatch bin/inotify

4.Change config/laravels.php: listen_ip, listen_port, refer Settings.

5.Performance tuning

  • Adjust kernel parameters

  • Number of Workers: LaravelS uses Swoole's Synchronous IO mode, the larger the worker_num setting, the better the concurrency performance, but it will cause more memory usage and process switching overhead. If one request takes 100ms, in order to provide 1000QPS concurrency, at least 100 Worker processes need to be configured. The calculation method is: worker_num = 1000QPS/(1s/1ms) = 100, so incremental pressure testing is needed to calculate the best worker_num.

  • Number of Task Workers

Run

Please read the notices carefully before running, Important notices(IMPORTANT).

  • Commands: php bin/laravels {start|stop|restart|reload|info|help}.
Command Description
start Start LaravelS, list the processes by "ps -ef|grep laravels"
stop Stop LaravelS, and trigger the method onStop of Custom process
restart Restart LaravelS: Stop gracefully before starting; The service is unavailable until startup is complete
reload Reload all Task/Worker/Timer processes which contain your business codes, and trigger the method onReload of Custom process, CANNOT reload Master/Manger processes. After modifying config/laravels.php, you only have to call restart to restart
info Display component version information
help Display help information
  • Boot options for the commands start and restart.
Option Description
-d|--daemonize Run as a daemon, this option will override the swoole.daemonize setting in laravels.php
-e|--env The environment the command should run under, such as --env=testing will use the configuration file .env.testing firstly, this feature requires Laravel 5.2+
-i|--ignore Ignore checking PID file of Master process
-x|--x-version The version(branch) of the current project, stored in $_ENV/$_SERVER, access via $_ENV['X_VERSION'] $_SERVER['X_VERSION'] $request->server->get('X_VERSION')
  • Runtime files: start will automatically execute php artisan laravels config and generate these files, developers generally don't need to pay attention to them, it's recommended to add them to .gitignore.
File Description
storage/laravels.conf LaravelS's runtime configuration file
storage/laravels.pid PID file of Master process
storage/laravels-timer-process.pid PID file of the Timer process
storage/laravels-custom-processes.pid PID file of all custom processes

Deploy

It is recommended to supervise the main process through Supervisord, the premise is without option -d and to set swoole.daemonize to false.

[program:laravel-s-test]
directory=/var/www/laravel-s-test
command=/usr/local/bin/php bin/laravels start -i
numprocs=1
autostart=true
autorestart=true
startretries=3
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/supervisor/%(program_name)s.log

Cooperate with Nginx (Recommended)

Demo.

gzip on;
gzip_min_length 1024;
gzip_comp_level 2;
gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml;
gzip_vary on;
gzip_disable "msie6";
upstream swoole {
    # Connect IP:Port
    server 127.0.0.1:5200 weight=5 max_fails=3 fail_timeout=30s;
    # Connect UnixSocket Stream file, tips: put the socket file in the /dev/shm directory to get better performance
    #server unix:/yourpath/laravel-s-test/storage/laravels.sock weight=5 max_fails=3 fail_timeout=30s;
    #server 192.168.1.1:5200 weight=3 max_fails=3 fail_timeout=30s;
    #server 192.168.1.2:5200 backup;
    keepalive 16;
}
server {
    listen 80;
    # Don't forget to bind the host
    server_name laravels.com;
    root /yourpath/laravel-s-test/public;
    access_log /yourpath/log/nginx/$server_name.access.log  main;
    autoindex off;
    index index.html index.htm;
    # Nginx handles the static resources(recommend enabling gzip), LaravelS handles the dynamic resource.
    location / {
        try_files $uri @laravels;
    }
    # Response 404 directly when request the PHP file, to avoid exposing public/*.php
    #location ~* \.php$ {
    #    return 404;
    #}
    location @laravels {
        # proxy_connect_timeout 60s;
        # proxy_send_timeout 60s;
        # proxy_read_timeout 120s;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Real-PORT $remote_port;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header Scheme $scheme;
        proxy_set_header Server-Protocol $server_protocol;
        proxy_set_header Server-Name $server_name;
        proxy_set_header Server-Addr $server_addr;
        proxy_set_header Server-Port $server_port;
        # "swoole" is the upstream
        proxy_pass http://swoole;
    }
}

Cooperate with Apache

AllowOverride None Require all granted RemoteIPHeader X-Forwarded-For ProxyRequests Off ProxyPreserveHost On BalancerMember http://192.168.1.1:5200 loadfactor=7 #BalancerMember http://192.168.1.2:5200 loadfactor=3 #BalancerMember http://192.168.1.3:5200 loadfactor=1 status=+H ProxySet lbmethod=byrequests #ProxyPass / balancer://laravels/ #ProxyPassReverse / balancer://laravels/ # Apache handles the static resources, LaravelS handles the dynamic resource. RewriteEngine On RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-d RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f RewriteRule ^/(.*)$ balancer://laravels%{REQUEST_URI} [P,L] ErrorLog ${APACHE_LOG_DIR}/www.laravels.com.error.log CustomLog ${APACHE_LOG_DIR}/www.laravels.com.access.log combined ">
LoadModule proxy_module /yourpath/modules/mod_proxy.so
LoadModule proxy_balancer_module /yourpath/modules/mod_proxy_balancer.so
LoadModule lbmethod_byrequests_module /yourpath/modules/mod_lbmethod_byrequests.so
LoadModule proxy_http_module /yourpath/modules/mod_proxy_http.so
LoadModule slotmem_shm_module /yourpath/modules/mod_slotmem_shm.so
LoadModule rewrite_module /yourpath/modules/mod_rewrite.so
LoadModule remoteip_module /yourpath/modules/mod_remoteip.so
LoadModule deflate_module /yourpath/modules/mod_deflate.so

<IfModule deflate_module>
    SetOutputFilter DEFLATE
    DeflateCompressionLevel 2
    AddOutputFilterByType DEFLATE text/html text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml
IfModule>

<VirtualHost *:80>
    # Don't forget to bind the host
    ServerName www.laravels.com
    ServerAdmin [email protected]

    DocumentRoot /yourpath/laravel-s-test/public;
    DirectoryIndex index.html index.htm
    <Directory "/">
        AllowOverride None
        Require all granted
    Directory>

    RemoteIPHeader X-Forwarded-For

    ProxyRequests Off
    ProxyPreserveHost On
    <Proxy balancer://laravels>  
        BalancerMember http://192.168.1.1:5200 loadfactor=7
        #BalancerMember http://192.168.1.2:5200 loadfactor=3
        #BalancerMember http://192.168.1.3:5200 loadfactor=1 status=+H
        ProxySet lbmethod=byrequests
    Proxy>
    #ProxyPass / balancer://laravels/
    #ProxyPassReverse / balancer://laravels/

    # Apache handles the static resources, LaravelS handles the dynamic resource.
    RewriteEngine On
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-d
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f
    RewriteRule ^/(.*)$ balancer://laravels%{REQUEST_URI} [P,L]

    ErrorLog ${APACHE_LOG_DIR}/www.laravels.com.error.log
    CustomLog ${APACHE_LOG_DIR}/www.laravels.com.access.log combined
VirtualHost>

Enable WebSocket server

The Listening address of WebSocket Sever is the same as Http Server.

1.Create WebSocket Handler class, and implement interface WebSocketHandlerInterface.The instant is automatically instantiated when start, you do not need to manually create it.

namespace App\Services;
use Hhxsv5\LaravelS\Swoole\WebSocketHandlerInterface;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;
/**
 * @see https://www.swoole.co.uk/docs/modules/swoole-websocket-server
 */
class WebSocketService implements WebSocketHandlerInterface
{
    // Declare constructor without parameters
    public function __construct()
    {
    }
    // public function onHandShake(Request $request, Response $response)
    // {
           // Custom handshake: https://www.swoole.co.uk/docs/modules/swoole-websocket-server-on-handshake
           // The onOpen event will be triggered automatically after a successful handshake
    // }
    public function onOpen(Server $server, Request $request)
    {
        // Before the onOpen event is triggered, the HTTP request to establish the WebSocket has passed the Laravel route,
        // so Laravel's Request, Auth information are readable, Session is readable and writable, but only in the onOpen event.
        // \Log::info('New WebSocket connection', [$request->fd, request()->all(), session()->getId(), session('xxx'), session(['yyy' => time()])]);
        // The exceptions thrown here will be caught by the upper layer and recorded in the Swoole log. Developers need to try/catch manually.
        $server->push($request->fd, 'Welcome to LaravelS');
    }
    public function onMessage(Server $server, Frame $frame)
    {
        // \Log::info('Received message', [$frame->fd, $frame->data, $frame->opcode, $frame->finish]);
        // The exceptions thrown here will be caught by the upper layer and recorded in the Swoole log. Developers need to try/catch manually.
        $server->push($frame->fd, date('Y-m-d H:i:s'));
    }
    public function onClose(Server $server, $fd, $reactorId)
    {
        // The exceptions thrown here will be caught by the upper layer and recorded in the Swoole log. Developers need to try/catch manually.
    }
}

2.Modify config/laravels.php.

// ...
'websocket'      => [
    'enable'  => true, // Note: set enable to true
    'handler' => \App\Services\WebSocketService::class,
],
'swoole'         => [
    //...
    // Must set dispatch_mode in (2, 4, 5), see https://www.swoole.co.uk/docs/modules/swoole-server/configuration
    'dispatch_mode' => 2,
    //...
],
// ...

3.Use SwooleTable to bind FD & UserId, optional, Swoole Table Demo. Also you can use the other global storage services, like Redis/Memcached/MySQL, but be careful that FD will be possible conflicting between multiple Swoole Servers.

4.Cooperate with Nginx (Recommended)

Refer WebSocket Proxy

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}
upstream swoole {
    # Connect IP:Port
    server 127.0.0.1:5200 weight=5 max_fails=3 fail_timeout=30s;
    # Connect UnixSocket Stream file, tips: put the socket file in the /dev/shm directory to get better performance
    #server unix:/yourpath/laravel-s-test/storage/laravels.sock weight=5 max_fails=3 fail_timeout=30s;
    #server 192.168.1.1:5200 weight=3 max_fails=3 fail_timeout=30s;
    #server 192.168.1.2:5200 backup;
    keepalive 16;
}
server {
    listen 80;
    # Don't forget to bind the host
    server_name laravels.com;
    root /yourpath/laravel-s-test/public;
    access_log /yourpath/log/nginx/$server_name.access.log  main;
    autoindex off;
    index index.html index.htm;
    # Nginx handles the static resources(recommend enabling gzip), LaravelS handles the dynamic resource.
    location / {
        try_files $uri @laravels;
    }
    # Response 404 directly when request the PHP file, to avoid exposing public/*.php
    #location ~* \.php$ {
    #    return 404;
    #}
    # Http and WebSocket are concomitant, Nginx identifies them by "location"
    # !!! The location of WebSocket is "/ws"
    # Javascript: var ws = new WebSocket("ws://laravels.com/ws");
    location =/ws {
        # proxy_connect_timeout 60s;
        # proxy_send_timeout 60s;
        # proxy_read_timeout: Nginx will close the connection if the proxied server does not send data to Nginx in 60 seconds; At the same time, this close behavior is also affected by heartbeat setting of Swoole.
        # proxy_read_timeout 60s;
        proxy_http_version 1.1;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Real-PORT $remote_port;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header Scheme $scheme;
        proxy_set_header Server-Protocol $server_protocol;
        proxy_set_header Server-Name $server_name;
        proxy_set_header Server-Addr $server_addr;
        proxy_set_header Server-Port $server_port;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_pass http://swoole;
    }
    location @laravels {
        # proxy_connect_timeout 60s;
        # proxy_send_timeout 60s;
        # proxy_read_timeout 60s;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Real-PORT $remote_port;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header Scheme $scheme;
        proxy_set_header Server-Protocol $server_protocol;
        proxy_set_header Server-Name $server_name;
        proxy_set_header Server-Addr $server_addr;
        proxy_set_header Server-Port $server_port;
        proxy_pass http://swoole;
    }
}

5.Heartbeat setting

  • Heartbeat setting of Swoole

    // config/laravels.php
    'swoole' => [
        //...
        // All connections are traversed every 60 seconds. If a connection does not send any data to the server within 600 seconds, the connection will be forced to close.
        'heartbeat_idle_time'      => 600,
        'heartbeat_check_interval' => 60,
        //...
    ],
  • Proxy read timeout of Nginx

    # Nginx will close the connection if the proxied server does not send data to Nginx in 60 seconds
    proxy_read_timeout 60s;

6.Push data in controller

namespace App\Http\Controllers;
class TestController extends Controller
{
    public function push()
    {
        $fd = 1; // Find fd by userId from a map [userId=>fd].
        /**@var \Swoole\WebSocket\Server $swoole */
        $swoole = app('swoole');
        $success = $swoole->push($fd, 'Push data to fd#1 in Controller');
        var_dump($success);
    }
}

Listen events

System events

Usually, you can reset/destroy some global/static variables, or change the current Request/Response object.

  • laravels.received_request After LaravelS parsed Swoole\Http\Request to Illuminate\Http\Request, before Laravel's Kernel handles this request.

    // Edit file `app/Providers/EventServiceProvider.php`, add the following code into method `boot`
    // If no variable $events, you can also call Facade \Event::listen(). 
    $events->listen('laravels.received_request', function (\Illuminate\Http\Request $req, $app) {
        $req->query->set('get_key', 'hhxsv5');// Change query of request
        $req->request->set('post_key', 'hhxsv5'); // Change post of request
    });
  • laravels.generated_response After Laravel's Kernel handled the request, before LaravelS parses Illuminate\Http\Response to Swoole\Http\Response.

    // Edit file `app/Providers/EventServiceProvider.php`, add the following code into method `boot`
    // If no variable $events, you can also call Facade \Event::listen(). 
    $events->listen('laravels.generated_response', function (\Illuminate\Http\Request $req, \Symfony\Component\HttpFoundation\Response $rsp, $app) {
        $rsp->headers->set('header-key', 'hhxsv5');// Change header of response
    });

Customized asynchronous events

This feature depends on AsyncTask of Swoole, your need to set swoole.task_worker_num in config/laravels.php firstly. The performance of asynchronous event processing is influenced by number of Swoole task process, you need to set task_worker_num appropriately.

1.Create event class.

use Hhxsv5\LaravelS\Swoole\Task\Event;
class TestEvent extends Event
{
    protected $listeners = [
        // Listener list
        TestListener1::class,
        // TestListener2::class,
    ];
    private $data;
    public function __construct($data)
    {
        $this->data = $data;
    }
    public function getData()
    {
        return $this->data;
    }
}

2.Create listener class.

use Hhxsv5\LaravelS\Swoole\Task\Task;
use Hhxsv5\LaravelS\Swoole\Task\Listener;
class TestListener1 extends Listener
{
    /**
     * @var TestEvent
     */
    protected $event;
    
    public function handle()
    {
        \Log::info(__CLASS__ . ':handle start', [$this->event->getData()]);
        sleep(2);// Simulate the slow codes
        // Deliver task in CronJob, but NOT support callback finish() of task.
        // Note: Modify task_ipc_mode to 1 or 2 in config/laravels.php, see https://www.swoole.co.uk/docs/modules/swoole-server/configuration
        $ret = Task::deliver(new TestTask('task data'));
        var_dump($ret);
        // The exceptions thrown here will be caught by the upper layer and recorded in the Swoole log. Developers need to try/catch manually.
    }
}

3.Fire event.

delay(10); // Delay 10 seconds to fire event // $event->setTries(3); // When an error occurs, try 3 times in total $success = Event::fire($event); var_dump($success);// Return true if sucess, otherwise false ">
// Create instance of event and fire it, "fire" is asynchronous.
use Hhxsv5\LaravelS\Swoole\Task\Event;
$event = new TestEvent('event data');
// $event->delay(10); // Delay 10 seconds to fire event
// $event->setTries(3); // When an error occurs, try 3 times in total
$success = Event::fire($event);
var_dump($success);// Return true if sucess, otherwise false

Asynchronous task queue

This feature depends on AsyncTask of Swoole, your need to set swoole.task_worker_num in config/laravels.php firstly. The performance of task processing is influenced by number of Swoole task process, you need to set task_worker_num appropriately.

1.Create task class.

use Hhxsv5\LaravelS\Swoole\Task\Task;
class TestTask extends Task
{
    private $data;
    private $result;
    public function __construct($data)
    {
        $this->data = $data;
    }
    // The logic of task handling, run in task process, CAN NOT deliver task
    public function handle()
    {
        \Log::info(__CLASS__ . ':handle start', [$this->data]);
        sleep(2);// Simulate the slow codes
        // The exceptions thrown here will be caught by the upper layer and recorded in the Swoole log. Developers need to try/catch manually.
        $this->result = 'the result of ' . $this->data;
    }
    // Optional, finish event, the logic of after task handling, run in worker process, CAN deliver task 
    public function finish()
    {
        \Log::info(__CLASS__ . ':finish start', [$this->result]);
        Task::deliver(new TestTask2('task2 data')); // Deliver the other task
    }
}

2.Deliver task.

delay(3);// delay 3 seconds to deliver task // $task->setTries(3); // When an error occurs, try 3 times in total $ret = Task::deliver($task); var_dump($ret);// Return true if sucess, otherwise false ">
// Create instance of TestTask and deliver it, "deliver" is asynchronous.
use Hhxsv5\LaravelS\Swoole\Task\Task;
$task = new TestTask('task data');
// $task->delay(3);// delay 3 seconds to deliver task
// $task->setTries(3); // When an error occurs, try 3 times in total
$ret = Task::deliver($task);
var_dump($ret);// Return true if sucess, otherwise false

Millisecond cron job

Wrapper cron job base on Swoole's Millisecond Timer, replace Linux Crontab.

1.Create cron job class.

namespace App\Jobs\Timer;
use App\Tasks\TestTask;
use Swoole\Coroutine;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Hhxsv5\LaravelS\Swoole\Timer\CronJob;
class TestCronJob extends CronJob
{
    protected $i = 0;
    // !!! The `interval` and `isImmediate` of cron job can be configured in two ways(pick one of two): one is to overload the corresponding method, and the other is to pass parameters when registering cron job.
    // --- Override the corresponding method to return the configuration: begin
    public function interval()
    {
        return 1000;// Run every 1000ms
    }
    public function isImmediate()
    {
        return false;// Whether to trigger `run` immediately after setting up
    }
    // --- Override the corresponding method to return the configuration: end
    public function run()
    {
        \Log::info(__METHOD__, ['start', $this->i, microtime(true)]);
        // do something
        // sleep(1); // Swoole < 2.1
        Coroutine::sleep(1); // Swoole>=2.1 Coroutine will be automatically created for run().
        $this->i++;
        \Log::info(__METHOD__, ['end', $this->i, microtime(true)]);

        if ($this->i >= 10) { // Run 10 times only
            \Log::info(__METHOD__, ['stop', $this->i, microtime(true)]);
            $this->stop(); // Stop this cron job, but it will run again after restart/reload.
            // Deliver task in CronJob, but NOT support callback finish() of task.
            // Note: Modify task_ipc_mode to 1 or 2 in config/laravels.php, see https://www.swoole.co.uk/docs/modules/swoole-server/configuration
            $ret = Task::deliver(new TestTask('task data'));
            var_dump($ret);
        }
        // The exceptions thrown here will be caught by the upper layer and recorded in the Swoole log. Developers need to try/catch manually.
    }
}

2.Register cron job.

[ 'enable' => true, // Enable Timer 'jobs' => [ // The list of cron job // Enable LaravelScheduleJob to run `php artisan schedule:run` every 1 minute, replace Linux Crontab // \Hhxsv5\LaravelS\Illuminate\LaravelScheduleJob::class, // Two ways to configure parameters: // [\App\Jobs\Timer\TestCronJob::class, [1000, true]], // Pass in parameters when registering \App\Jobs\Timer\TestCronJob::class, // Override the corresponding method to return the configuration ], 'max_wait_time' => 5, // Max waiting time of reloading // Enable the global lock to ensure that only one instance starts the timer when deploying multiple instances. This feature depends on Redis, please see https://laravel.com/docs/7.x/redis 'global_lock' => false, 'global_lock_key' => config('app.name', 'Laravel'), ], // ... ]; ">
// Register cron jobs in file "config/laravels.php"
[
    // ...
    'timer'          => [
        'enable' => true, // Enable Timer
        'jobs'   => [ // The list of cron job
            // Enable LaravelScheduleJob to run `php artisan schedule:run` every 1 minute, replace Linux Crontab
            // \Hhxsv5\LaravelS\Illuminate\LaravelScheduleJob::class,
            // Two ways to configure parameters:
            // [\App\Jobs\Timer\TestCronJob::class, [1000, true]], // Pass in parameters when registering
            \App\Jobs\Timer\TestCronJob::class, // Override the corresponding method to return the configuration
        ],
        'max_wait_time' => 5, // Max waiting time of reloading
        // Enable the global lock to ensure that only one instance starts the timer when deploying multiple instances. This feature depends on Redis, please see https://laravel.com/docs/7.x/redis
        'global_lock'     => false,
        'global_lock_key' => config('app.name', 'Laravel'),
    ],
    // ...
];

3.Note: it will launch multiple timers when build the server cluster, so you need to make sure that launch one timer only to avoid running repetitive task.

4.LaravelS v3.4.0 starts to support the hot restart [Reload] Timer process. After LaravelS receives the SIGUSR1 signal, it waits for max_wait_time(default 5) seconds to end the process, then the Manager process will pull up the Timer process again.

5.If you only need to use minute-level scheduled tasks, it is recommended to enable Hhxsv5\LaravelS\Illuminate\LaravelScheduleJob instead of Linux Crontab, so that you can follow the coding habits of Laravel task scheduling and configure Kernel.

// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
    // runInBackground() will start a new child process to execute the task. This is asynchronous and will not affect the execution timing of other tasks.
    $schedule->command(TestCommand::class)->runInBackground()->everyMinute();
}

Automatically reload after modifying code

  • Via inotify, support Linux only.

    1.Install inotify extension.

    2.Turn on the switch in Settings.

    3.Notice: Modify the file only in Linux to receive the file change events. It's recommended to use the latest Docker. Vagrant Solution.

  • Via fswatch, support OS X/Linux/Windows.

    1.Install fswatch.

    2.Run command in your project root directory.

    # Watch current directory
    ./bin/fswatch
    # Watch app directory
    ./bin/fswatch ./app
  • Via inotifywait, support Linux.

    1.Install inotify-tools.

    2.Run command in your project root directory.

    # Watch current directory
    ./bin/inotify
    # Watch app directory
    ./bin/inotify ./app
  • When the above methods does not work, the ultimate solution: set max_request=1,worker_num=1, so that Worker process will restart after processing a request. The performance of this method is very poor, so only development environment use.

Get the instance of SwooleServer in your project

/**
 * $swoole is the instance of `Swoole\WebSocket\Server` if enable WebSocket server, otherwise `Swoole\Http\Server`
 * @var \Swoole\WebSocket\Server|\Swoole\Http\Server $swoole
 */
$swoole = app('swoole');
var_dump($swoole->stats());
$swoole->push($fd, 'Push WebSocket message');

Use SwooleTable

1.Define Table, support multiple.

All defined tables will be created before Swoole starting.

[ // Scene:bind UserId & FD in WebSocket 'ws' => [// The Key is table name, will add suffix "Table" to avoid naming conflicts. Here defined a table named "wsTable" 'size' => 102400,// The max size 'column' => [// Define the columns ['name' => 'value', 'type' => \Swoole\Table::TYPE_INT, 'size' => 8], ], ], //...Define the other tables ], // ... ]; ">
// in file "config/laravels.php"
[
    // ...
    'swoole_tables'  => [
        // Scene:bind UserId & FD in WebSocket
        'ws' => [// The Key is table name, will add suffix "Table" to avoid naming conflicts. Here defined a table named "wsTable"
            'size'   => 102400,// The max size
            'column' => [// Define the columns
                ['name' => 'value', 'type' => \Swoole\Table::TYPE_INT, 'size' => 8],
            ],
        ],
        //...Define the other tables
    ],
    // ...
];

2.Access Table: all table instances will be bound on SwooleServer, access by app('swoole')->xxxTable.

middleware(['auth']); */ // $user = Auth::user(); // $userId = $user ? $user->id : 0; // 0 means a guest user who is not logged in $userId = mt_rand(1000, 10000); // if (!$userId) { // // Disconnect the connections of unlogged users // $server->disconnect($request->fd); // return; // } $this->wsTable->set('uid:' . $userId, ['value' => $request->fd]);// Bind map uid to fd $this->wsTable->set('fd:' . $request->fd, ['value' => $userId]);// Bind map fd to uid $server->push($request->fd, "Welcome to LaravelS #{$request->fd}"); } public function onMessage(Server $server, Frame $frame) { // Broadcast foreach ($this->wsTable as $key => $row) { if (strpos($key, 'uid:') === 0 && $server->isEstablished($row['value'])) { $content = sprintf('Broadcast: new message "%s" from #%d', $frame->data, $frame->fd); $server->push($row['value'], $content); } } } public function onClose(Server $server, $fd, $reactorId) { $uid = $this->wsTable->get('fd:' . $fd); if ($uid !== false) { $this->wsTable->del('uid:' . $uid['value']); // Unbind uid map } $this->wsTable->del('fd:' . $fd);// Unbind fd map $server->push($fd, "Goodbye #{$fd}"); } } ">
namespace App\Services;
use Hhxsv5\LaravelS\Swoole\WebSocketHandlerInterface;
use Swoole\Http\Request;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;
class WebSocketService implements WebSocketHandlerInterface
{
    /**@var \Swoole\Table $wsTable */
    private $wsTable;
    public function __construct()
    {
        $this->wsTable = app('swoole')->wsTable;
    }
    // Scene:bind UserId & FD in WebSocket
    public function onOpen(Server $server, Request $request)
    {
        // var_dump(app('swoole') === $server);// The same instance
        /**
         * Get the currently logged in user
         * This feature requires that the path to establish a WebSocket connection go through middleware such as Authenticate.
         * E.g:
         * Browser side: var ws = new WebSocket("ws://127.0.0.1:5200/ws");
         * Then the /ws route in Laravel needs to add the middleware like Authenticate.
         * Route::get('/ws', function () {
         *     // Respond any content with status code 200
         *     return 'websocket';
         * })->middleware(['auth']);
         */
        // $user = Auth::user();
        // $userId = $user ? $user->id : 0; // 0 means a guest user who is not logged in
        $userId = mt_rand(1000, 10000);
        // if (!$userId) {
        //     // Disconnect the connections of unlogged users
        //     $server->disconnect($request->fd);
        //     return;
        // }
        $this->wsTable->set('uid:' . $userId, ['value' => $request->fd]);// Bind map uid to fd
        $this->wsTable->set('fd:' . $request->fd, ['value' => $userId]);// Bind map fd to uid
        $server->push($request->fd, "Welcome to LaravelS #{$request->fd}");
    }
    public function onMessage(Server $server, Frame $frame)
    {
        // Broadcast
        foreach ($this->wsTable as $key => $row) {
            if (strpos($key, 'uid:') === 0 && $server->isEstablished($row['value'])) {
                $content = sprintf('Broadcast: new message "%s" from #%d', $frame->data, $frame->fd);
                $server->push($row['value'], $content);
            }
        }
    }
    public function onClose(Server $server, $fd, $reactorId)
    {
        $uid = $this->wsTable->get('fd:' . $fd);
        if ($uid !== false) {
            $this->wsTable->del('uid:' . $uid['value']); // Unbind uid map
        }
        $this->wsTable->del('fd:' . $fd);// Unbind fd map
        $server->push($fd, "Goodbye #{$fd}");
    }
}

Multi-port mixed protocol

For more information, please refer to Swoole Server AddListener

To make our main server support more protocols not just Http and WebSocket, we bring the feature multi-port mixed protocol of Swoole in LaravelS and name it Socket. Now, you can build TCP/UDP applications easily on top of Laravel.

  1. Create Socket handler class, and extend Hhxsv5\LaravelS\Swoole\Socket\{TcpSocket|UdpSocket|Http|WebSocket}.

    send($fd, 'LaravelS: bye' . PHP_EOL); $server->close($fd); } } public function onClose(Server $server, $fd, $reactorId) { \Log::info('Close TCP connection', [$fd]); $server->send($fd, 'Goodbye'); } } ">
    namespace App\Sockets;
    use Hhxsv5\LaravelS\Swoole\Socket\TcpSocket;
    use Swoole\Server;
    class TestTcpSocket extends TcpSocket
    {
        public function onConnect(Server $server, $fd, $reactorId)
        {
            \Log::info('New TCP connection', [$fd]);
            $server->send($fd, 'Welcome to LaravelS.');
        }
        public function onReceive(Server $server, $fd, $reactorId, $data)
        {
            \Log::info('Received data', [$fd, $data]);
            $server->send($fd, 'LaravelS: ' . $data);
            if ($data === "quit\r\n") {
                $server->send($fd, 'LaravelS: bye' . PHP_EOL);
                $server->close($fd);
            }
        }
        public function onClose(Server $server, $fd, $reactorId)
        {
            \Log::info('Close TCP connection', [$fd]);
            $server->send($fd, 'Goodbye');
        }
    }

    These Socket connections share the same worker processes with your HTTP/WebSocket connections. So it won't be a problem at all if you want to deliver tasks, use SwooleTable, even Laravel components such as DB, Eloquent and so on. At the same time, you can access Swoole\Server\Port object directly by member property swoolePort.

    public function onReceive(Server $server, $fd, $reactorId, $data)
    {
        $port = $this->swoolePort; // Get the `Swoole\Server\Port` object
    }
    namespace App\Http\Controllers;
    class TestController extends Controller
    {
        public function test()
        {
            /**@var \Swoole\Http\Server|\Swoole\WebSocket\Server $swoole */
            $swoole = app('swoole');
            // $swoole->ports: Traverse all Port objects, https://www.swoole.co.uk/docs/modules/swoole-server/multiple-ports
            $port = $swoole->ports[0]; // Get the `Swoole\Server\Port` object, $port[0] is the port of the main server
            foreach ($port->connections as $fd) { // Traverse all connections
                // $swoole->send($fd, 'Send tcp message');
                // if($swoole->isEstablished($fd)) {
                //     $swoole->push($fd, 'Send websocket message');
                // }
            }
        }
    }
  2. Register Sockets.

    \App\Sockets\TestTcpSocket::class, 'enable' => true, // whether to enable, default true ], ], ">
    // Edit `config/laravels.php`
    //...
    'sockets' => [
        [
            'host'     => '127.0.0.1',
            'port'     => 5291,
            'type'     => SWOOLE_SOCK_TCP,// Socket type: SWOOLE_SOCK_TCP/SWOOLE_SOCK_TCP6/SWOOLE_SOCK_UDP/SWOOLE_SOCK_UDP6/SWOOLE_UNIX_DGRAM/SWOOLE_UNIX_STREAM
            'settings' => [// Swoole settings:https://www.swoole.co.uk/docs/modules/swoole-server-methods#swoole_server-addlistener
                'open_eof_check' => true,
                'package_eof'    => "\r\n",
            ],
            'handler'  => \App\Sockets\TestTcpSocket::class,
            'enable'   => true, // whether to enable, default true
        ],
    ],

    About the heartbeat configuration, it can only be set on the main server and cannot be configured on Socket, but the Socket inherits the heartbeat configuration of the main server.

    For TCP socket, onConnect and onClose events will be blocked when dispatch_mode of Swoole is 1/3, so if you want to unblock these two events please set dispatch_mode to 2/4/5.

    'swoole' => [
        //...
        'dispatch_mode' => 2,
        //...
    ];
  3. Test.

  • TCP: telnet 127.0.0.1 5291

  • UDP: [Linux] echo "Hello LaravelS" > /dev/udp/127.0.0.1/5292

  1. Register example of other protocols.

    • UDP
    \App\Sockets\TestUdpSocket::class, ], ], ">
    'sockets' => [
        [
            'host'     => '0.0.0.0',
            'port'     => 5292,
            'type'     => SWOOLE_SOCK_UDP,
            'settings' => [
                'open_eof_check' => true,
                'package_eof'    => "\r\n",
            ],
            'handler'  => \App\Sockets\TestUdpSocket::class,
        ],
    ],
    • Http
    'sockets' => [
        [
            'host'     => '0.0.0.0',
            'port'     => 5293,
            'type'     => SWOOLE_SOCK_TCP,
            'settings' => [
                'open_http_protocol' => true,
            ],
            'handler'  => \App\Sockets\TestHttp::class,
        ],
    ],
    • WebSocket: The main server must turn on WebSocket, that is, set websocket.enable to true.
    'sockets' => [
        [
            'host'     => '0.0.0.0',
            'port'     => 5294,
            'type'     => SWOOLE_SOCK_TCP,
            'settings' => [
                'open_http_protocol'      => true,
                'open_websocket_protocol' => true,
            ],
            'handler'  => \App\Sockets\TestWebSocket::class,
        ],
    ],

Coroutine

Swoole Coroutine

  • Warning: The order of code execution in the coroutine is out of order. The data of the request level should be isolated by the coroutine ID. However, there are many singleton and static attributes in Laravel/Lumen, the data between different requests will affect each other, it's Unsafe. For example, the database connection is a singleton, the same database connection shares the same PDO resource. This is fine in the synchronous blocking mode, but it does not work in the asynchronous coroutine mode. Each query needs to create different connections and maintain IO state of different connections, which requires a connection pool.

  • DO NOT enable the coroutine, only the custom process can use the coroutine.

Custom process

Support developers to create special work processes for monitoring, reporting, or other special tasks. Refer addProcess.

  1. Create Proccess class, implements CustomProcessInterface.

    namespace App\Processes;
    use App\Tasks\TestTask;
    use Hhxsv5\LaravelS\Swoole\Process\CustomProcessInterface;
    use Hhxsv5\LaravelS\Swoole\Task\Task;
    use Swoole\Coroutine;
    use Swoole\Http\Server;
    use Swoole\Process;
    class TestProcess implements CustomProcessInterface
    {
        /**
         * @var bool Quit tag for Reload updates
         */
        private static $quit = false;
    
        public static function callback(Server $swoole, Process $process)
        {
            // The callback method cannot exit. Once exited, Manager process will automatically create the process 
            while (!self::$quit) {
                \Log::info('Test process: running');
                // sleep(1); // Swoole < 2.1
                Coroutine::sleep(1); // Swoole>=2.1: Coroutine & Runtime will be automatically enabled for callback().
                 // Deliver task in custom process, but NOT support callback finish() of task.
                // Note: Modify task_ipc_mode to 1 or 2 in config/laravels.php, see https://www.swoole.co.uk/docs/modules/swoole-server/configuration
                $ret = Task::deliver(new TestTask('task data'));
                var_dump($ret);
                // The upper layer will catch the exception thrown in the callback and record it in the Swoole log, and then this process will exit. The Manager process will re-create the process after 3 seconds, so developers need to try/catch to catch the exception by themselves to avoid frequent process creation.
                // throw new \Exception('an exception');
            }
        }
        // Requirements: LaravelS >= v3.4.0 & callback() must be async non-blocking program.
        public static function onReload(Server $swoole, Process $process)
        {
            // Stop the process...
            // Then end process
            \Log::info('Test process: reloading');
            self::$quit = true;
            // $process->exit(0); // Force exit process
        }
        // Requirements: LaravelS >= v3.7.4 & callback() must be async non-blocking program.
        public static function onStop(Server $swoole, Process $process)
        {
            // Stop the process...
            // Then end process
            \Log::info('Test process: stopping');
            self::$quit = true;
            // $process->exit(0); // Force exit process
        }
    }
  2. Register TestProcess.

    // Edit `config/laravels.php`
    // ...
    'processes' => [
        'test' => [ // Key name is process name
            'class'    => \App\Processes\TestProcess::class,
            'redirect' => false, // Whether redirect stdin/stdout, true or false
            'pipe'     => 0,     // The type of pipeline, 0: no pipeline 1: SOCK_STREAM 2: SOCK_DGRAM
            'enable'   => true,  // Whether to enable, default true
            //'num'    => 3   // To create multiple processes of this class, default is 1
            //'queue'    => [ // Enable message queue as inter-process communication, configure empty array means use default parameters
            //    'msg_key'  => 0,    // The key of the message queue. Default: ftok(__FILE__, 1).
            //    'mode'     => 2,    // Communication mode, default is 2, which means contention mode
            //    'capacity' => 8192, // The length of a single message, is limited by the operating system kernel parameters. The default is 8192, and the maximum is 65536
            //],
            //'restart_interval' => 5, // After the process exits abnormally, how many seconds to wait before restarting the process, default 5 seconds
        ],
    ],
  3. Note: The callback() cannot quit. If quit, the Manager process will re-create the process.

  4. Example: Write data to a custom process.

    // config/laravels.php
    'processes' => [
        'test' => [
            'class'    => \App\Processes\TestProcess::class,
            'redirect' => false,
            'pipe'     => 1,
        ],
    ],
    // app/Processes/TestProcess.php
    public static function callback(Server $swoole, Process $process)
    {
        while ($data = $process->read()) {
            \Log::info('TestProcess: read data', [$data]);
            $process->write('TestProcess: ' . $data);
        }
    }
    // app/Http/Controllers/TestController.php
    public function testProcessWrite()
    {
        /**@var \Swoole\Process $process */
        $process = app('swoole')->customProcesses['test'];
        $process->write('TestController: write data' . time());
        var_dump($process->read());
    }

Common components

Apollo

LaravelS will pull the Apollo configuration and write it to the .env file when starting. At the same time, LaravelS will start the custom process apollo to monitor the configuration and automatically reload when the configuration changes.

  1. Enable Apollo: add --enable-apollo and Apollo parameters to the startup parameters.

    php bin/laravels start --enable-apollo --apollo-server=http://127.0.0.1:8080 --apollo-app-id=LARAVEL-S-TEST
  2. Support hot updates(optional).

    // Edit `config/laravels.php`
    'processes' => Hhxsv5\LaravelS\Components\Apollo\Process::getDefinition(),
    // When there are other custom process configurations
    'processes' => [
        'test' => [
            'class'    => \App\Processes\TestProcess::class,
            'redirect' => false,
            'pipe'     => 1,
        ],
        // ...
    ] + Hhxsv5\LaravelS\Components\Apollo\Process::getDefinition(),
  3. List of available parameters.

Parameter Description Default Demo
apollo-server Apollo server URL - --apollo-server=http://127.0.0.1:8080
apollo-app-id Apollo APP ID - --apollo-app-id=LARAVEL-S-TEST
apollo-namespaces The namespace to which the APP belongs, support specify the multiple application --apollo-namespaces=application --apollo-namespaces=env
apollo-cluster The cluster to which the APP belongs default --apollo-cluster=default
apollo-client-ip IP of current instance, can also be used for grayscale publishing Local intranet IP --apollo-client-ip=10.2.1.83
apollo-pull-timeout Timeout time(seconds) when pulling configuration 5 --apollo-pull-timeout=5
apollo-backup-old-env Whether to backup the old configuration file when updating the configuration file .env false --apollo-backup-old-env

Prometheus

Support Prometheus monitoring and alarm, Grafana visually view monitoring metrics. Please refer to Docker Compose for the environment construction of Prometheus and Grafana.

  1. Require extension APCu >= 5.0.0, please install it by pecl install apcu.

  2. Copy the configuration file prometheus.php to the config directory of your project. Modify the configuration as appropriate.

    # Execute commands in the project root directory
    cp vendor/hhxsv5/laravel-s/config/prometheus.php config/

    If your project is Lumen, you also need to manually load the configuration $app->configure('prometheus'); in bootstrap/app.php.

  3. Configure global middleware: Hhxsv5\LaravelS\Components\Prometheus\RequestMiddleware::class. In order to count the request time consumption as accurately as possible, RequestMiddleware must be the first global middleware, which needs to be placed in front of other middleware.

  4. Register ServiceProvider: Hhxsv5\LaravelS\Components\Prometheus\ServiceProvider::class.

  5. Configure the CollectorProcess in config/laravels.php to collect the metrics of Swoole Worker/Task/Timer processes regularly.

    'processes' => Hhxsv5\LaravelS\Components\Prometheus\CollectorProcess::getDefinition(),
  6. Create the route to output metrics.

    use Hhxsv5\LaravelS\Components\Prometheus\Exporter;
    
    Route::get('/actuator/prometheus', function () {
        $result = app(Exporter::class)->render();
        return response($result, 200, ['Content-Type' => Exporter::REDNER_MIME_TYPE]);
    });
  7. Complete the configuration of Prometheus and start it.

    global:
      scrape_interval: 5s
      scrape_timeout: 5s
      evaluation_interval: 30s
    scrape_configs:
    - job_name: laravel-s-test
      honor_timestamps: true
      metrics_path: /actuator/prometheus
      scheme: http
      follow_redirects: true
      static_configs:
      - targets:
        - 127.0.0.1:5200 # The ip and port of the monitored service
    # Dynamically discovered using one of the supported service-discovery mechanisms
    # https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config
    # - job_name: laravels-eureka
    #   honor_timestamps: true
    #   scrape_interval: 5s
    #   metrics_path: /actuator/prometheus
    #   scheme: http
    #   follow_redirects: true
      # eureka_sd_configs:
      # - server: http://127.0.0.1:8080/eureka
      #   follow_redirects: true
      #   refresh_interval: 5s
  8. Start Grafana, then import panel json.

Grafana Dashboard

Other features

Configure Swoole events

Supported events:

Event Interface When happened
ServerStart Hhxsv5\LaravelS\Swoole\Events\ServerStartInterface Occurs when the Master process is starting, this event should not handle complex business logic, and can only do some simple work of initialization.
ServerStop Hhxsv5\LaravelS\Swoole\Events\ServerStopInterface Occurs when the server exits normally, CANNOT use async or coroutine related APIs in this event.
WorkerStart Hhxsv5\LaravelS\Swoole\Events\WorkerStartInterface Occurs after the Worker/Task process is started, and the Laravel initialization has been completed.
WorkerStop Hhxsv5\LaravelS\Swoole\Events\WorkerStopInterface Occurs after the Worker/Task process exits normally
WorkerError Hhxsv5\LaravelS\Swoole\Events\WorkerErrorInterface Occurs when an exception or fatal error occurs in the Worker/Task process

1.Create an event class to implement the corresponding interface.

namespace App\Events;
use Hhxsv5\LaravelS\Swoole\Events\ServerStartInterface;
use Swoole\Atomic;
use Swoole\Http\Server;
class ServerStartEvent implements ServerStartInterface
{
    public function __construct()
    {
    }
    public function handle(Server $server)
    {
        // Initialize a global counter (available across processes)
        $server->atomicCount = new Atomic(2233);

        // Invoked in controller: app('swoole')->atomicCount->get();
    }
}
namespace App\Events;
use Hhxsv5\LaravelS\Swoole\Events\WorkerStartInterface;
use Swoole\Http\Server;
class WorkerStartEvent implements WorkerStartInterface
{
    public function __construct()
    {
    }
    public function handle(Server $server, $workerId)
    {
        // Initialize a database connection pool
        // DatabaseConnectionPool::init();
    }
}

2.Configuration.

// Edit `config/laravels.php`
'event_handlers' => [
    'ServerStart' => [\App\Events\ServerStartEvent::class], // Trigger events in array order
    'WorkerStart' => [\App\Events\WorkerStartEvent::class],
],

Serverless

Alibaba Cloud Function Compute

Function Compute.

1.Modify bootstrap/app.php and set the storage directory. Because the project directory is read-only, the /tmp directory can only be read and written.

$app->useStoragePath(env('APP_STORAGE_PATH', '/tmp/storage'));

2.Create a shell script laravels_bootstrap and grant executable permission.

#!/usr/bin/env bash
set +e

# Create storage-related directories
mkdir -p /tmp/storage/app/public
mkdir -p /tmp/storage/framework/cache
mkdir -p /tmp/storage/framework/sessions
mkdir -p /tmp/storage/framework/testing
mkdir -p /tmp/storage/framework/views
mkdir -p /tmp/storage/logs

# Set the environment variable APP_STORAGE_PATH, please make sure it's the same as APP_STORAGE_PATH in .env
export APP_STORAGE_PATH=/tmp/storage

# Start LaravelS
php bin/laravels start

3.Configure template.xml.

ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
  laravel-s-demo:
    Type: 'Aliyun::Serverless::Service'
    Properties:
      Description: 'LaravelS Demo for Serverless'
    fc-laravel-s:
      Type: 'Aliyun::Serverless::Function'
      Properties:
        Handler: laravels.handler
        Runtime: custom
        MemorySize: 512
        Timeout: 30
        CodeUri: ./
        InstanceConcurrency: 10
        EnvironmentVariables:
          BOOTSTRAP_FILE: laravels_bootstrap

Important notices

Singleton Issue

  • Under FPM mode, singleton instances will be instantiated and recycled in every request, request start=>instantiate instance=>request end=>recycled instance.

  • Under Swoole Server, All singleton instances will be held in memory, different lifetime from FPM, request start=>instantiate instance=>request end=>do not recycle singleton instance. So need developer to maintain status of singleton instances in every request.

  • Common solutions:

    1. Write a XxxCleaner class to clean up the singleton object state. This class implements the interface Hhxsv5\LaravelS\Illuminate\Cleaners\CleanerInterface and then registers it in cleaners of laravels.php.

    2. Reset status of singleton instances by Middleware.

    3. Re-register ServiceProvider, add XxxServiceProvider into register_providers of file laravels.php. So that reinitialize singleton instances in every request Refer.

Cleaners

Configuration cleaners.

Known issues

Known issues: a package of known issues and solutions.

Debugging method

  • Logging; if you want to output to the console, you can use stderr, Log::channel('stderr')->debug('debug message').

  • Laravel Dump Server(Laravel 5.7 has been integrated by default).

Read request

Read request by Illuminate\Http\Request Object, $_ENV is readable, $_SERVER is partially readable, CANNOT USE $_GET/$_POST/$_FILES/$_COOKIE/$_REQUEST/$_SESSION/$GLOBALS.

public function form(\Illuminate\Http\Request $request)
{
    $name = $request->input('name');
    $all = $request->all();
    $sessionId = $request->cookie('sessionId');
    $photo = $request->file('photo');
    // Call getContent() to get the raw POST body, instead of file_get_contents('php://input')
    $rawContent = $request->getContent();
    //...
}

Output response

Respond by Illuminate\Http\Response Object, compatible with echo/vardump()/print_r(),CANNOT USE functions dd()/exit()/die()/header()/setcookie()/http_response_code().

public function json()
{
    return response()->json(['time' => time()])->header('header1', 'value1')->withCookie('c1', 'v1');
}

Persistent connection

Singleton connection will be resident in memory, it is recommended to turn on persistent connection for better performance.

  1. Database connection, it will reconnect automatically immediately after disconnect.
// config/database.php
'connections' => [
    'my_conn' => [
        'driver'    => 'mysql',
        'host'      => env('DB_MY_CONN_HOST', 'localhost'),
        'port'      => env('DB_MY_CONN_PORT', 3306),
        'database'  => env('DB_MY_CONN_DATABASE', 'forge'),
        'username'  => env('DB_MY_CONN_USERNAME', 'forge'),
        'password'  => env('DB_MY_CONN_PASSWORD', ''),
        'charset'   => 'utf8mb4',
        'collation' => 'utf8mb4_unicode_ci',
        'prefix'    => '',
        'strict'    => false,
        'options'   => [
            // Enable persistent connection
            \PDO::ATTR_PERSISTENT => true,
        ],
    ],
],
  1. Redis connection, it won't reconnect automatically immediately after disconnect, and will throw an exception about lost connection, reconnect next time. You need to make sure that SELECT DB correctly before operating Redis every time.
// config/database.php
'redis' => [
    'client' => env('REDIS_CLIENT', 'phpredis'), // It is recommended to use phpredis for better performance.
    'default' => [
        'host'       => env('REDIS_HOST', 'localhost'),
        'password'   => env('REDIS_PASSWORD', null),
        'port'       => env('REDIS_PORT', 6379),
        'database'   => 0,
        'persistent' => true, // Enable persistent connection
    ],
],

About memory leaks

  • Avoid using global variables. If necessary, please clean or reset them manually.

  • Infinitely appending element into static/global variable will lead to OOM(Out of Memory).

    class Test
    {
        public static $array = [];
        public static $string = '';
    }
    
    // Controller
    public function test(Request $req)
    {
        // Out of Memory
        Test::$array[] = $req->input('param1');
        Test::$string .= $req->input('param2');
    }
  • Memory leak detection method

    1. Modify config/laravels.php: worker_num=1, max_request=1000000, remember to change it back after test;

    2. Add routing /debug-memory-leak without route middleware to observe the memory changes of the Worker process;

    Route::get('/debug-memory-leak', function () {
        global $previous;
        $current = memory_get_usage();
        $stats = [
            'prev_mem' => $previous,
            'curr_mem' => $current,
            'diff_mem' => $current - $previous,
        ];
        $previous = $current;
        return $stats;
    });
    1. Start LaravelS and request /debug-memory-leak until diff_mem is less than or equal to zero; if diff_mem is always greater than zero, it means that there may be a memory leak in Global Middleware or Laravel Framework;

    2. After completing Step 3, alternately request the business routes and /debug-memory-leak (It is recommended to use ab/wrk to make a large number of requests for business routes), the initial increase in memory is normal. After a large number of requests for the business routes, if diff_mem is always greater than zero and curr_mem continues to increase, there is a high probability of memory leak; If curr_mem always changes within a certain range and does not continue to increase, there is a low probability of memory leak.

    3. If you still can't solve it, max_request is the last guarantee.

Linux kernel parameter adjustment

Linux kernel parameter adjustment

Pressure test

Pressure test

Alternatives

Sponsor

License

MIT

Comments
  • Login once, logged in everywhere. 在一处登录后,所有设备均显示已登录。

    Login once, logged in everywhere. 在一处登录后,所有设备均显示已登录。

    1. Tell us your software version

      | Software | Version | | --------- | --------- | | PHP | 7.2.12 | | Swoole | 4.2.10-alpha | | Laravel/Lumen | 5.7.* |

    2. Detail description about this issue(error/log)

      使用 Laravel 内置的用户系统时,只要在一处登录,在其他任何地方访问均显示我已登录,清空 Cookies 也无效。

      我已经按照 #99、#50 中的描述设置了 config/laravels.php 中的「register_providers」,但是并没有用。按照前一个 issue 提出者所说将 cleanRequest 函数里那两行取消注释,还是这样。

      这个 bug 并不稳定,不能 100% 复现。但它的确存在,用一个从未访问过此网站的手机打开也可能触发。

      我觉得就是那两个 issue 中的相关问题,但我想知道如何修复。

      When I use the built-in user system of Laravel, if I login once, the site shows I am logged in everywhere, even after I cleaned my cookies.

      I have set the "register_providers" in config/laravels.php to the following as mentioned in #99 and #50, but it did no good. Neither did uncommenting the two lines in the cleanRequest function.

      This bug isn't consistent, but it does exists. I have ever used a phone that never visited the site to browse it, and it said I was logged in.

      I think the bug is related to the two issues above. I want to know how to fix it.

      config/laravels.php:

    return [
        //......
        'register_providers'       => [
            '\App\Providers\AuthServiceProvider',
            '\Illuminate\Auth\AuthServiceProvider',
            '\Illuminate\Auth\Passwords\PasswordResetServiceProvider',
        ],
        //......
    ];
    

    image

    请求头中没有 cookies,但还是显示已登录。

    No cookies passed to server. Still shows logged in.

    1. Give us a reproducible code block and steps

      创建一个最简单的应用:

      1. laravel new test,进入目录,用 composer 安装 laravel-s。
      2. php artisan laravels publish,修改 config/laravels.php
      3. 使用 SQLite 数据库(只是简单起见,用 PostgreSQL 也一样),php artisan migrate
      4. 打开 http://127.0.0.1:5200 注册并登录。
      5. 清除 Cookies / 关闭浏览器,再打开这个站点,依然显示已登录。

      Just create a most simplified application (also the steps to have this bug):

      1. laravel new test and cd into it, install laravel-s via composer.
      2. php artisan laravels publish and modify the config/laravels.php file.
      3. php artisan make:auth to enable the user system.
      4. Change the database to SQLite (just to simplify. using PostgreSQL doesn't help), and php artisan migrate.
      5. Open http://127.0.0.1:5200, register a user, login.
      6. Clean the cookies / close the browser / change the device, re-open the site. It shows you are still online.
    bug 
    opened by moycat 35
  • 大佬请教一下,启用WebSocket服务器后,WebSocketService无法获取客户端的websocket请求

    大佬请教一下,启用WebSocket服务器后,WebSocketService无法获取客户端的websocket请求

    1. Tell us your PHP version(php -v)

    7.2.6

    1. Tell us your Swoole version(php --ri swoole)

    swoole support => enabled Version => 4.0.0

    1. Tell us your Laravel/Lumen version(check composer.json & composer.lock)

    Laravel

    1. Detail description about this issue(error/log)

    artisan启动后一切正常就是无法在服务器端接受客户端的数据

    1. Give us a reproducible code block and steps
       // 声明没有参数的构造函数
        public function __construct()
        {
            Log::info('是否进入websocket');
        }
        public function onOpen(\swoole_websocket_server $server, \swoole_http_request $request)
        {
    
            // 在触发onOpen事件之前Laravel的生命周期已经完结,所以Laravel的Request是可读的,Session是可读写的
            \Log::info('New WebSocket connection', [$request->fd, request()->all(), session()->getId(), session('xxx'), session(['yyy' => time()])]);
            $server->push($request->fd, 'Welcome to LaravelS');
            // throw new \Exception('an exception');// 此时抛出的异常上层会忽略,并记录到Swoole日志,需要开发者try/catch捕获处理
        }
        public function onMessage(\swoole_websocket_server $server, \swoole_websocket_frame $frame)
        {
            \Log::info('Received message', [$frame->fd, $frame->data, $frame->opcode, $frame->finish]);
            $server->push($frame->fd, date('Y-m-d H:i:s'));
            // throw new \Exception('an exception');// 此时抛出的异常上层会忽略,并记录到Swoole日志,需要开发者try/catch捕获处理
        }
    
    

    6.客户端JS

    
         var ws = new WebSocket("wss://chat.hbmsj.cn/wss");
            ws.onopen = function (event) {
                console.log("Send Text WS was opened.");
            };
            ws.onmessage = function (event) {
                console.log("response text msg: " + event.data);
            };
            ws.onerror = function (event) {
                console.log("Send Text fired an error");
            };
            ws.onclose = function (event) {
                console.log("WebSocket instance closed.");
            };
    

    Nginx 配置文件都是粘贴复制的

    question 
    opened by HugBoomsj 33
  • Using persistent connection for Predis leads to the conflict of database selection

    Using persistent connection for Predis leads to the conflict of database selection

    1. Tell us your software version How to know it?

      # PHP
      php -v
      # Swoole
      php --ri swoole
      # Laravel
      grep 'laravel/framework' composer.json
      # Lumen
      grep 'laravel/lumen-framework' composer.json
      

      | Software | Version | | --------- | --------- | | PHP | 7.3.8 | | Swoole | 4.3.5 | | Laravel/Lumen | 5.8.30 |

    2. Detail description about this issue(error/log)

      redis缓存设置持久化连接后,更新缓存统计数据,数据无变化,去掉持久化连接选项后,恢复正常

    3. Give us a reproducible code block and steps

      \Cache::store('redis')->increment('statics');//假如值为100,更新数据后无变化
      

    去除cache中的持久化连接,缓存数据更新成功 QQ20190806-181524@2x

    wontfix 
    opened by ashuiccc 27
  • 高并发下容易出现错误SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active

    高并发下容易出现错误SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active

    1. Tell us your software version How to know it?

      # PHP
      php -v
      # Swoole
      php --ri swoole
      # Laravel
      grep 'laravel/framework' composer.json
      # Lumen
      grep 'laravel/lumen-framework' composer.json
      

      | Software | Version | | --------- | --------- | | PHP | 7.1.23| | Swoole | 4.2.5 | | Laravel | 5.5 |

    2. Detail description about this issue(error/log)

    SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active. Consider using PDOStatement::fetchAll(). Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by settingthe PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute. (SQL: select * fromadmin_userwhereid= 1 andadmin_user.deleted_atis null limit 1) {"exception":"[object] (Illuminate\\Database\\QueryException(code: HY000): SQLSTATE[HY000]: General error: 2014 Cannot execute queries while otherunbuffered queries are active. Consider using PDOStatement::fetchAll(). Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute. (SQL: select * fromadmin_userwhereid= 1 andadmin_user.deleted_atis null limit 1) at /home/deploy/apps/codebase/releases/codebase_1.0.3238/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664, Doctrine\\DBAL\\Driver\\PDOException(code: HY000): SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active. Consider using PDOStatement::fetchAll(). Alternatively, if your code is only ever going to run against mysql, you mayenable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute. at /home/deploy/apps/codebase/releases/codebase_1.0.3238/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:79, PDOException(code: HY000): SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active. Consider using PDOStatement::fetchAll(). Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute. at /home/deploy/apps/codebase/releases/codebase_1.0.3238/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:77

    补充: 在mysql 的connection 里面已经加入了下面的配置,但是问题依旧 'options' => array(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true),

    1. Give us a reproducible code block and steps

    业务代码为从一个表里面查询一个字段: User::whereId(1)->first()

    在压力测试的时候很容易复现该问题

    wontfix 
    opened by zjuxxd 18
  •  使用 jwt 时,auth 获取当前登录用户有问题。

    使用 jwt 时,auth 获取当前登录用户有问题。

    1. Tell us your PHP version(php -v)

    PHP 7.1.8

    1. Tell us your Swoole version(php --ri swoole)

    4.0.0-alpha

    1. Tell us your Laravel/Lumen version(check composer.json & composer.lock)

    Laravel 5.6

    1. Detail description about this issue(error/log)

    不是用 laravels 是一切正常的,但是用后,每运行一段时间,jwt 的接口就会出现401,jwt 的 token 是新声称的(jwt的 token 是新生成的)。 尝试过把reactor_numworker_num设置成1,没有解决问题。

    1. Give us a reproducible code block and steps

    middleware

    public function handle($request, Closure $next)
        {
    
            $admin = auth()->user();
    
            try {
                // ...
            } catch (\Exception $e) {
    			// 每次会走进来这里
                throw new BadRoleException();
            }
    
            return $next($request);
        }
    

    laravels.php

            'daemonize'          => env('LARAVELS_DAEMONIZE', true),
            'dispatch_mode'      => 1,
            'reactor_num'        => 1,
            'worker_num'         => 1,
            'task_ipc_mode'      => 3,
            'task_max_request'   => 3000,
            'task_tmpdir'        => @is_writable('/dev/shm/') ? '/dev/shm' : '/tmp',
            'message_queue_key'  => ftok(base_path('public/index.php'), 1),
            'max_request'        => 3000,
            'open_tcp_nodelay'   => true,
            'pid_file'           => storage_path('laravels.pid'),
            'log_file'           => storage_path(sprintf('logs/swoole-%s.log', date('Y-m'))),
            'log_level'          => 5,
            'document_root'      => base_path('public'),
            'buffer_output_size' => 32 * 1024 * 1024,
            'socket_buffer_size' => 128 * 1024 * 1024,
            'reload_async'       => true,
            'max_wait_time'      => 60,
            'enable_reuse_port'  => true,
    
    bug 
    opened by terranc 17
  • redis 过10几分钟后连接不上了 请帮助一下我

    redis 过10几分钟后连接不上了 请帮助一下我

    | Software | Version |
    | --------- | --------- |
    | PHP | 7.3.11-1+ubuntu18.04.1+deb.sury.org+1 |
    | Swoole | `4.4.12` |
    | Laravel | `5.5.48` |
    | LaravelS | `3.5.14` |
    
    1. Detail description about this issue(error/log)
    {
        "context":{
            "exception":{
                "class":"RedisException",
                "message":"Connection refused",
                "code":0,
                "file":"/var/www/code/api.laravel/vendor/laravel/framework/src/Illuminate/Redis/Connectors/PhpRedisConnector.php:96"
            }
        },
        "level":400,
        "level_name":"ERROR",
        "channel":"local",
        "datetime":{
            "date":"2019-11-22 17:53:25.961116",
            "timezone_type":3,
            "timezone":"Asia/Shanghai"
        },
        "extra":{
            "file":"App\Exceptions\Handler->report:39",
            "line":39,
            "ip":"192.168.31.85",
            "url":"[POST]http://oa-api.517rxt.test/v1/auth/login"
        },
        "message_string":"Connection refused",
        "@timestamp":"2019-11-22T17:53:25+08:00",
        "trace_id":0
    }
    
    

    第二次在请求

    {
        "context":{
            "exception":{
                "class":"RedisException",
                "message":"NOAUTH Authentication required.",
                "code":0,
                "file":"/var/www/code/api.laravel/vendor/laravel/framework/src/Illuminate/Redis/Connections/PhpRedisConnection.php:32"
            }
        },
        "level":400,
        "level_name":"ERROR",
        "channel":"local",
        "datetime":{
            "date":"2019-11-26 16:46:57.673060",
            "timezone_type":3,
            "timezone":"Asia/Shanghai"
        },
        "extra":{
            "file":"App\Exceptions\Handler->report:39",
            "line":39,
            "ip":"192.168.31.85",
            "url":"[POST]http://tmr-api.517rxt.test/v1/auth/login?XDEBUG_SESSION_START=14915"
        },
        "message_string":"NOAUTH Authentication required.",
        "@timestamp":"2019-11-26T16:46:57+08:00",
        "trace_id":0
    }
    
    
    1. Give us a reproducible code block and steps
    
     'redis' => [
    
            'client' => env('REDIS_CLIENT', 'phpredis'),
            'options'=>[
                'timeout'=>8*3600,
                'read_timeout'=>8*3600,
                'read_write_timeout'=>8*3600,
                'persistent'=>true
            ],
            'default' => [
                'host' => env('REDIS_HOST', '127.0.0.1'),
                'password' => env('REDIS_PASSWORD', null),
                'port' => env('REDIS_PORT', 6379),
                'database' => 0,
                'timeout'=>8*3600,
                'read_timeout'=>8*3600,
                'read_write_timeout'=>8*3600,
                'persistent'=>true
            ],
        ],
    
    
    invalid 
    opened by erdong01 15
  • 原本的laravel项目在引入本包用swoole启动后,发现请求参数会“串”

    原本的laravel项目在引入本包用swoole启动后,发现请求参数会“串”

    比如请求接口/api?param=a 结果正常 然后请求/api 结果也正常 再重复请求一次/api 发现结果是/api?param=a的内容(此时确认请求的参数没有问题,就是接口返回了“之前请求过的参数的内容”)

    时有时无,但不是偶然,而是非常容易触发!

    我用的laravel是5.5,加上了dingo api包。

    question 
    opened by MukuroSama 15
  • 负责处理http请求的worker进程占用内存一直在递增

    负责处理http请求的worker进程占用内存一直在递增

    +-------------------+---------------------------------------+ | Component | Version | +-------------------+---------------------------------------+ | PHP | 7.1.20-1+ubuntu16.04.1+deb.sury.org+1 | | Swoole | 4.0.2 | | Laravel Framework | 5.4.36 | +-------------------+---------------------------------------+

    负责处理http请求的worker进程占用内存一直在递增,没有任何业务逻辑,直接在路由文件里返回数组。

    Route::get('/history/list', function (){
            return ['code' => 0, 'data' => []];
        });
    

    测试的时候循环批量请求20来次,然后ps查看进程观察出来的结果。

    bug 
    opened by minxinqing 15
  • LaravelS v3.3.x New Startup Way 新的启动方式

    LaravelS v3.3.x New Startup Way 新的启动方式

    1. Tell us your software version How to know it?

      # PHP
      php -v
      # Swoole
      php --ri swoole
      # Laravel
      grep 'laravel/framework' composer.json
      # Lumen
      grep 'laravel/lumen-framework' composer.json
      

      | Software | Version | | --------- | --------- | | PHP | 7.1.0 | | Swoole | 4.2.2 | | Laravel/Lumen | Lumen (5.7.6) (Laravel Components 5.7.*) |

    2. Detail description about this issue(error/log)

    直接执行下面的报错, 就没有任何提示了。 [root@xxx]# php artisan laravels start Usage: [/data/services/php7/bin/php] ./artisan laravels publish|config|info

    1. Give us a reproducible code block and steps

      //TODO: Your code
      
    question 
    opened by dafa168 14
  • Open transactions must be committed or rolled back, otherwise it will affect other requests. 打开的事务必须提交或回滚,否则会影响其他请求。

    Open transactions must be committed or rolled back, otherwise it will affect other requests. 打开的事务必须提交或回滚,否则会影响其他请求。

    1. Tell us your software version | Software | Version | | --------- | --------- | | PHP | 7.2.19 | | Swoole | 4.3.5 | | Laravel/Lumen | 5.4.* |

    2. Detail description about this issue(error/log)

    image

    将公司项目从迁移到swoole后,就会报这个错。最后没有办法,切回的fpm这个问题就没有了。

    1. Give us a reproducible code block and steps

      //TODO: Your code
      
    question 
    opened by cheerego 12
  •  WARNING swServer_master_onAccept (ERROR 9002): Too many connections [now: 10000]

    WARNING swServer_master_onAccept (ERROR 9002): Too many connections [now: 10000]

    1. Tell us your software version How to know it?

      # PHP
      PHP 7.2.13-1+ubuntu16.04.1+deb.sury.org+1 (cli) (built: Dec  7 2018 08:07:08) ( NTS )
      Copyright (c) 1997-2018 The PHP Group
      Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
          with Zend OPcache v7.2.13-1+ubuntu16.04.1+deb.sury.org+1, Copyright (c) 1999-2018, by Zend Technologies
          # Swoole
      swoole support => enabled
      Version => 4.2.11
      Author => Swoole Group[email: [email protected]]
      coroutine => enabled
      epoll => enabled
      eventfd => enabled
      signalfd => enabled
      cpu_affinity => enabled
      spinlock => enabled
      rwlock => enabled
      pcre => enabled
      mutex_timedlock => enabled
      pthread_barrier => enabled
      futex => enabled
      async_redis => enabled
      
      Directive => Local Value => Master Value
      swoole.enable_coroutine => On => On
      swoole.aio_thread_num => 2 => 2
      swoole.display_errors => On => On
      swoole.use_shortname => On => On
      swoole.fast_serialize => Off => Off
      swoole.unixsock_buffer_size => 8388608 => 8388608
      # Lumen
          "laravel/lumen-framework": "5.6.4",    ```
      | Software | Version |
      | --------- | --------- |
      | PHP | `7.2` |
      | Swoole | `4.2.11` |
      | Laravel/Lumen | `5.6.4` |
      
      
    2. Detail description about this issue(error/log)

      TODO

    3. Give us a reproducible code block and steps

    重现方式: 运行一段时间之后,就会满了,是不是有啥配置不对么》? 看样子不是并发连接数的问题,我们没有那么多连接, 看起来是有些连接没释放, 我们项目重,用到的连接用mysql,redis,没有做特别的配置

    waiting for user action 
    opened by pensacola1989 12
  • 怎么设置websocket的超时

    怎么设置websocket的超时

    1. Your software version (Screenshot of your startup)

      | Software | Version | | --------- | --------- | | PHP | TODO | | Swoole | TODO | | Laravel/Lumen | TODO |

    2. Detail description about this issue(error/log)

      TODO

    3. Some reproducible code blocks and steps

      // TODO: your code
      
    analyzing 
    opened by gwokwong 1
  • streamDownload下载大文件总是内存超限

    streamDownload下载大文件总是内存超限

    +---------------------------+---------+ | Component | Version | +---------------------------+---------+ | PHP | 7.4.21 | | Swoole | 4.8.12 | | LaravelS | 3.7.31 | | Laravel Framework [local] | 8.83.13 | +---------------------------+---------+

    重现代码如下。

    这是为什么?laravel streamDownload是用来下载大文件的,怎么和laravels+swoole配合就回超过内存限制?找了大半天不知道原因在哪里

    php配置的内存限制是128M,请问应该怎么解决呢?(除了修改内存限制)

    <?php
    namespace App\Http\Controllers;
    
    use Illuminate\Support\Str;
    use Illuminate\Support\Facades\Log;
    
    class IndexController extends Controller
    {
      public function export(Request $request){
        return response()->streamDownload(function () {
                $file = fopen('php://output', 'w+');
    
                $i=1;
                while ($i <= 1000000) {
                    $data = Str::random(mt_rand(100, 200));
                    fputcsv($file, [$data]);
                    $i++;
                }
    
                Log::info(memory_get_usage() / 1024 / 1024);
    
                fclose($file);
            }, 'file.csv');
      }
    
    }
    
    opened by jasonbigl 0
  • 异步延时任务时间大于max_wait_time后不执行

    异步延时任务时间大于max_wait_time后不执行

    1. Your software version (Screenshot of your startup)

      | Software | Version | | --------- | --------- | | PHP | 7.4.0 | | Swoole | 4.8.9 | | LaravelS | 3.7.34 | | Laravel Framework [dev] | 6.18.13

    2. Detail description about this issue(error/log)

    异步延时任务时间大于max_wait_time后不执行

    1. Some reproducible code blocks and steps

      # max_wait_time = 5
      # 当delay time小于max_wait_time可以执行,并输出日志
      Task::deliver($task->delay(4));
      # 当delay time大于max_wait_time不能执行
      Task::deliver($task->delay(6));
      

    `[2022-12-01T19:12:31.012289+08:00] &&&&&&&&&&&&&&taskdelay--:4 [2022-12-01T19:12:31.012917+08:00] &&&&&&&&&&&&&&taskdelay--:4 [2022-12-01T19:12:31.013092+08:00] &&&&&&&&&&&&&&taskdelay--:4 [2022-12-01T19:12:31.013236+08:00] &&&&&&&&&&&&&&taskdelay--:4 [2022-12-01T19:12:31.013368+08:00] &&&&&&&&&&&&&&taskdelay--:4 [2022-12-01T19:12:31.013525+08:00] &&&&&&&&&&&&&&taskdelay--:4 [2022-12-01T19:12:31.013656+08:00] &&&&&&&&&&&&&&taskdelay--:4 [2022-12-01T19:12:31.013780+08:00] &&&&&&&&&&&&&&taskdelay--:4 [2022-12-01T19:12:31.013900+08:00] &&&&&&&&&&&&&&taskdelay--:4 [2022-12-01T19:12:31.014019+08:00] &&&&&&&&&&&&&&taskdelay--:4 [2022-12-01T19:12:35.017835+08:00] ------------taskId--:0 [2022-12-01T19:12:35.018749+08:00] ------------taskId--:1 [2022-12-01T19:12:35.018982+08:00] ------------taskId--:2 [2022-12-01T19:12:35.019161+08:00] ------------taskId--:3 [2022-12-01T19:12:35.019327+08:00] ------------taskId--:4 [2022-12-01T19:12:35.019670+08:00] ------------taskId--:5 [2022-12-01T19:12:35.019954+08:00] ------------taskId--:6 [2022-12-01T19:12:35.020230+08:00] ------------taskId--:7 [2022-12-01T19:12:35.020526+08:00] ------------taskId--:8 [2022-12-01T19:12:35.020806+08:00] ------------taskId--:9 [2022-12-01T19:12:35.059887+08:00] agent_id:1 [2022-12-01T19:12:36.850096+08:00] agent_id:2 [2022-12-01T19:12:38.462523+08:00] agent_id:4 [2022-12-01T19:12:40.023610+08:00] agent_id:5 [2022-12-01T19:12:41.556211+08:00] agent_id:7 [2022-12-01T19:12:43.075164+08:00] agent_id:6 [2022-12-01T19:12:44.587094+08:00] agent_id:3 [2022-12-01T19:12:46.106688+08:00] agent_id:10 [2022-12-01T19:12:47.602191+08:00] agent_id:9 [2022-12-01T19:12:49.098091+08:00] agent_id:8

    [2022-12-01T19:13:22.835207+08:00] &&&&&&&&&&&&&&taskdelay--:6 [2022-12-01T19:13:22.835844+08:00] &&&&&&&&&&&&&&taskdelay--:6 [2022-12-01T19:13:22.836027+08:00] &&&&&&&&&&&&&&taskdelay--:6 [2022-12-01T19:13:22.836182+08:00] &&&&&&&&&&&&&&taskdelay--:6 [2022-12-01T19:13:22.836322+08:00] &&&&&&&&&&&&&&taskdelay--:6 [2022-12-01T19:13:22.836482+08:00] &&&&&&&&&&&&&&taskdelay--:6 [2022-12-01T19:13:22.836617+08:00] &&&&&&&&&&&&&&taskdelay--:6 [2022-12-01T19:13:22.836747+08:00] &&&&&&&&&&&&&&taskdelay--:6 [2022-12-01T19:13:22.836873+08:00] &&&&&&&&&&&&&&taskdelay--:6 [2022-12-01T19:13:22.837002+08:00] &&&&&&&&&&&&&&taskdelay--:6`

    analyzing 
    opened by xiaoxie110 0
  • 有时候的返回是正常的,有时候又是204

    有时候的返回是正常的,有时候又是204

    1. Your software version (Screenshot of your startup)

      | Software | Version | | --------- | --------- | | PHP | 7.4.21| | Swoole | 4.8.12| | Laravel| 5.7.26|

    2. Detail description about this issue(error/log) 前后端分离的项目,单独的网页请求是正常的,前端跨域请求就会偶尔报204错误。我加了日志后发现, 发现:在\vendor\hhxsv5\laravel-s\src\Illuminate\Laravel.php的handleDynamic()中

    public function handleDynamic(IlluminateRequest $request) {

        ob_start();
        if ($this->conf['is_lumen']) {
               ....
        } else {
            $response = $this->kernel->handle($request);
            $content = $response->getContent();
            app('log')->info('kernel get content is '. json_encode($content));    //这里打印的content偶尔为空值,所以返回给前端显示204
            $this->kernel->terminate($request, $response);
        }
        // prefer content in response, secondly ob
        if (!($response instanceof StreamedResponse) && (string)$content === '' && ob_get_length() > 0) {
            $response->setContent(ob_get_contents());
        }
        ob_end_clean();
        return $response;
    } 
    
    analyzing 
    opened by dengao123 0
  • No idle task worker is available

    No idle task worker is available

    1. Your software version (Screenshot of your startup)

      | Software | Version | | --------- | --------- | | PHP | 8.1 | | Swoole | 5.0.0-dev | | Laravel/Lumen | 9 |

    2. Detail description about this issue(error/log)

      image

    3. Some reproducible code blocks and steps

      class TestTask extends Task
        {
            protected $title;
      
            public function __construct($title)
            {
                $this->title = $title;
            }
      
            public function handle()
            {
                sleep(rand(1, 3));
                info($this->title . ". " . date("Y-m-d H:i:s"));
            }
        }
      
          //--------------
      
          $title = Base::generatePassword(2, 22);
          for($i = 1; $i <= 1000; $i++) {
              $t = new TestTask($title . '-' . $i);
              $t->delay(rand(1, 3));
              Task::deliver($t);
          }
      
    analyzing 
    opened by kuaifan 0
Releases(v3.7.34)
Owner
Biao Xie
Show me the code
Biao Xie
Multi-process coroutine edition Swoole spider !! Learn about Swoole's network programming and the use of its related APIs

swoole_spider php bin/spider // Just do it !! Cache use Swoole\Table; use App\Table\Cache; $table = new Table(1<<20); // capacity size $table->column

null 3 Apr 22, 2021
swoole and golang ipc, use goroutine complete swoole coroutine

swoole and golang ipc demo swoole process module exec go excutable file as sider car, use goroutine complete swoole coroutine hub.php <?php require '

null 2 Apr 17, 2022
💫 Vega is a CLI mode HTTP web framework written in PHP support Swoole, WorkerMan / Vega 是一个用 PHP 编写的 CLI 模式 HTTP 网络框架,支持 Swoole、WorkerMan

Mix Vega 中文 | English Vega is a CLI mode HTTP web framework written in PHP support Swoole, WorkerMan Vega 是一个用 PHP 编写的 CLI 模式 HTTP 网络框架,支持 Swoole、Work

Mix PHP 46 Apr 28, 2022
swoole,easyswoole,swoole framework

EasySwoole - A High Performance Swoole Framework EasySwoole is a distributed, persistent memory PHP framework based on the Swoole extension. It was cr

null 4.6k Jan 2, 2023
This package provides a high performance HTTP server to speed up your Laravel/Lumen application based on Swoole.

This package provides a high performance HTTP server to speed up your Laravel/Lumen application based on Swoole.

Swoole Taiwan 3.9k Jan 8, 2023
PSR-15 Adapter for InertiaJS

inertia-psr15 Before using this library, is important to know what is Inertia.js, what is it for and how it works, in the official Inertia.js website

Mohamed Cherif Bouchelaghem 28 Oct 26, 2022
Pug Yii2 adapter

Yii 2 Pug (ex Jade) extension This extension provides a view renderer for Pug templates for Yii framework 2.0 applications. Support GutHub issues Inst

Pug PHP 8 Jun 17, 2022
Pug template engine adapter for Slim

Pug for Slim For details about the template engine see phug-lang.com Installation Install with Composer: composer require pug/slim Usage with Slim 3 u

Pug PHP 5 May 18, 2022
Dictionary of attack patterns and primitives for black-box application fault injection and resource discovery.

FuzzDB was created to increase the likelihood of finding application security vulnerabilities through dynamic application security testing. It's the f

FuzzDB Project 7.1k Dec 27, 2022
Simple live support server with PHP Swoole Websocket and Telegram API

Telgraf Simple live support server with PHP Swoole Websocket and Telegram API. Usage Server Setup Clone repository with following command. git clone h

Adem Ali Durmuş 6 Dec 30, 2022
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
A sample CakePHP api application using CakeDC/cakephp-api and swoole as server

CakePHP Application Skeleton composer create-project --prefer-dist cakephp/app Added sample data using https://github.com/annexare/Countries Created m

Marcelo Rocha 3 Jul 28, 2022
基于 swoole 的多进程队列系统,低延时(最低毫秒级)、低资源占用, 支持一键化协程、超时控制、失败重试。可与 laravel thinkphp 等框架配合使用

multi-process-queue 基于swoole的多进程队列系统,manage进程管理子进程,master进程监听队列分发任务,worker进程执行任务, 多进程、低延时(最低毫秒级)、低资源占用。可与 laravel thinkphp 等框架配合使用 版本要求: php>=7.1 swoo

yuntian 55 Dec 12, 2022
一个极简高性能php框架,支持[swoole | php-fpm ]环境

One - 一个极简高性能php框架,支持[swoole | php-fpm ]环境 快 - 即使在php-fpm下也能1ms内响应请求 简单 - 让你重点关心用one做什么,而不是怎么用one 灵活 - 各个组件松耦合,可以灵活搭配使用,使用方法保持一致 原生sql可以和模型关系with搭配使用,

vic 862 Jan 1, 2023
球球大作战(PHP+Swoole)

球球大作战 在线多人H5游戏(H5客户端, 服务端) W A S D 控制 技术栈: PHP7.4+ Swoole4.6(多进程, WebSocket, 共享内存) SpriteJS v3(2D Canvas渲染) 演示Demo 安装: 环境要求:Linux,PHP7.4+(启用Swoole拓展)

Vacant 15 May 9, 2022
swoole worker

SwooleWorker SwooleWorker is a distributed long connection development framework based on Swoole4. 【Github】 【HomePage】 Manual 【ENGLISH】 【简体中文】 Usage s

null 123 Jan 9, 2023
🚀 Developing Rocketseat's Next Level Week (NLW#05) Application using PHP/Swoole + Hyperf

Inmana PHP ?? Developing Rocketseat 's Next Level Week (NLW#05) Application using Swoole + Hyperf. This is the app of the Elixir track. I know PHP/Swo

Leo Cavalcante 18 Jun 1, 2022
Library for Open Swoole extension

Open Swoole Library This library works with Open Swoole since release version v4.7.1. WIP Table of Contents How to Contribute Code Requirements Develo

Open Swoole 3 Dec 22, 2022
Hyperf instant messaging program based on swoole framework

Hyperf instant messaging program based on swoole framework

null 20 Aug 12, 2022