hhxsv5 / laravel-s

LaravelS is an out-of-the-box adapter between Laravel/Lumen and Swoole.
MIT License
3.84k stars 475 forks source link

Abnormal exit or error is not restarting process, need to restart supervisor #258

Open juslintek opened 4 years ago

juslintek commented 4 years ago
  1. Your software version (Screenshot of your startup)

Run inside a docker container of ubuntu:latest

FROM ubuntu:latest

ARG WWWUSER=1000
ARG XDEBUG_HOST=host.docker.internal
ARG CONTAINER_ENV=production

ENV WWWUSER=1000
ENV XDEBUG_HOST=host.docker.internal
ENV CONTAINER_ENV=production

RUN useradd -ms /bin/bash -u ${WWWUSER} vessel
WORKDIR /var/www/html

ENV TZ=UTC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

RUN set -x \
    && apt-get update \
    && apt-get install -y \
        gnupg \
        gosu \
        curl \
        dos2unix \
        inotify-tools \
        iputils-ping \
        iproute2 \
        zip \
        unzip \
        openssl \
        libssl-dev \
        git \
        supervisor \
        sqlite3 \
    && gosu nobody true

RUN apt-get install -y \
    php-dev \
    php-cli \
    php-pgsql \
    php-sqlite3 \
    php-gd \
    php-curl \
    php-memcached \
    php-imap \
    php-mysql \
    php-mbstring \
    php-xml \
    php-zip \
    php-bcmath \
    php-soap \
    php-intl \
    php-readline \
    php-msgpack \
    php-igbinary \
    php-ldap \
    php-redis

RUN pecl install -a --nobuild swoole \
    && cd "$(pecl config-get temp_dir)/swoole" \
    && phpize \
    && ./configure --enable-sockets --enable-openssl --enable-http2 --enable-mysqlnd \
    && make \
    && make install \
    && echo "extension=swoole.so" >> $(php -i | grep "php.*php.ini" | rev | cut -d' ' -f1 | rev)

RUN pecl install inotify \
    && echo "extension=inotify.so" >> $(php -i | grep "php.*php.ini" | rev | cut -d' ' -f1 | rev)

RUN git clone https://github.com/swoole/sdebug.git \
    && cd sdebug \
    && ./rebuild.sh \
    && cd .. \
    && rm -rf sdebug

RUN php -r "readfile('http://getcomposer.org/installer');" | php -- --install-dir=/usr/bin/ --filename=composer \
    && apt-get -y autoremove \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

COPY xdebug.ini /etc/php/7.4/cli/conf.d/20-xdebug.ini

EXPOSE 80

COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY start-container /usr/local/bin/start-container
RUN dos2unix /usr/local/bin/start-container \
    && chmod +x /usr/local/bin/start-container

ENTRYPOINT ["start-container"]

xdebug.ini

zend_extension=xdebug.so
xdebug.remote_enable=on
xdebug.remote_autostart=on
xdebug.remote_connect_back=off
xdebug.remote_handler=dbgp
xdebug.remote_port=9000
xdebug.idekey=DOCKER-BACKEND
xdebug.remote_host=host.docker.internal
xdebug.max_nesting_level=500
xdebug.profiler_enable_trigger=on
xdebug.profiler_output_dir=/var/www/html/storage/cache/xdebug-profiles/
;xdebug.remote_log=/var/www/html/storage/logs/xdebug-remote.log
xdebug.profiler_output_name=cachegrind.out.%R.%p

supervisor.config

[supervisord]
nodaemon=true
user=root

[program:horizon]
process_name=%(program_name)s
command=php /var/www/html/artisan horizon
autostart=true
autorestart=true
user=vessel
redirect_stderr=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stopwaitsecs=3600

[program:swoole]
process_name=%(program_name)s
directory=/var/www/html/
command=bin/laravels start -i
numprocs=1
autostart=true
autorestart=true
startretries=3
user=root
redirect_stderr=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0

+---------------------------+---------+ | Component | Version | +---------------------------+---------+ | PHP | 7.4.3 | | Swoole | 4.5.1 | | LaravelS | 3.7.3 | | Laravel Framework [local] | 6.18.16 | +---------------------------+---------+

