laravel / reverb

Laravel Reverb provides a real-time WebSocket communication backend for Laravel applications.
https://reverb.laravel.com
MIT License
942 stars 63 forks source link

Dispatch a broadcast event all time fails when it is sends from a MessageReceived listener #209

Closed wazzu3000 closed 1 month ago

wazzu3000 commented 1 month ago

Reverb Version

1.0.0-beta12

Laravel Version

11.9

PHP Version

8.2.8

Description

Hi everybody and sorry for my terrible English.

From the last week I started to work with Laravel Reverb and I'm getting an error that is making me crazy. At time to call an event dispatch function from a MessageReceived listener, I'm receiving this error message:

Pusher error: cURL error 28: Operation timed out after 30009 milliseconds with 0 bytes received (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for http://localhost:8080/apps/751596/events?auth_key=ygvwwztqu7pi8egjbq8b&auth_timestamp=1718031505&auth_version=1.0&body_md5=ce1a06ea08584ba2c0e0777a13c875ef&auth_signature=f7d413d1d1d7881ddbcce5a40f565022899d710c39ccd678b3e39214915f19a2

Steps To Reproduce

  1. I created a new Laravel project and installed the following packages: laravel/reverb, mongodb/laravel-mongodb, php-open-source-saver/jwt-auth and laravel/sanctum.
  2. Created a new broadcast event with the following code:
<?php

namespace App\Events;

use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class DbReplication implements ShouldBroadcastNow
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     */
    public function __construct(
        private string $accountId,
        public string $message
    ) { }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return PresenceChannel
     */
    public function broadcastOn(): PresenceChannel
    {
        return new PresenceChannel("DbReplication.{$this->accountId}");
    }
}
  1. Created a new listener with the following code:
<?php

namespace App\Listeners;

use App\Events\DbReplication;
use Illuminate\Support\Str;
use Laravel\Reverb\Events\MessageReceived;

class DbReplicationListener
{
    /**
     * Create the event listener.
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     */
    public function handle(MessageReceived $event): void
    {
        $message = json_decode($event->message);
        if (Str::startsWith($message->channel ?? '', 'presence-DbReplication.'))
        {
            $accountId = (string) str($message->channel)->match('/\w+$/');
            // This line is throwing the error
            DbReplication::dispatch($accountId, 'hello world'); 
            /**
             * I tried use too:
             * DbReplication::broadcast($accountId, 'hello world')
             * broadcast(new DbReplication($accountId, 'hello world'))
             *
             * All are throwing the same error
             */
        }
    }
}
  1. I registered the listener in App\Providers\AppServiceProvider class:
<?php

namespace App\Providers;

use App\Listeners\DbReplicationListener;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
use Laravel\Reverb\Events\MessageReceived;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        //
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Event::listen(
            MessageReceived::class,
            DbReplicationListener::class
        );
    }
}
  1. And I added the broadcast channel using the following code:
<?php

use Illuminate\Support\Facades\Broadcast;

Broadcast::channel('App.Models.User.{id}', function ($user, $id) {
    return (int) $user->id === (int) $id;
});

Broadcast::channel('DbReplication.{account}', fn () => true);

I'm using the following environment variables for Laravel Reverb.

BROADCAST_CONNECTION=reverb

# I included pusher values because I'm not sure if this could produce the error
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1

REVERB_APP_ID=751596
REVERB_APP_KEY=ygvwwztqu7pi8egjbq8b
REVERB_APP_SECRET=xg2sadjwdqynmuw6j3t1
REVERB_HOST="localhost"
REVERB_PORT=8080
REVERB_SCHEME=http
joedixon commented 1 month ago

You'll need to queue the listener in order to prevent a deadlock.

The issue with broadcasting (without queuing) is that you are making an HTTP request to the WebSocket server, which can't accept the request until the current process is complete. Of course, the current process cannot complete as it's waiting on the HTTP request so you end up in deadlock.