88use Illuminate \Queue \QueueManager ;
99use Illuminate \Queue \WorkerOptions ;
1010use Illuminate \Support \Str ;
11+ use PhpAmqpLib \Connection \AMQPStreamConnection ;
1112use PhpAmqpLib \Exception \AMQPChannelClosedException ;
1213use PhpAmqpLib \Exception \AMQPConnectionClosedException ;
1314use PhpAmqpLib \Exception \AMQPProtocolChannelException ;
1920use Salesmessage \LibRabbitMQ \Dto \ConsumeVhostsFiltersDto ;
2021use Salesmessage \LibRabbitMQ \Dto \QueueApiDto ;
2122use Salesmessage \LibRabbitMQ \Dto \VhostApiDto ;
23+ use Salesmessage \LibRabbitMQ \Exceptions \MutexTimeout ;
2224use Salesmessage \LibRabbitMQ \Interfaces \RabbitMQBatchable ;
2325use Salesmessage \LibRabbitMQ \Mutex ;
2426use Salesmessage \LibRabbitMQ \Queue \Jobs \RabbitMQJob ;
@@ -29,6 +31,8 @@ abstract class AbstractVhostsConsumer extends Consumer
2931{
3032 protected const MAIN_HANDLER_LOCK = 'vhost_handler ' ;
3133
34+ protected const HEALTHCHECK_HANDLER_LOCK = 'healthcheck_vhost_handler ' ;
35+
3236 protected ?OutputStyle $ output = null ;
3337
3438 protected ?ConsumeVhostsFiltersDto $ filtersDto = null ;
@@ -59,6 +63,12 @@ abstract class AbstractVhostsConsumer extends Consumer
5963
6064 protected bool $ hadJobs = false ;
6165
66+ protected ?int $ stopStatusCode = null ;
67+
68+ protected array $ config = [];
69+
70+ protected bool $ asyncMode = false ;
71+
6272 /**
6373 * @param InternalStorageManager $internalStorageManager
6474 * @param LoggerInterface $logger
@@ -110,19 +120,71 @@ public function setBatchSize(int $batchSize): self
110120 return $ this ;
111121 }
112122
123+ /**
124+ * @param array $config
125+ * @return $this
126+ */
127+ public function setConfig (array $ config ): self
128+ {
129+ $ this ->config = $ config ;
130+ return $ this ;
131+ }
132+
133+ /**
134+ * @param bool $asyncMode
135+ * @return $this
136+ */
137+ public function setAsyncMode (bool $ asyncMode ): self
138+ {
139+ $ this ->asyncMode = $ asyncMode ;
140+ return $ this ;
141+ }
142+
113143 public function daemon ($ connectionName , $ queue , WorkerOptions $ options )
114144 {
115145 $ this ->goAheadOrWait ();
116146
117- $ this ->connectionMutex = new Mutex (false );
118-
119147 $ this ->configConnectionName = (string ) $ connectionName ;
120148 $ this ->workerOptions = $ options ;
121149
122150 if ($ this ->supportsAsyncSignals ()) {
123151 $ this ->listenForSignals ();
124152 }
125153
154+ if ($ this ->asyncMode ) {
155+ $ this ->logInfo ('daemon.AsyncMode.On ' );
156+
157+ $ coroutineContextHandler = function () use ($ connectionName , $ options ) {
158+ $ this ->logInfo ('daemon.AsyncMode.Coroutines.Running ' );
159+
160+ // we can't move it outside since Mutex should be created within coroutine context
161+ $ this ->connectionMutex = new Mutex (true );
162+ $ this ->startHeartbeatCheck ();
163+ \go (function () use ($ connectionName , $ options ) {
164+ $ this ->vhostDaemon ($ connectionName , $ options );
165+ });
166+ };
167+
168+ if (extension_loaded ('swoole ' )) {
169+ $ this ->logInfo ('daemon.AsyncMode.Swoole ' );
170+
171+ \Co \run ($ coroutineContextHandler );
172+ } elseif (extension_loaded ('openswoole ' )) {
173+ $ this ->logInfo ('daemon.AsyncMode.OpenSwoole ' );
174+
175+ \OpenSwoole \Runtime::enableCoroutine (true , \OpenSwoole \Runtime::HOOK_ALL );
176+ \co::run ($ coroutineContextHandler );
177+ } else {
178+ throw new \Exception ('Async mode is not supported. Check if Swoole extension is installed ' );
179+ }
180+
181+ return ;
182+ }
183+
184+ $ this ->logInfo ('daemon.AsyncMode.Off ' );
185+
186+ $ this ->connectionMutex = new Mutex (false );
187+ $ this ->startHeartbeatCheck ();
126188 $ this ->vhostDaemon ($ connectionName , $ options );
127189 }
128190
@@ -623,6 +685,64 @@ protected function initConnection(): RabbitMQQueue
623685 return $ connection ;
624686 }
625687
688+ /**
689+ * @return void
690+ */
691+ protected function startHeartbeatCheck (): void
692+ {
693+ if (false === $ this ->asyncMode ) {
694+ return ;
695+ }
696+
697+ $ heartbeatInterval = (int ) ($ this ->config ['options ' ]['heartbeat ' ] ?? 0 );
698+ if (!$ heartbeatInterval ) {
699+ return ;
700+ }
701+
702+ $ heartbeatHandler = function () {
703+ if ($ this ->shouldQuit || (null !== $ this ->stopStatusCode )) {
704+ return ;
705+ }
706+
707+ try {
708+ /** @var AMQPStreamConnection $connection */
709+ $ connection = $ this ->connection ?->getConnection();
710+ if ((null === $ connection )
711+ || (false === $ connection ->isConnected ())
712+ || $ connection ->isWriting ()
713+ || $ connection ->isBlocked ()
714+ ) {
715+ return ;
716+ }
717+
718+ $ this ->connectionMutex ->lock (static ::HEALTHCHECK_HANDLER_LOCK , 3 );
719+ $ connection ->checkHeartBeat ();
720+ } catch (MutexTimeout ) {
721+ } catch (Throwable $ exception ) {
722+ $ this ->logError ('startHeartbeatCheck.exception ' , [
723+ 'eroor ' => $ exception ->getMessage (),
724+ 'trace ' => $ e ->getTraceAsString (),
725+ ]);
726+
727+ $ this ->shouldQuit = true ;
728+ } finally {
729+ $ this ->connectionMutex ->unlock (static ::HEALTHCHECK_HANDLER_LOCK );
730+ }
731+ };
732+
733+ \go (function () use ($ heartbeatHandler , $ heartbeatInterval ) {
734+ $ this ->logInfo ('startHeartbeatCheck.started ' );
735+
736+ while (true ) {
737+ sleep ($ heartbeatInterval );
738+ $ heartbeatHandler ();
739+ if ($ this ->shouldQuit || !is_null ($ this ->stopStatusCode )) {
740+ return ;
741+ }
742+ }
743+ });
744+ }
745+
626746 /**
627747 * @return string
628748 */
0 commit comments