root@a80ea3cc87a2:/var/www/html# php -m
[PHP Modules]
bcmath
calendar
Core
ctype
curl
date
dom
exif
FFI
fileinfo
filter
ftp
gd
gettext
hash
iconv
igbinary
imap
inotify
intl
json
ldap
libxml
mbstring
memcached
msgpack
mysqli
mysqlnd
openssl
pcntl
pcre
PDO
pdo_mysql
pdo_pgsql
pdo_sqlite
pgsql
Phar
posix
readline
redis
Reflection
sdebug
session
shmop
SimpleXML
soap
sockets
sodium
SPL
sqlite3
standard
swoole
sysvmsg
sysvsem
sysvshm
tokenizer
xml
xmlreader
xmlwriter
xsl
Zend OPcache
zip
zlib

[Zend Modules]
Sdebug
Zend OPcache
  1. Detail description about this issue(error/log)

If a Task or process gets the following error, the process is not restarted even if it should do that after 5s of abnormal exit.

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

Speed up your Laravel/Lumen
>>> Components
+---------------------------+---------+
| Component                 | Version |
+---------------------------+---------+
| PHP                       | 7.4.3   |
| Swoole                    | 4.5.1   |
| LaravelS                  | 3.7.3   |
| Laravel Framework [local] | 6.18.16 |
+---------------------------+---------+
>>> Protocols
+-----------+--------+-------------------+------------+
| Protocol  | Status | Handler           | Listen At  |
+-----------+--------+-------------------+------------+
| Main HTTP | On     | Laravel Framework | 0.0.0.0:80 |
+-----------+--------+-------------------+------------+
>>> Feedback: https://github.com/hhxsv5/laravel-s
[2020-05-29 18:39:44] [TRACE] Swoole is running, press Ctrl+C to quit.
[2020-05-29 18:40:33] [ERROR] Uncaught exception 'Exception': [0]Serialization of 'Closure' is not allowed called in /var/www/html/vendor/hhxsv5/laravel-s/src/Swoole/Task/BaseTask.php:93
#0 /var/www/html/vendor/hhxsv5/laravel-s/src/Swoole/Task/BaseTask.php(93): Swoole\Server->sendMessage()
#1 /var/www/html/vendor/hhxsv5/laravel-s/src/Swoole/Task/BaseTask.php(103): Hhxsv5\LaravelS\Swoole\Task\BaseTask->Hhxsv5\LaravelS\Swoole\Task\{closure}()
#2 /var/www/html/vendor/hhxsv5/laravel-s/src/Swoole/Task/Task.php(24): Hhxsv5\LaravelS\Swoole\Task\BaseTask->task()
#3 /var/www/html/app/Processes/WebSocketClientProcess.php(57): Hhxsv5\LaravelS\Swoole\Task\Task::deliver()
#4 [internal function]: App\Processes\WebSocketClientProcess::callback()
#5 /var/www/html/vendor/hhxsv5/laravel-s/src/Illuminate/LogTrait.php(82): call_user_func_array()
#6 /var/www/html/vendor/hhxsv5/laravel-s/src/Swoole/Process/CustomProcessTrait.php(57): Hhxsv5\LaravelS\LaravelS->callWithCatchException()
#7 {main}
PHP Fatal error:  Uncaught ErrorException: Swoole\Server::start(): Unexpected end of serialized data in /var/www/html/vendor/hhxsv5/laravel-s/src/Swoole/Server.php:345
Stack trace:
#0 [internal function]: Illuminate\Foundation\Bootstrap\HandleExceptions->handleError()
#1 /var/www/html/vendor/hhxsv5/laravel-s/src/Swoole/Server.php(345): Swoole\Server->start()
#2 /var/www/html/vendor/hhxsv5/laravel-s/src/Console/Portal.php(153): Hhxsv5\LaravelS\Swoole\Server->run()
#3 /var/www/html/vendor/hhxsv5/laravel-s/src/Console/Portal.php(58): Hhxsv5\LaravelS\Console\Portal->start()
#4 /var/www/html/vendor/symfony/console/Command/Command.php(255): Hhxsv5\LaravelS\Console\Portal->execute()
#5 /var/www/html/bin/laravels(164): Symfony\Component\Console\Command\Command->run()
#6 {main}
  thrown in /var/www/html/vendor/hhxsv5/laravel-s/src/Swoole/Server.php on line 345
PHP Stack trace:
PHP   1. {main}() /var/www/html/bin/laravels:0
PHP   2. Hhxsv5\LaravelS\Console\Portal->run() /var/www/html/bin/laravels:164
PHP   3. Hhxsv5\LaravelS\Console\Portal->execute() /var/www/html/vendor/symfony/console/Command/Command.php:255
PHP   4. Hhxsv5\LaravelS\Console\Portal->start() /var/www/html/vendor/hhxsv5/laravel-s/src/Console/Portal.php:58
PHP   5. Hhxsv5\LaravelS\LaravelS->run() /var/www/html/vendor/hhxsv5/laravel-s/src/Console/Portal.php:153
PHP   6. Swoole\Http\Server->start() /var/www/html/vendor/hhxsv5/laravel-s/src/Swoole/Server.php:345

   Symfony\Component\Debug\Exception\FatalErrorException  : Uncaught ErrorException: Swoole\Server::start(): Unexpected end of serialized data in /var/www/html/vendor/hhxsv5/laravel-s/src/Swoole/Server.php:345
Stack trace:
#0 [internal function]: Illuminate\Foundation\Bootstrap\HandleExceptions->handleError()
#1 /var/www/html/vendor/hhxsv5/laravel-s/src/Swoole/Server.php(345): Swoole\Server->start()
#2 /var/www/html/vendor/hhxsv5/laravel-s/src/Console/Portal.php(153): Hhxsv5\LaravelS\Swoole\Server->run()
#3 /var/www/html/vendor/hhxsv5/laravel-s/src/Console/Portal.php(58): Hhxsv5\LaravelS\Console\Portal->start()
#4 /var/www/html/vendor/symfony/console/Command/Command.php(255): Hhxsv5\LaravelS\Console\Portal->execute()
#5 /var/www/html/bin/laravels(164): Symfony\Component\Console\Command\Command->run()
#6 {main}
  thrown

  at /var/www/html/vendor/hhxsv5/laravel-s/src/Swoole/Server.php:345
    341|     }
    342| 
    343|     public function run()
    344|     {
  > 345|         $this->swoole->start();
    346|     }
    347| }
    348|

  Exception trace:

  1   Swoole\Http\Server::start()
      /var/www/html/vendor/hhxsv5/laravel-s/src/Swoole/Server.php:345

  2   Hhxsv5\LaravelS\LaravelS::run()
      /var/www/html/vendor/hhxsv5/laravel-s/src/Console/Portal.php:153

  Please use the argument -v to see more details.

   Whoops\Exception\ErrorException  : Uncaught ErrorException: Swoole\Server::start(): Unexpected end of serialized data in /var/www/html/vendor/hhxsv5/laravel-s/src/Swoole/Server.php:345
Stack trace:
#0 [internal function]: Illuminate\Foundation\Bootstrap\HandleExceptions->handleError()
#1 /var/www/html/vendor/hhxsv5/laravel-s/src/Swoole/Server.php(345): Swoole\Server->start()
#2 /var/www/html/vendor/hhxsv5/laravel-s/src/Console/Portal.php(153): Hhxsv5\LaravelS\Swoole\Server->run()
#3 /var/www/html/vendor/hhxsv5/laravel-s/src/Console/Portal.php(58): Hhxsv5\LaravelS\Console\Portal->start()
#4 /var/www/html/vendor/symfony/console/Command/Command.php(255): Hhxsv5\LaravelS\Console\Portal->execute()
#5 /var/www/html/bin/laravels(164): Symfony\Component\Console\Command\Command->run()
#6 {main}
  thrown

  at /var/www/html/vendor/hhxsv5/laravel-s/src/Swoole/Server.php:345
    341|     }
    342| 
    343|     public function run()
    344|     {
  > 345|         $this->swoole->start();
    346|     }
    347| }
    348|

  Exception trace:

  1   Whoops\Run::handleError()
      /var/www/html/vendor/filp/whoops/src/Whoops/Run.php:408

  2   Whoops\Run::handleShutdown()
      [internal]:0
[2020-05-29 18:40:34] [ERROR] worker[5] error: exitCode=255, signal=0
[2020-05-29 18:40:40] [INFO] [Inotify] watched files: 9244; file types: .php,.env; excluded directories: 
  1. Some reproducible code blocks and steps

No code necessary, just do configs like this:

<?php

declare(strict_types = 1);

return [
    'listen_ip' => '0.0.0.0',
    'listen_port' => 80,
    'socket_type' => env('SWOOLE_SOCKET_TYPE', 1),
    'enable_coroutine_runtime' => false,
    'server' => env('LARAVELS_SERVER', 'LaravelS'),
    'handle_static' => env('LARAVELS_HANDLE_STATIC', false),
    'laravel_base_path' => env('LARAVEL_BASE_PATH', base_path()),
    'inotify_reload' => [
        'enable' => env('LARAVELS_INOTIFY_RELOAD', false),
        'watch_path' => base_path(),
        'file_types' => ['.php', '.env'],
        'excluded_dirs' => [],
        'log' => true,
    ],
    'event_handlers' => [],
    'websocket' => [
        'enable' => false,
    ],
    'sockets' => [],
    'processes' => [
        'WebSocketProcess' => [
            'class' => \App\Processes\WebSocketClientProcess::class,
            'redirect' => false,
            'pipe' => 0,
            'enable' => true, 
            'queue' => [
                'msg_key' => 0,
                'mode' => 2,
                'capacity' => 8192,
            ],
            'restart_interval' => 5,
        ],
    ],
    'timer' => [
        'enable' => env('LARAVELS_TIMER', false),
        'jobs' => [
            // 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\XxxCronJob::class, [1000, true]], // Pass in parameters when registering
            // \App\Jobs\XxxCronJob::class, // Override the corresponding method to return the configuration
        ],
        'max_wait_time' => 5,
    ],
    'swoole_tables' => [],
    'register_providers' => [
        \Laravel\Horizon\HorizonServiceProvider::class,
        \App\Providers\AzureLogicAppsProvider::class,
        \App\Providers\BroadcastServiceProvider::class,
        \App\Providers\EventServiceProvider::class,
        \App\Providers\RouteServiceProvider::class,
        \App\Providers\AuthServiceProvider::class,
    ],
    'cleaners' => [
        // See LaravelS's built-in cleaners: https://github.com/hhxsv5/laravel-s/blob/master/Settings.md#cleaners
    ],
    'destroy_controllers' => [
        'enable' => false,
        'excluded_list' => [
            //\App\Http\Controllers\TestController::class,
        ],
    ],
    'swoole' => [
        'daemonize' => env('LARAVELS_DAEMONIZE', false),
        'dispatch_mode' => 2,
        'reactor_num' => env('LARAVELS_REACTOR_NUM', function_exists('swoole_cpu_num') ? swoole_cpu_num() * 2 : 4),
        'worker_num' => env('LARAVELS_WORKER_NUM', function_exists('swoole_cpu_num') ? swoole_cpu_num() * 2 : 8),
        'task_worker_num' => env(
            'LARAVELS_TASK_WORKER_NUM',
            function_exists('swoole_cpu_num') ? swoole_cpu_num() * 2 : 8
        ),
        'task_ipc_mode' => 1,
        'task_max_request' => env('LARAVELS_TASK_MAX_REQUEST', 8000),
        'task_tmpdir' => is_writable('/dev/shm/') ? '/dev/shm' : '/tmp',
        'max_request' => env('LARAVELS_MAX_REQUEST', 8000),
        'open_tcp_nodelay' => true,
        'pid_file' => storage_path('laravels.pid'),
        'log_file' => storage_path(sprintf('logs/swoole-%s.log', date('Y-m-d'))),
        'log_level' => 1,
        'document_root' => base_path('public'),
        'buffer_output_size' => 2 * 1024 * 1024,
        'socket_buffer_size' => 128 * 1024 * 1024,
        'package_max_length' => 4 * 1024 * 1024,
        'reload_async' => true,
        'max_wait_time' => 60,
        'enable_reuse_port' => true,
        'enable_coroutine' => true,
        'http_compression' => true,
        'heartbeat_idle_time' => 600,
        'heartbeat_check_interval' => 60,

        // Slow log
        // 'request_slowlog_timeout' => 2,
        // 'request_slowlog_file'    => storage_path(sprintf('logs/slow-%s.log', date('Y-m'))),
        // 'trace_event_worker'      => true,

        /*
         * More settings of Swoole
         * @see https://wiki.swoole.com/#/server/setting  Chinese
         * @see https://www.swoole.co.uk/docs/modules/swoole-server/configuration  English
         */
    ],
];

and create processes with task, run inside task closure after error remove it and try var dumping something. It will not happen, service will be stuck.

I want to make swoole to always listen to WebSocket. Seems like after some time it becomes unresponsive and that's it. Websocket frames are sent, but swoole coroutine client is not picking up any frames anymore...

hhxsv5 commented 4 years ago

@juslintek The member properties of your task can only be in the limited types (boolean/integer/float/string/null/array/object), callable/resource cannot be serialized.

[2020-05-29 18:40:33] [ERROR] Uncaught exception 'Exception': [0]Serialization of 'Closure' is not allowed called in /var/www/html/vendor/hhxsv5/laravel-s/src/Swoole/Task/BaseTask.php:93

BTW, xdebug conflicts with swoole, they cannot be enabled at the same time.

juslintek commented 4 years ago

I use sdebug the swoole compatible one. But one way or another it doesn't work... Is there any tool like xdebug for swoole?

P.S. problem is not the error I get. It is obvious. Its that processes after reload is not connecting to websocket again. Seems like callback is not happening anymore after reload or error. Here is process and task codes:

Process:

<?php

declare(strict_types=1);

namespace App\Processes;

use App\Services\DataloyApiClient;
use App\Tasks\FrameToNotificationConversion;
use Hhxsv5\LaravelS\Swoole\Process\CustomProcessInterface;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Swoole\Http\Server;
use Swoole\Process;
use Swoole\WebSocket\Frame;
use Swoole\Coroutine\HTTP\Client;

class WebSocketClientProcess implements CustomProcessInterface
{
    /**
     * @var bool Quit tag for Reload updates
     */
    private static bool $quit = false;

    private static string $accessToken = '';

    private static Client $client;

    public static function callback(Server $swoole, Process $process): void
    {
        $dataloyClient = new DataloyApiClient();
        if (self::$accessToken === '') {
            self::$accessToken = $dataloyClient->getAccessToken();
        }

        self::$client = new Client(
            'socket-server',
            80
        );

        self::$client->setHeaders([
            'Host' => 'socket-server',
            'User-Agent' => 'Chrome/49.0.2587.3',
            'Accept' => 'application/json',
            'Accept-Encoding' => 'gzip',
        ]);

        self::$client->set(['timeout' => 1]);

        $upgraded = self::$client->upgrade('/ws/rest/WebSocketAlertScriptRaw/COA/' . self::$accessToken);

        if (!$upgraded) {
            return;
        }

        while (!self::$quit) {
            $frame = self::$client->recv();
            if ($frame instanceof Frame) {
                $task = new FrameToNotificationConversion($frame);
                $task::deliver($task);
            }
        }
    }

    // Requirements: LaravelS >= v3.4.0 & callback() must be async non-blocking program.
    public static function onReload(Server $swoole, Process $process): void
    {
        \Log::info('WebSocket Client process: reloading (' . self::$client->host . ')');
        self::$quit = true;
        self::$client->close();
    }
}

Task:

<?php

declare(strict_types = 1);

namespace App\Tasks;

use App\Company;
use App\Notifications\ShipmentArrival;
use App\Services\DataloyApiClient;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Notification;
use Swoole\WebSocket\Frame;

class FrameToNotificationConversion extends Task
{
    private array $data;

    private array $result = [];

    public function __construct(Frame $frame)
    {
        $this->data = json_decode($frame->data, true);
    }

    // The logic of task handling, run in task process, CAN NOT deliver task
    public function handle(): void
    {
        \Log::info(__CLASS__ . ':handle start', [$this->data]);

        foreach ($this->data['cargos'] as $cargo) {
            $cargoKey = $cargo['key'];
            try {
                $cargo = (new DataloyApiClient())
                    ->get('Cargo', $cargoKey)
                    ->fields(json_encode(
                        [
                            'cargoReference' => '',
                            'charterer' => [
                                'businessPartnerCode' => '',
                                'businessPartnerName' => '',
                            ],
                            'cargoPorts' => [
                                'port' => [
                                    'portName' => '',
                                ],
                                'reasonForCall' => [
                                    'reasonForCall' => '',
                                ],
                            ],
                        ],
                        JSON_THROW_ON_ERROR | JSON_FORCE_OBJECT
                    ))
                    ->fetch();
            } catch (\Exception $exception) {
                \Log::error($exception->getMessage() . '. CargoKey:' . $cargoKey);
                continue;
            }

            try {
                $company = Company::where(
                    'dataloy_code',
                    $cargo->charterer->businessPartnerCode
                )
                    ->firstOrFail();
            } catch (ModelNotFoundException $modelNotFoundException) {
                \Log::notice(
                    $modelNotFoundException->getMessage()
                    . ' with dataloy_code = '
                    . $cargo->charterer->businessPartnerCode
                    . ' in '
                    . $modelNotFoundException->getFile()
                    . ':'
                    . $modelNotFoundException->getLine()
                );
                continue;
            }

            $messageData = [
                'port_name' => $cargo->cargoPorts[1]->port->portName,
                'shipment_id' => $cargo->cargoReference,
                'action_url' => '/shipment/' . $cargo->cargoReference,
                'new_eta' => $this->data['newETA'],
            ];

            $this->result[] = $messageData;

            Notification::send($company->users, new ShipmentArrival($messageData));
        }
    }

    // Optional, finish event, the logic of after task handling, run in worker process, CAN deliver task
    public function finish(): void
    {
        \Log::info(__CLASS__ . ':finish start', [$this->result]);
    }
}
hhxsv5 commented 4 years ago

As far as I know, currently only sdebug. About the reload problem, I do not reproduce.

class WebSocketService implements WebSocketHandlerInterface
{
    public function __construct()
    {
    }

    public function onOpen(Server $server, Request $request)
    {
        $server->push($request->fd, 'Welcome to LaravelS[main]#' . $request->fd);
    }

    public function onMessage(Server $server, Frame $frame)
    {
        $server->push($frame->fd, 'LaravelS: ' . $frame->data);
    }

    public function onClose(Server $server, $fd, $reactorId)
    {
    }
}
class TestProcess implements CustomProcessInterface
{
    protected static $quit = false;

    public static function callback(Server $swoole, Process $process)
    {
        \Log::info(__METHOD__ . ':start');
        $cli = new Client('127.0.0.1', 5200);
        $ret = $cli->upgrade('/');
        if ($ret) {
            while (!self::$quit) {
                $cli->push('hello ' . date('Y-m-d H:i:s'));
                $frame = $cli->recv();
                Task::deliver(new TestWebSocketTask($frame));
                Coroutine::sleep(1);
            }
        }
        \Log::info(__METHOD__ . ':quit');
    }

    public static function onReload(Server $swoole, Process $process)
    {
        \Log::info('custom process: reloading');
        self::$quit = true;
    }
}
class TestWebSocketTask extends Task
{
    private $frame;
    private $result;

    public function __construct(Frame $frame)
    {
        $this->frame = $frame;
    }

    public function handle()
    {
        \Log::info(__METHOD__, [$this->frame->data]);
        $this->result = 'the result: ' . microtime(true);
    }

    public function finish()
    {
        \Log::info(__METHOD__, [$this->result]);
    }
}
[2020-05-31 10:26:10] local.INFO: App\Tasks\TestWebSocketTask::handle ["LaravelS: hello 2020-05-31 10:26:09"]
[2020-05-31 10:26:16] local.INFO: App\Processes\TestProcess::callback:start
[2020-05-31 10:26:16] local.INFO: App\Tasks\TestWebSocketTask::handle ["Welcome to LaravelS[main]#1"]
[2020-05-31 10:26:17] local.INFO: App\Tasks\TestWebSocketTask::handle ["LaravelS: hello 2020-05-31 10:26:16"]
[2020-05-31 10:26:18] local.INFO: App\Tasks\TestWebSocketTask::handle ["LaravelS: hello 2020-05-31 10:26:17"]
[2020-05-31 10:26:19] local.INFO: App\Tasks\TestWebSocketTask::handle ["LaravelS: hello 2020-05-31 10:26:18"]
[2020-05-31 10:26:20] local.INFO: App\Tasks\TestWebSocketTask::handle ["LaravelS: hello 2020-05-31 10:26:19"]
[2020-05-31 10:26:21] local.INFO: App\Tasks\TestWebSocketTask::handle ["LaravelS: hello 2020-05-31 10:26:20"]
[2020-05-31 10:26:22] local.INFO: App\Tasks\TestWebSocketTask::handle ["LaravelS: hello 2020-05-31 10:26:21"]
[2020-05-31 10:26:23] local.INFO: App\Tasks\TestWebSocketTask::handle ["LaravelS: hello 2020-05-31 10:26:22"]
[2020-05-31 10:26:24] local.INFO: App\Tasks\TestWebSocketTask::handle ["LaravelS: hello 2020-05-31 10:26:23"]
[2020-05-31 10:26:25] local.INFO: App\Tasks\TestWebSocketTask::handle ["LaravelS: hello 2020-05-31 10:26:24"]
[2020-05-31 10:26:26] local.INFO: App\Tasks\TestWebSocketTask::handle ["LaravelS: hello 2020-05-31 10:26:25"]
[2020-05-31 10:26:27] local.INFO: App\Tasks\TestWebSocketTask::handle ["LaravelS: hello 2020-05-31 10:26:26"]
[2020-05-31 10:26:27] local.INFO: custom process: reloading
[2020-05-31 10:26:28] local.INFO: App\Processes\TestProcess::callback:quit
[2020-05-31 10:26:35] local.INFO: App\Processes\TestProcess::callback:start
[2020-05-31 10:26:35] local.INFO: App\Tasks\TestWebSocketTask::handle ["Welcome to LaravelS[main]#2"]
[2020-05-31 10:26:36] local.INFO: App\Tasks\TestWebSocketTask::handle ["LaravelS: hello 2020-05-31 10:26:35"]
[2020-05-31 10:26:37] local.INFO: App\Tasks\TestWebSocketTask::handle ["LaravelS: hello 2020-05-31 10:26:36"]
[2020-05-31 10:26:38] local.INFO: App\Tasks\TestWebSocketTask::handle ["LaravelS: hello 2020-05-31 10:26:37"]
[2020-05-31 10:26:39] local.INFO: App\Tasks\TestWebSocketTask::handle ["LaravelS: hello 2020-05-31 10:26:38"]
juslintek commented 4 years ago

@hhxsv5, But is WebSocket Service websocket client like new Websocket on javascript? Or WebSocket Server, like laravel-echo-server?

Because I currently need WebSocket as a client, not a service.

Maybe your wrapper cannot handle WebSocket clients?

Can you use my code, just without the processing complexity of data?

You can use this server for testing: https://www.websocket.org/echo.html wss://echo.websocket.org you can send any messages via the form at their website.

P.S. Have you used my Dockerfile?

I am adding composer.json in case:

{
    "name": "juslintek/laravels-websocket-client",
    "type": "project",
    "description": "Swoole websocket client in laravel",
    "keywords": [
        "framework",
        "laravel"
    ],
    "license": "MIT",
    "require": {
        "php": "^7.4",
        "ext-json": "*",
        "fideloper/proxy": "^4.0",
        "hhxsv5/laravel-s": "^3.7",
        "laravel/framework": "^6.2",
        "laravel/horizon": "^3.4",
        "laravel/passport": "^8.0",
        "laravel/socialite": "^4.3",
        "laravel/tinker": "^1.0",
        "predis/predis": "^1.1",
        "rennokki/laravel-eloquent-query-cache": "^1.3",
        "socialiteproviders/microsoft-azure": "^3.0",
        "spatie/enum": "^2.3",
        "spatie/laravel-responsecache": "^6.5"
    },
    "require-dev": {
        "barryvdh/laravel-ide-helper": "^2.6",
        "codedungeon/phpunit-result-printer": "^0.26.2",
        "eaglewu/swoole-ide-helper": "dev-master",
        "facade/ignition": "^1.4",
        "fzaninotto/faker": "^1.4",
        "laravel/homestead": "^9.4",
        "laravel/telescope": "^3.5",
        "laravel/ui": "^1.1",
        "mockery/mockery": "^1.0",
        "nunomaduro/collision": "^3.0",
        "nunomaduro/larastan": "^0.5.8",
        "nunomaduro/phpinsights": "^1.9",
        "phpmd/phpmd": "^2.8",
        "phpstan/phpstan": "^0.12",
        "phpunit/phpunit": "^8.0",
        "symplify/easy-coding-standard": "^7.0",
        "yurunsoft/guzzle-swoole": "^2.1"
    },
    "config": {
        "optimize-autoloader": true,
        "preferred-install": "dist",
        "sort-packages": true
    },
    "extra": {
        "laravel": {
            "dont-discover": [
                "laravel/socialite"
            ]
        }
    },
    "autoload": {
        "psr-4": {
            "App\\": "app/"
        },
        "classmap": [
            "database/seeds",
            "database/factories"
        ]
    },
    "autoload-dev": {
        "psr-4": {
            "Tests\\": "tests/"
        }
    },
    "minimum-stability": "dev",
    "prefer-stable": true,
    "scripts": {
        "phpstan:test": "phpstan analyze --ansi --memory-limit=1G",
        "phpunit:test": "phpunit  --colors=always",
        "ecs:test": "ecs check app tests",
        "ecs:fix": "ecs check --fix app tests",
        "php-cs-fixer:fix": "php-cs-fixer fix --config=.php_cs -v --using-cache=no ./app ./tests ./routes ./database",
        "insights:test": "php artisan insights --no-interaction",
        "test": [
            "@insights:test",
            "@phpunit:test"
        ],
        "ide-helper:generate": [
            "php artisan ide-helper:generate",
            "php artisan ide-helper:meta",
            "php artisan ide-helper:models --nowrite"
        ],
        "post-autoload-dump": [
            "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
            "@php artisan package:discover --ansi"
        ],
        "post-root-package-install": [
            "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
        ],
        "post-create-project-cmd": [
            "@php artisan key:generate --ansi"
        ],
        "post-update-cmd": "@ide-helper:generate"
    }
}

And as well .env file:

APP_PORT=80
MYSQL_PORT=3306
IGNITION_THEME=dark

APP_NAME=Laravel
APP_ENV=local
APP_KEY= # generate your own code
APP_DEBUG=true
APP_URL=http://localhost
#APP_RUNNING_IN_CONSOLE=false

MIX_SENTRY_DSN_PUBLIC="localhost"

LOG_CHANNEL=stack

# run MySQL container for that.
DB_CONNECTION=mysql
DB_HOST=mysql #docker MySQL container
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=secret

REDIS_CLIENT=phpredis
REDIS_HOST=redis #docker redis container
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_CACHE_DB=2
REDIS_QUEUES_DB=3
REDIS_HORIZON_CONNECTION=default
REDIS_PREFIX=

BROADCAST_DRIVER=redis
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
SESSION_LIFETIME=120

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=

PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1

MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

TELESCOPE_ENABLED=true

PASSPORT_LOGIN_URL=${APP_URL}/oauth/token
PASSPORT_CLIENT_ID= #genrate your own
PASSPORT_CLIENT_SECRET= #genrate your own

PHP_IDE=phpstorm

LARAVEL_ECHO_SERVER_AUTH_HOST=http://localhost
LARAVEL_ECHO_SERVER_REDIS_HOST=${REDIS_HOST}
LARAVEL_ECHO_SERVER_REDIS_PORT=${REDIS_PORT}

MIX_SOCKETIO_APP_ID=
MIX_SOCKETIO_APP_KEY=

HORIZON_BASIC_AUTH_USERNAME=
HORIZON_BASIC_AUTH_PASSWORD=

LARAVELS_INOTIFY_RELOAD=true
LARAVELS_LISTEN_IP=0.0.0.0
LARAVELS_LISTEN_PORT=80
LARAVELS_TIMER=true
SWOOLE_SOCKET_TYPE=1
LARAVELS_REACTOR_NUM=2
LARAVELS_WORKER_NUM=4
LARAVELS_TASK_WORKER_NUM=4
juslintek commented 4 years ago

UPDATE Seems like it stops listening after the 60s or something and you have to manually restart service in order for socket to continue listening. image

juslintek commented 4 years ago

UPDATE: Guess it was more of a swoole thing. Solved it by changing heartbeat check interval and hearbeat_idle_time: config/laravels.php

    'swoole' => [
        // ...
        'heartbeat_idle_time' => 60,
        'heartbeat_check_interval' => 10,
         // ...
     ],

Guess to prevent also tried this in the loop:

        while (!self::$quit) {
            $frame = self::$client->recv();
            if ($frame instanceof Frame) {
                $task = new FrameToNotificationConversion($frame);
                $task::deliver($task);
            }

            if (!self::$client->connected) {
                self::$quit = true;
            }
        }

and it worked. This one creates rapid reconnect, can change networks whatever you want. Very persistent. :-) Even better than health checks. :-) Basically reruns process a new. Can even put your computer to sleep run back again and bam its running and listening to socket. :D

Super awesome.

hhxsv5 commented 4 years ago

@juslintek According to the Swoole documentation, after 4.x, the connected property is no longer updated in real time and is no longer reliable. So I suggest to use errCode as the connection status judgment.

while (!self::$quit) {
    $frame = self::$client->recv();
    if (!self::$client->errCode) {
        Log::error('An error occurred', [$client->errCode, $client->errMsg]);
        self::$quit = true;
    }
    if ($frame instanceof Frame) {
        $task = new FrameToNotificationConversion($frame);
        $task::deliver($task);
    }
}
juslintek commented 4 years ago

@hhxsv5 thank you very much, I will change code accordingly. :-)

juslintek commented 4 years ago

@hhxsv5 When this process runs for a very long time, ./bin/laravels stop doesn't kill it. It continues working in the background unless you add timeouts to the connection, then it waits for a connection to timeout and after that gets killed, but if the connection is continuous like WebSocket, then does not get interrupted and killed. So the only way to kill it is with:

kill -9 PID

I thought that onStop it will get killed. But it doesn't.

I see one solution is to use the client as a static property for the whole process. And onStop, close its connection.

What do you think? Do I need to send some keepalive or ping pong to check if the connection is running? I basically need to stop it instantly on-demand.

juber-nunes commented 3 years ago

I'm running laravel-s and trying to implement sdebug, could you guys assist on this?

Its seems to be connecting to my vscode but it drops right after. is there any specific configuration I need to change?

juslintek commented 3 years ago

@juber-ivre, Sdebug works only with phpunit. :-) Or first request in requests sequence or I assume it gets clogged somewhere and cannot trace requests. You can check my issues related to sdebug. Basically if you retry request from dev tools network it usually hits the breakpoint. But you do page reload it gets ignored. After it gets ignored you have to restart laravels process. Then again you can catch sdebug responses on breakpoints.