2014-06-11 18:33:15 +02:00
< ? php
2014-09-23 04:38:32 +02:00
namespace Amp ;
2014-06-11 18:33:15 +02:00
2015-07-21 04:40:06 +02:00
/**
* @ codeCoverageIgnore
* @ TODO remove code coverage ignore once we ' re able to install php - uv on travis
*/
2015-07-29 07:15:43 +02:00
class UvReactor implements Reactor {
2015-07-30 05:23:53 +02:00
use Struct ;
2014-06-11 18:33:15 +02:00
private $loop ;
2014-08-06 20:17:23 +02:00
private $watchers ;
2015-07-31 07:21:21 +02:00
private $keepAliveCount = 0 ;
2014-11-11 22:07:15 +01:00
private $streamIdPollMap = [];
2015-08-04 17:06:49 +02:00
private $state = self :: STOPPED ;
2014-06-11 18:33:15 +02:00
private $stopException ;
2014-08-06 05:45:33 +02:00
private $isWindows ;
2014-08-08 22:39:11 +02:00
private $immediates = [];
2014-12-02 00:08:49 +01:00
private $onError ;
2015-04-03 17:56:16 +02:00
private $onCoroutineResolution ;
2014-06-11 18:33:15 +02:00
2015-04-30 19:41:14 +02:00
/* Pre-PHP7 closure GC hack vars */
private $garbage ;
private $gcWatcher ;
private $gcCallback ;
2014-12-01 21:39:57 +01:00
public function __construct () {
2015-07-22 17:38:17 +02:00
// @codeCoverageIgnoreStart
2015-08-04 17:06:49 +02:00
if ( ! \extension_loaded ( " uv " )) {
2015-07-21 04:40:06 +02:00
throw new \RuntimeException (
2015-07-22 17:38:17 +02:00
" The php-uv extension is required to use " . __CLASS__
2015-07-21 04:40:06 +02:00
);
2015-01-06 03:01:07 +01:00
}
2015-07-22 17:38:17 +02:00
// @codeCoverageIgnoreEnd
2015-01-06 03:01:07 +01:00
2015-07-29 07:15:43 +02:00
$this -> loop = \uv_loop_new ();
2014-08-06 05:45:33 +02:00
$this -> isWindows = ( stripos ( PHP_OS , 'win' ) === 0 );
2015-04-30 19:41:14 +02:00
/**
* Prior to PHP7 we can ' t cancel closure watchers inside their own callbacks
* because PHP will fatal . In legacy versions we schedule manual GC workarounds .
*
* @ link https :// bugs . php . net / bug . php ? id = 62452
*/
if ( PHP_MAJOR_VERSION < 7 ) {
$this -> garbage = [];
2015-07-29 07:15:43 +02:00
$this -> gcWatcher = \uv_timer_init ( $this -> loop );
2015-04-30 19:41:14 +02:00
$this -> gcCallback = function () {
$this -> garbage = [];
$this -> isGcScheduled = false ;
};
}
2015-04-03 17:56:16 +02:00
$this -> onCoroutineResolution = function ( $e = null , $r = null ) {
2015-04-30 19:41:14 +02:00
if ( $e ) {
$this -> onCallbackError ( $e );
2014-09-22 22:47:48 +02:00
}
};
2014-06-11 18:33:15 +02:00
}
/**
2015-03-19 16:14:21 +01:00
* { @ inheritDoc }
2014-06-11 18:33:15 +02:00
*/
2014-08-06 05:45:33 +02:00
public function run ( callable $onStart = null ) {
2015-08-04 17:06:49 +02:00
if ( $this -> state !== self :: STOPPED ) {
throw new \LogicException (
" Cannot run() recursively; event reactor already active "
);
2014-06-11 18:33:15 +02:00
}
2014-08-08 20:15:23 +02:00
if ( $onStart ) {
2015-08-04 17:06:49 +02:00
$this -> state = self :: STARTING ;
2015-07-31 07:21:21 +02:00
$onStartWatcherId = $this -> immediately ( $onStart );
$this -> tryImmediate ( $this -> watchers [ $onStartWatcherId ]);
2015-08-04 17:06:49 +02:00
if ( empty ( $this -> keepAliveCount ) && empty ( $this -> stopException )) {
$this -> state = self :: STOPPED ;
2015-07-31 07:21:21 +02:00
}
2015-08-04 17:06:49 +02:00
} else {
$this -> state = self :: RUNNING ;
2014-08-08 20:15:23 +02:00
}
2014-08-08 22:39:11 +02:00
2015-08-04 17:06:49 +02:00
while ( $this -> state > self :: STOPPED ) {
2015-07-31 07:21:21 +02:00
$immediates = $this -> immediates ;
foreach ( $immediates as $watcher ) {
if ( ! $this -> tryImmediate ( $watcher )) {
break ;
}
2014-08-08 22:39:11 +02:00
}
2015-08-04 17:06:49 +02:00
if ( empty ( $this -> keepAliveCount ) || $this -> state <= self :: STOPPED ) {
2014-12-02 00:08:49 +01:00
break ;
}
2015-08-20 03:28:14 +02:00
\uv_run ( $this -> loop , $this -> immediates ? \UV :: RUN_NOWAIT : \UV :: RUN_ONCE );
2014-08-08 22:39:11 +02:00
}
2014-06-11 18:33:15 +02:00
2015-08-10 19:36:05 +02:00
\gc_collect_cycles ();
2015-08-04 17:06:49 +02:00
$this -> state = self :: STOPPED ;
2014-06-11 18:33:15 +02:00
if ( $this -> stopException ) {
$e = $this -> stopException ;
2014-08-06 05:45:33 +02:00
$this -> stopException = null ;
2014-06-11 18:33:15 +02:00
throw $e ;
}
}
2015-07-31 07:21:21 +02:00
private function tryImmediate ( $watcher ) {
try {
2015-08-05 05:11:52 +02:00
unset (
$this -> watchers [ $watcher -> id ],
$this -> immediates [ $watcher -> id ]
);
2015-07-31 07:21:21 +02:00
$this -> keepAliveCount -= $watcher -> keepAlive ;
$out = \call_user_func ( $watcher -> callback , $watcher -> id , $watcher -> cbData );
if ( $out instanceof \Generator ) {
resolve ( $out ) -> when ( $this -> onCoroutineResolution );
2014-08-08 22:39:11 +02:00
}
2015-07-31 07:21:21 +02:00
} catch ( \Throwable $e ) {
// @TODO Remove coverage ignore block once PHP5 support is no longer required
// @codeCoverageIgnoreStart
$this -> onCallbackError ( $e );
// @codeCoverageIgnoreEnd
} catch ( \Exception $e ) {
// @TODO Remove this catch block once PHP5 support is no longer required
$this -> onCallbackError ( $e );
2014-08-08 22:39:11 +02:00
}
2015-08-04 17:06:49 +02:00
return $this -> state ;
2014-08-08 22:39:11 +02:00
}
2014-06-11 18:33:15 +02:00
/**
2015-03-19 16:14:21 +01:00
* { @ inheritDoc }
2014-06-11 18:33:15 +02:00
*/
2015-04-30 19:41:14 +02:00
public function tick ( $noWait = false ) {
2015-08-04 17:06:49 +02:00
if ( $this -> state ) {
throw new \LogicException (
" Cannot tick() recursively; event reactor already active "
);
}
2015-08-10 19:36:05 +02:00
2015-08-04 17:06:49 +02:00
$this -> state = self :: TICKING ;
2015-04-30 19:41:14 +02:00
$noWait = ( bool ) $noWait ;
2015-07-31 07:21:21 +02:00
$immediates = $this -> immediates ;
foreach ( $immediates as $watcher ) {
if ( ! $this -> tryImmediate ( $watcher )) {
break ;
}
}
2015-08-10 19:36:05 +02:00
2015-08-04 17:06:49 +02:00
// Check the conditional again because a manual stop() could've changed the state
2015-08-12 01:57:59 +02:00
if ( $this -> state > 0 ) {
2015-08-20 03:28:14 +02:00
\uv_run ( $this -> loop , $noWait || $this -> immediates ? \UV :: RUN_NOWAIT : \UV :: RUN_ONCE );
2014-08-08 22:39:11 +02:00
}
2015-08-04 17:06:49 +02:00
$this -> state = self :: STOPPED ;
2014-06-11 18:33:15 +02:00
if ( $this -> stopException ) {
$e = $this -> stopException ;
2014-08-06 05:45:33 +02:00
$this -> stopException = null ;
2014-06-11 18:33:15 +02:00
throw $e ;
}
}
/**
2015-03-19 16:14:21 +01:00
* { @ inheritDoc }
2014-06-11 18:33:15 +02:00
*/
public function stop () {
2015-08-04 17:06:49 +02:00
if ( $this -> state !== self :: STOPPED ) {
\uv_stop ( $this -> loop );
$this -> state = self :: STOPPING ;
} else {
throw new \LogicException (
" Cannot stop(); event reactor not currently active "
);
}
2014-06-11 18:33:15 +02:00
}
/**
2015-03-19 16:14:21 +01:00
* { @ inheritDoc }
2014-06-11 18:33:15 +02:00
*/
2015-04-30 19:41:14 +02:00
public function immediately ( callable $callback , array $options = []) {
$watcher = new \StdClass ;
2015-07-30 04:10:24 +02:00
$watcher -> id = $watcherId = \spl_object_hash ( $watcher );
2014-11-26 22:27:30 +01:00
$watcher -> type = Watcher :: IMMEDIATE ;
2014-08-08 22:39:11 +02:00
$watcher -> callback = $callback ;
2015-07-31 07:21:21 +02:00
$watcher -> cbData = isset ( $options [ " cb_data " ]) ? $options [ " cb_data " ] : null ;
2015-04-30 19:41:14 +02:00
$watcher -> isEnabled = isset ( $options [ " enable " ]) ? ( bool ) $options [ " enable " ] : true ;
2015-07-31 07:21:21 +02:00
$watcher -> keepAlive = isset ( $options [ " keep_alive " ]) ? ( bool ) $options [ " keep_alive " ] : true ;
$this -> keepAliveCount += ( $watcher -> isEnabled && $watcher -> keepAlive );
2015-04-03 17:56:16 +02:00
if ( $watcher -> isEnabled ) {
$this -> immediates [ $watcherId ] = $watcher ;
}
$this -> watchers [ $watcherId ] = $watcher ;
2014-08-08 22:39:11 +02:00
return $watcherId ;
2014-06-11 18:33:15 +02:00
}
/**
2015-03-19 16:14:21 +01:00
* { @ inheritDoc }
2014-06-11 18:33:15 +02:00
*/
2015-04-30 19:41:14 +02:00
public function once ( callable $callback , $msDelay , array $options = []) {
2015-04-03 17:56:16 +02:00
assert (( $msDelay >= 0 ), " \$ msDelay at Argument 2 expects integer >= 0 " );
return $this -> registerTimer ( $callback , $msDelay , $msInterval = - 1 , $options );
2014-06-11 18:33:15 +02:00
}
/**
2015-03-19 16:14:21 +01:00
* { @ inheritDoc }
2014-06-11 18:33:15 +02:00
*/
2015-04-30 19:41:14 +02:00
public function repeat ( callable $callback , $msInterval , array $options = []) {
2015-04-03 17:56:16 +02:00
assert (( $msInterval >= 0 ), " \$ msInterval at Argument 2 expects integer >= 0 " );
2015-04-30 19:41:14 +02:00
$msDelay = isset ( $options [ " ms_delay " ]) ? $options [ " ms_delay " ] : $msInterval ;
assert (( $msDelay >= 0 ), " ms_delay option expects integer >= 0 " );
2015-04-03 17:56:16 +02:00
// libuv interprets a zero interval as "non-repeating." Because we support
// zero-time repeat intervals in our other event reactors we hack in support
// for this by assigning a 1ms interval when zero is passed by the user.
if ( $msInterval === 0 ) {
$msInterval = 1 ;
}
return $this -> registerTimer ( $callback , $msDelay , $msInterval , $options );
2014-06-11 18:33:15 +02:00
}
2015-04-30 19:41:14 +02:00
private function registerTimer ( callable $callback , $msDelay , $msInterval , array $options ) {
2015-04-03 17:56:16 +02:00
$isRepeating = ( $msInterval !== - 1 );
2015-04-30 19:41:14 +02:00
$watcher = new \StdClass ;
2015-07-30 04:10:24 +02:00
$watcher -> id = $watcherId = \spl_object_hash ( $watcher );
2015-05-25 18:47:04 +02:00
$watcher -> type = ( $isRepeating ) ? Watcher :: TIMER_REPEAT : Watcher :: TIMER_ONCE ;
2015-07-31 07:21:21 +02:00
$watcher -> uvHandle = \uv_timer_init ( $this -> loop );
2016-05-11 02:16:15 +02:00
$watcher -> callback = $this -> wrapTimerCallback ( $watcher , $callback , isset ( $options [ " cb_data " ]) ? $options [ " cb_data " ] : null );
2015-04-30 19:41:14 +02:00
$watcher -> isEnabled = isset ( $options [ " enable " ]) ? ( bool ) $options [ " enable " ] : true ;
2015-07-31 07:21:21 +02:00
$watcher -> keepAlive = isset ( $options [ " keep_alive " ]) ? ( bool ) $options [ " keep_alive " ] : true ;
$this -> keepAliveCount += ( $watcher -> isEnabled && $watcher -> keepAlive );
if ( empty ( $watcher -> keepAlive )) {
\uv_unref ( $watcher -> uvHandle );
}
2015-04-03 17:56:16 +02:00
$watcher -> msDelay = $msDelay ;
$watcher -> msInterval = $isRepeating ? $msInterval : 0 ;
2014-06-11 18:33:15 +02:00
2015-04-03 17:56:16 +02:00
$this -> watchers [ $watcherId ] = $watcher ;
2014-06-11 18:33:15 +02:00
2015-04-03 17:56:16 +02:00
if ( $watcher -> isEnabled ) {
2015-07-29 07:15:43 +02:00
\uv_timer_start ( $watcher -> uvHandle , $watcher -> msDelay , $watcher -> msInterval , $watcher -> callback );
2015-04-03 17:56:16 +02:00
}
2014-06-11 18:33:15 +02:00
2015-04-30 19:41:14 +02:00
return $watcherId ;
2014-06-11 18:33:15 +02:00
}
2016-05-11 02:16:15 +02:00
private function wrapTimerCallback ( $watcher , $callback , $cbData ) {
$watcherId = $watcher -> id ;
$once = $watcher -> type === Watcher :: TIMER_ONCE ;
return function () use ( $once , $watcherId , $callback , $cbData ) {
2014-06-11 18:33:15 +02:00
try {
2016-05-11 02:16:15 +02:00
$result = \call_user_func ( $callback , $watcherId , $cbData );
2014-09-22 22:47:48 +02:00
if ( $result instanceof \Generator ) {
2015-07-29 07:15:43 +02:00
resolve ( $result ) -> when ( $this -> onCoroutineResolution );
2014-09-22 22:47:48 +02:00
}
2015-01-19 02:13:08 +01:00
// The isset() check is necessary because the "once" timer
// callback may have cancelled itself when it was invoked.
2016-05-11 02:16:15 +02:00
if ( $once && isset ( $this -> watchers [ $watcherId ])) {
2015-04-03 17:56:16 +02:00
$this -> clearWatcher ( $watcherId );
2014-06-11 18:33:15 +02:00
}
2015-07-22 17:38:17 +02:00
} catch ( \Throwable $e ) {
// @TODO Remove coverage ignore block once PHP5 support is no longer required
// @codeCoverageIgnoreStart
$this -> onCallbackError ( $e );
// @codeCoverageIgnoreEnd
2014-06-11 18:33:15 +02:00
} catch ( \Exception $e ) {
2015-07-22 17:38:17 +02:00
// @TODO Remove this catch block once PHP5 support is no longer required
2015-04-30 19:41:14 +02:00
$this -> onCallbackError ( $e );
2014-12-02 00:08:49 +01:00
}
};
}
2015-07-22 17:38:17 +02:00
/**
*@ TODO Add a \Throwable typehint once PHP5 is no longer required
*/
private function onCallbackError ( $e ) {
2015-04-30 19:41:14 +02:00
if ( empty ( $this -> onError )) {
$this -> stopException = $e ;
$this -> stop ();
} else {
$this -> tryUserErrorCallback ( $e );
}
}
2015-07-22 17:38:17 +02:00
/**
*@ TODO Add a \Throwable typehint once PHP5 is no longer required
*/
private function tryUserErrorCallback ( $e ) {
2014-12-02 00:08:49 +01:00
try {
2015-07-22 17:38:17 +02:00
\call_user_func ( $this -> onError , $e );
} catch ( \Throwable $e ) {
// @TODO Remove coverage ignore block once PHP5 support is no longer required
// @codeCoverageIgnoreStart
$this -> stopException = $e ;
$this -> stop ();
// @codeCoverageIgnoreEnd
2014-12-02 00:08:49 +01:00
} catch ( \Exception $e ) {
2015-07-22 17:38:17 +02:00
// @TODO Remove this catch block once PHP5 support is no longer required
2014-12-02 00:08:49 +01:00
$this -> stopException = $e ;
$this -> stop ();
}
2014-06-11 18:33:15 +02:00
}
/**
2015-03-19 16:14:21 +01:00
* { @ inheritDoc }
2014-06-11 18:33:15 +02:00
*/
2015-04-30 19:41:14 +02:00
public function onReadable ( $stream , callable $callback , array $options = []) {
2015-04-03 17:56:16 +02:00
return $this -> watchStream ( $stream , $callback , Watcher :: IO_READER , $options );
2014-06-11 18:33:15 +02:00
}
/**
2015-03-19 16:14:21 +01:00
* { @ inheritDoc }
2014-06-11 18:33:15 +02:00
*/
2015-04-30 19:41:14 +02:00
public function onWritable ( $stream , callable $callback , array $options = []) {
2015-04-03 17:56:16 +02:00
return $this -> watchStream ( $stream , $callback , Watcher :: IO_WRITER , $options );
2014-06-11 18:33:15 +02:00
}
2015-04-30 19:41:14 +02:00
private function watchStream ( $stream , callable $callback , $type , array $options ) {
$watcher = new \StdClass ;
2015-07-30 04:10:24 +02:00
$watcher -> id = $watcherId = \spl_object_hash ( $watcher );
2014-11-26 22:27:30 +01:00
$watcher -> type = $type ;
2014-11-11 22:07:15 +01:00
$watcher -> callback = $callback ;
2016-05-11 02:16:15 +02:00
$watcher -> cbData = isset ( $options [ " cb_data " ]) ? $options [ " cb_data " ] : null ;
2015-04-30 19:41:14 +02:00
$watcher -> isEnabled = isset ( $options [ " enable " ]) ? ( bool ) $options [ " enable " ] : true ;
2015-07-31 07:21:21 +02:00
$watcher -> keepAlive = isset ( $options [ " keep_alive " ]) ? ( bool ) $options [ " keep_alive " ] : true ;
$this -> keepAliveCount += ( $watcher -> isEnabled && $watcher -> keepAlive );
2015-04-03 17:56:16 +02:00
$watcher -> stream = $stream ;
$watcher -> streamId = $streamId = ( int ) $stream ;
2015-07-31 07:21:21 +02:00
if ( empty ( $this -> streamIdPollMap [ $streamId ])) {
$watcher -> poll = $poll = $this -> makePollHandle ( $stream );
if ( empty ( $watcher -> keepAlive )) {
\uv_unref ( $poll -> handle );
2016-05-12 07:51:00 +02:00
} else {
$poll -> keepAlives ++ ;
2015-07-31 07:21:21 +02:00
}
} else {
$watcher -> poll = $poll = $this -> streamIdPollMap [ $streamId ];
2016-05-12 07:51:00 +02:00
if ( $watcher -> keepAlive && $poll -> keepAlives ++ == 0 ) {
2015-07-31 07:21:21 +02:00
\uv_ref ( $poll -> handle );
}
}
2014-11-11 22:07:15 +01:00
2015-04-30 19:41:14 +02:00
$this -> watchers [ $watcherId ] = $watcher ;
2015-04-03 17:56:16 +02:00
if ( ! $watcher -> isEnabled ) {
2014-11-11 22:07:15 +01:00
$poll -> disable [ $watcherId ] = $watcher ;
2015-04-03 17:56:16 +02:00
// If the poll is disabled we don't need to do anything else
2014-11-11 22:07:15 +01:00
return $watcherId ;
}
2014-06-11 18:33:15 +02:00
2014-11-26 22:27:30 +01:00
if ( $type === Watcher :: IO_READER ) {
2014-11-11 22:07:15 +01:00
$poll -> readers [ $watcherId ] = $watcher ;
2014-06-11 18:33:15 +02:00
} else {
2014-11-11 22:07:15 +01:00
$poll -> writers [ $watcherId ] = $watcher ;
2014-06-11 18:33:15 +02:00
}
2014-11-12 19:17:08 +01:00
$newFlags = 0 ;
2014-11-11 22:07:15 +01:00
if ( $poll -> readers ) {
2014-11-12 19:17:08 +01:00
$newFlags |= \UV :: READABLE ;
2014-11-11 22:07:15 +01:00
}
if ( $poll -> writers ) {
2014-11-12 19:17:08 +01:00
$newFlags |= \UV :: WRITABLE ;
2014-11-11 22:07:15 +01:00
}
2014-11-12 19:17:08 +01:00
if ( $newFlags != $poll -> flags ) {
$poll -> flags = $newFlags ;
2015-07-29 07:15:43 +02:00
\uv_poll_start ( $poll -> handle , $newFlags , $poll -> callback );
2014-11-11 22:07:15 +01:00
}
2014-06-11 18:33:15 +02:00
2014-11-11 22:07:15 +01:00
return $watcherId ;
}
2014-06-11 18:33:15 +02:00
2014-11-11 22:07:15 +01:00
private function makePollHandle ( $stream ) {
// Windows needs the socket-specific init function, so make sure we use
// it when dealing with tcp/ssl streams.
$pollInitFunc = $this -> isWindows
? $this -> chooseWindowsPollingFunction ( $stream )
: 'uv_poll_init' ;
2014-06-11 18:33:15 +02:00
2014-11-11 22:07:15 +01:00
$streamId = ( int ) $stream ;
2015-07-10 21:36:13 +02:00
2015-05-25 18:47:04 +02:00
$poll = new \StdClass ;
2016-05-11 02:16:15 +02:00
$readers = $writers = [];
$poll -> readers = & $readers ;
$poll -> writers = & $writers ;
2015-05-25 18:47:04 +02:00
$poll -> disable = [];
$poll -> flags = 0 ;
2016-05-12 07:51:00 +02:00
$poll -> keepAlives = 0 ;
2015-05-25 18:47:04 +02:00
$poll -> handle = \call_user_func ( $pollInitFunc , $this -> loop , $stream );
2016-05-11 02:16:15 +02:00
$poll -> callback = function ( $uvHandle , $stat , $events ) use ( & $readers , & $writers ) {
2015-05-25 18:47:04 +02:00
if ( $events & \UV :: READABLE ) {
2016-05-11 02:16:15 +02:00
foreach ( $readers as $watcher ) {
2015-05-25 18:47:04 +02:00
$this -> invokePollWatcher ( $watcher );
2014-11-11 22:07:15 +01:00
}
2015-05-25 18:47:04 +02:00
}
if ( $events & \UV :: WRITABLE ) {
2016-05-11 02:16:15 +02:00
foreach ( $writers as $watcher ) {
2015-05-25 18:47:04 +02:00
$this -> invokePollWatcher ( $watcher );
2014-11-11 22:07:15 +01:00
}
}
2015-05-25 18:47:04 +02:00
};
return $this -> streamIdPollMap [ $streamId ] = $poll ;
2014-06-11 18:33:15 +02:00
}
2015-04-30 19:41:14 +02:00
private function chooseWindowsPollingFunction ( $stream ) {
2014-11-11 17:08:46 +01:00
$streamType = stream_get_meta_data ( $stream )[ 'stream_type' ];
return ( $streamType === 'tcp_socket/ssl' || $streamType === 'tcp_socket' )
2015-07-29 07:15:43 +02:00
? '\uv_poll_init_socket'
: '\uv_poll_init' ;
2014-08-06 05:45:33 +02:00
}
2015-04-03 17:56:16 +02:00
private function invokePollWatcher ( $watcher ) {
2014-11-11 22:07:15 +01:00
try {
2015-07-31 07:21:21 +02:00
$result = \call_user_func ( $watcher -> callback , $watcher -> id , $watcher -> stream , $watcher -> cbData );
2014-11-11 22:07:15 +01:00
if ( $result instanceof \Generator ) {
2015-07-29 07:15:43 +02:00
resolve ( $result ) -> when ( $this -> onCoroutineResolution );
2014-06-11 18:33:15 +02:00
}
2015-07-22 17:38:17 +02:00
} catch ( \Throwable $e ) {
// @TODO Remove coverage ignore block once PHP5 support is no longer required
// @codeCoverageIgnoreStart
$this -> onCallbackError ( $e );
// @codeCoverageIgnoreEnd
2014-11-11 22:07:15 +01:00
} catch ( \Exception $e ) {
2015-07-22 17:38:17 +02:00
// @TODO Remove this catch block once PHP5 support is no longer required
2015-04-30 19:41:14 +02:00
$this -> onCallbackError ( $e );
2014-11-11 22:07:15 +01:00
}
2014-06-11 18:33:15 +02:00
}
2014-08-06 06:17:03 +02:00
/**
2015-03-19 16:14:21 +01:00
* { @ inheritDoc }
2014-08-06 06:17:03 +02:00
*/
2015-04-30 19:41:14 +02:00
public function onSignal ( $signo , callable $func , array $options = []) {
$watcher = new \StdClass ;
2015-07-30 04:10:24 +02:00
$watcher -> id = $watcherId = \spl_object_hash ( $watcher );
2014-11-26 22:27:30 +01:00
$watcher -> type = Watcher :: SIGNAL ;
2016-05-11 02:16:15 +02:00
$watcher -> signo = $signo ;
$watcher -> callback = $this -> wrapSignalCallback ( $watcher , $func , isset ( $options [ " cb_data " ]) ? $options [ " cb_data " ] : null );
2015-04-30 19:41:14 +02:00
$watcher -> isEnabled = isset ( $options [ " enable " ]) ? ( bool ) $options [ " enable " ] : true ;
2015-07-31 07:21:21 +02:00
$watcher -> keepAlive = isset ( $options [ " keep_alive " ]) ? ( bool ) $options [ " keep_alive " ] : true ;
$this -> keepAliveCount += ( $watcher -> isEnabled && $watcher -> keepAlive );
$watcher -> uvHandle = \uv_signal_init ( $this -> loop );
if ( empty ( $watcher -> keepAlive )) {
\uv_unref ( $watcher -> uvHandle );
}
2015-04-03 17:56:16 +02:00
if ( $watcher -> isEnabled ) {
2015-07-29 07:15:43 +02:00
\uv_signal_start ( $watcher -> uvHandle , $watcher -> callback , $watcher -> signo );
2015-04-03 17:56:16 +02:00
}
$this -> watchers [ $watcherId ] = $watcher ;
2014-08-06 06:17:03 +02:00
2015-04-03 17:56:16 +02:00
return $watcherId ;
2014-08-06 06:17:03 +02:00
}
2016-05-11 02:16:15 +02:00
private function wrapSignalCallback ( $watcher , $callback , $cbData ) {
$watcherId = $watcher -> id ;
$signo = $watcher -> signo ;
return function () use ( $watcherId , $signo , $callback , $cbData ) {
2014-08-06 06:17:03 +02:00
try {
2016-05-11 02:16:15 +02:00
$result = \call_user_func ( $callback , $watcherId , $signo , $cbData );
2014-09-22 22:47:48 +02:00
if ( $result instanceof \Generator ) {
2015-07-29 07:15:43 +02:00
resolve ( $result ) -> when ( $this -> onCoroutineResolution );
2014-09-22 22:47:48 +02:00
}
2015-07-22 17:38:17 +02:00
} catch ( \Throwable $e ) {
// @TODO Remove coverage ignore block once PHP5 support is no longer required
// @codeCoverageIgnoreStart
$this -> onCallbackError ( $e );
// @codeCoverageIgnoreEnd
2014-08-06 06:17:03 +02:00
} catch ( \Exception $e ) {
2015-07-22 17:38:17 +02:00
// @TODO Remove this catch block once PHP5 support is no longer required
2015-04-30 19:41:14 +02:00
$this -> onCallbackError ( $e );
2014-08-06 06:17:03 +02:00
}
};
}
2014-06-11 18:33:15 +02:00
/**
2015-03-19 16:14:21 +01:00
* { @ inheritDoc }
2014-06-11 18:33:15 +02:00
*/
2015-04-30 19:41:14 +02:00
public function cancel ( $watcherId ) {
2014-06-11 18:33:15 +02:00
if ( isset ( $this -> watchers [ $watcherId ])) {
$this -> clearWatcher ( $watcherId );
}
}
2015-04-30 19:41:14 +02:00
private function clearWatcher ( $watcherId ) {
2014-06-11 18:33:15 +02:00
$watcher = $this -> watchers [ $watcherId ];
unset ( $this -> watchers [ $watcherId ]);
if ( $watcher -> isEnabled ) {
2015-07-31 07:21:21 +02:00
$this -> keepAliveCount -= $watcher -> keepAlive ;
2014-11-26 22:27:30 +01:00
switch ( $watcher -> type ) {
case Watcher :: IO_READER :
2014-11-11 22:07:15 +01:00
// fallthrough
2014-11-26 22:27:30 +01:00
case Watcher :: IO_WRITER :
2015-07-31 07:21:21 +02:00
$this -> clearPollFromWatcher ( $watcher , $unref = $watcher -> keepAlive );
2014-08-06 06:17:03 +02:00
break ;
2014-11-26 22:27:30 +01:00
case Watcher :: SIGNAL :
2016-05-12 07:51:00 +02:00
\uv_signal_stop ( $watcher -> uvHandle );
2014-08-06 06:17:03 +02:00
break ;
2014-11-26 22:27:30 +01:00
case Watcher :: IMMEDIATE :
2014-08-08 22:39:11 +02:00
unset ( $this -> immediates [ $watcherId ]);
break ;
2015-02-03 06:21:07 +01:00
case Watcher :: TIMER_ONCE :
2015-05-31 18:06:25 +02:00
case Watcher :: TIMER_REPEAT :
2015-07-29 07:15:43 +02:00
@ \uv_timer_stop ( $watcher -> uvHandle );
2014-08-06 06:17:03 +02:00
break ;
}
2015-06-10 20:10:33 +02:00
} elseif ( $watcher -> type == Watcher :: IO_READER || $watcher -> type == Watcher :: IO_WRITER ) {
2015-07-31 07:21:21 +02:00
$this -> clearPollFromWatcher ( $watcher , $unref = false );
2014-06-11 18:33:15 +02:00
}
2015-04-30 19:41:14 +02:00
if ( PHP_MAJOR_VERSION < 7 ) {
$this -> garbage [] = $watcher ;
if ( ! $this -> isGcScheduled ) {
2015-07-29 07:15:43 +02:00
\uv_timer_start ( $this -> gcWatcher , 250 , 0 , $this -> gcCallback );
2015-04-30 19:41:14 +02:00
$this -> isGcScheduled = true ;
}
}
2014-06-11 18:33:15 +02:00
}
2015-07-31 07:21:21 +02:00
private function clearPollFromWatcher ( $watcher , $unref ) {
2014-11-11 22:07:15 +01:00
$poll = $watcher -> poll ;
$watcherId = $watcher -> id ;
unset (
$poll -> readers [ $watcherId ],
$poll -> writers [ $watcherId ],
$poll -> disable [ $watcherId ]
);
// If any watchers are still enabled for this stream we're finished here
$hasEnabledWatchers = (( int ) $poll -> readers ) + (( int ) $poll -> writers );
if ( $hasEnabledWatchers ) {
return ;
}
2016-05-12 07:51:00 +02:00
if ( $unref && -- $poll -> keepAlives == 0 ) {
2015-07-31 07:21:21 +02:00
\uv_unref ( $poll -> handle );
}
2014-11-11 22:07:15 +01:00
// Always stop polling if no enabled watchers remain
2015-07-29 07:15:43 +02:00
\uv_poll_stop ( $poll -> handle );
2014-11-11 22:07:15 +01:00
// If all watchers are disabled we can pull out here
2015-07-31 07:21:21 +02:00
if ( $poll -> disable ) {
2014-11-11 22:07:15 +01:00
return ;
}
// Otherwise there are no watchers left for this poll and we should clear it
$streamId = ( int ) $watcher -> stream ;
unset ( $this -> streamIdPollMap [ $streamId ]);
2016-05-12 07:51:00 +02:00
// Force explicit handle close as libuv does not like two handles simultaneously existing for a same file descriptor (it is referenced until handle close callback end)
\uv_close ( $poll -> handle );
2014-11-11 22:07:15 +01:00
}
2014-06-11 18:33:15 +02:00
/**
2015-03-19 16:14:21 +01:00
* { @ inheritDoc }
2014-06-11 18:33:15 +02:00
*/
2015-04-30 19:41:14 +02:00
public function disable ( $watcherId ) {
2014-06-11 18:33:15 +02:00
if ( ! isset ( $this -> watchers [ $watcherId ])) {
return ;
}
$watcher = $this -> watchers [ $watcherId ];
2014-08-06 06:17:03 +02:00
if ( ! $watcher -> isEnabled ) {
return ;
}
2015-07-31 07:21:21 +02:00
$watcher -> isEnabled = false ;
$this -> keepAliveCount -= $watcher -> keepAlive ;
2014-11-26 22:27:30 +01:00
switch ( $watcher -> type ) {
case Watcher :: IO_READER :
2014-11-11 22:07:15 +01:00
// fallthrough
2014-11-26 22:27:30 +01:00
case Watcher :: IO_WRITER :
2014-11-11 22:07:15 +01:00
$this -> disablePollFromWatcher ( $watcher );
2014-08-06 06:17:03 +02:00
break ;
2014-11-26 22:27:30 +01:00
case Watcher :: SIGNAL :
2015-07-29 07:15:43 +02:00
\uv_signal_stop ( $watcher -> uvHandle );
2014-08-06 06:17:03 +02:00
break ;
2014-11-26 22:27:30 +01:00
case Watcher :: IMMEDIATE :
2015-04-03 17:56:16 +02:00
unset ( $this -> immediates [ $watcherId ]);
2014-08-08 22:39:11 +02:00
break ;
2015-04-03 17:56:16 +02:00
case Watcher :: TIMER_ONCE :
// fallthrough
case Watcher :: TIMER_REPEAT :
2015-07-29 07:15:43 +02:00
\uv_timer_stop ( $watcher -> uvHandle );
2014-08-06 06:17:03 +02:00
break ;
2015-04-03 17:56:16 +02:00
default :
2015-04-30 19:41:14 +02:00
throw new \RuntimeException ( " Unexpected Watcher type encountered " );
2014-06-11 18:33:15 +02:00
}
}
2015-04-03 17:56:16 +02:00
private function disablePollFromWatcher ( $watcher ) {
2014-11-11 22:07:15 +01:00
$poll = $watcher -> poll ;
$watcherId = $watcher -> id ;
2014-11-12 19:17:08 +01:00
unset (
$poll -> readers [ $watcherId ],
$poll -> writers [ $watcherId ]
);
2014-11-11 22:07:15 +01:00
$poll -> disable [ $watcherId ] = $watcher ;
2016-05-12 07:51:00 +02:00
if ( $watcher -> keepAlive && -- $poll -> keepAlives == 0 ) {
2015-07-31 07:21:21 +02:00
\uv_unref ( $poll -> handle );
}
2014-11-12 19:17:08 +01:00
if ( ! ( $poll -> readers || $poll -> writers )) {
2015-07-29 07:15:43 +02:00
\uv_poll_stop ( $poll -> handle );
2014-11-12 19:17:08 +01:00
return ;
}
// If we're still here we may need to update the polling flags
$newFlags = 0 ;
if ( $poll -> readers ) {
$newFlags |= \UV :: READABLE ;
}
if ( $poll -> writers ) {
$newFlags |= \UV :: WRITABLE ;
}
if ( $poll -> flags != $newFlags ) {
$poll -> flags = $newFlags ;
2015-07-29 07:15:43 +02:00
\uv_poll_start ( $poll -> handle , $newFlags , $poll -> callback );
2014-11-11 22:07:15 +01:00
}
}
2014-06-11 18:33:15 +02:00
/**
2015-03-19 16:14:21 +01:00
* { @ inheritDoc }
2014-06-11 18:33:15 +02:00
*/
2015-04-30 19:41:14 +02:00
public function enable ( $watcherId ) {
2014-06-11 18:33:15 +02:00
if ( ! isset ( $this -> watchers [ $watcherId ])) {
return ;
}
$watcher = $this -> watchers [ $watcherId ];
if ( $watcher -> isEnabled ) {
return ;
}
2015-07-31 07:21:21 +02:00
$watcher -> isEnabled = true ;
$this -> keepAliveCount += $watcher -> keepAlive ;
2014-11-26 22:27:30 +01:00
switch ( $watcher -> type ) {
2015-04-03 17:56:16 +02:00
case Watcher :: TIMER_ONCE : // fallthrough
case Watcher :: TIMER_REPEAT :
2015-07-29 07:15:43 +02:00
\uv_timer_start ( $watcher -> uvHandle , $watcher -> msDelay , $watcher -> msInterval , $watcher -> callback );
2015-04-03 17:56:16 +02:00
break ;
case Watcher :: IO_READER : // fallthrough
2014-11-26 22:27:30 +01:00
case Watcher :: IO_WRITER :
2014-11-11 22:07:15 +01:00
$this -> enablePollFromWatcher ( $watcher );
2014-08-06 06:17:03 +02:00
break ;
2014-11-26 22:27:30 +01:00
case Watcher :: SIGNAL :
2015-07-29 07:15:43 +02:00
\uv_signal_start ( $watcher -> uvHandle , $watcher -> callback , $watcher -> signo );
2014-08-06 06:17:03 +02:00
break ;
2014-11-26 22:27:30 +01:00
case Watcher :: IMMEDIATE :
2015-04-03 17:56:16 +02:00
$this -> immediates [ $watcherId ] = $watcher ;
2014-08-08 22:39:11 +02:00
break ;
2014-08-06 06:17:03 +02:00
default :
2015-04-30 19:41:14 +02:00
throw new \RuntimeException ( " Unexpected Watcher type encountered " );
2014-06-11 18:33:15 +02:00
}
}
2014-08-19 20:20:04 +02:00
2015-04-03 17:56:16 +02:00
private function enablePollFromWatcher ( $watcher ) {
2014-11-11 22:07:15 +01:00
$poll = $watcher -> poll ;
$watcherId = $watcher -> id ;
unset ( $poll -> disable [ $watcherId ]);
2014-11-26 22:27:30 +01:00
if ( $watcher -> type === Watcher :: IO_READER ) {
2014-11-11 22:07:15 +01:00
$poll -> flags |= \UV :: READABLE ;
$poll -> readers [ $watcherId ] = $watcher ;
} else {
$poll -> flags |= \UV :: WRITABLE ;
$poll -> writers [ $watcherId ] = $watcher ;
}
2016-05-12 07:51:00 +02:00
if ( $watcher -> keepAlive && $poll -> keepAlives ++ == 0 ) {
2015-07-31 07:21:21 +02:00
\uv_ref ( $poll -> handle );
}
2015-07-29 07:15:43 +02:00
@ \uv_poll_start ( $poll -> handle , $poll -> flags , $poll -> callback );
2014-08-19 20:20:04 +02:00
}
2014-12-02 00:08:49 +01:00
2015-07-10 21:36:13 +02:00
/**
* Manually increment the watcher refcount
*
* This method , like it ' s delRef () counterpart , is * only * necessary when manually
* operating directly on the underlying uv loop to avoid the reactor ' s run loop
* exiting because no enabled watchers exist when waiting on manually registered
* uv_ * () callbacks .
*
* @ return void
*/
public function addRef () {
2015-07-31 07:21:21 +02:00
$this -> keepAliveCount ++ ;
2015-07-10 21:36:13 +02:00
}
/**
* Manually decrement the watcher refcount
*
* @ return void
*/
public function delRef () {
2015-07-31 07:21:21 +02:00
$this -> keepAliveCount -- ;
2015-07-10 21:36:13 +02:00
}
2014-12-02 00:08:49 +01:00
/**
2015-03-19 16:14:21 +01:00
* { @ inheritDoc }
2014-12-02 00:08:49 +01:00
*/
2015-07-20 21:42:56 +02:00
public function onError ( callable $callback ) {
$this -> onError = $callback ;
2014-12-02 00:08:49 +01:00
}
2014-12-02 07:06:36 +01:00
2015-07-29 07:15:43 +02:00
/**
* { @ inheritDoc }
*/
public function info () {
$once = $repeat = $immediately = $onReadable = $onWritable = $onSignal = [
" enabled " => 0 ,
" disabled " => 0 ,
];
2014-11-26 22:27:30 +01:00
foreach ( $this -> watchers as $watcher ) {
switch ( $watcher -> type ) {
2015-07-29 07:15:43 +02:00
case Watcher :: IMMEDIATE : $arr =& $immediately ; break ;
case Watcher :: TIMER_ONCE : $arr =& $once ; break ;
case Watcher :: TIMER_REPEAT : $arr =& $repeat ; break ;
case Watcher :: IO_READER : $arr =& $onReadable ; break ;
case Watcher :: IO_WRITER : $arr =& $onWritable ; break ;
case Watcher :: SIGNAL : $arr =& $onSignal ; break ;
2014-11-26 22:27:30 +01:00
}
2015-07-29 07:15:43 +02:00
if ( $watcher -> isEnabled ) {
$arr [ " enabled " ] += 1 ;
} else {
$arr [ " disabled " ] += 1 ;
}
2014-11-26 22:27:30 +01:00
}
return [
2015-07-29 07:15:43 +02:00
" immediately " => $immediately ,
" once " => $once ,
" repeat " => $repeat ,
" on_readable " => $onReadable ,
" on_writable " => $onWritable ,
" on_signal " => $onSignal ,
2015-07-31 07:21:21 +02:00
" keep_alive " => $this -> keepAliveCount ,
2015-08-05 05:47:20 +02:00
" state " => $this -> state ,
2014-11-26 22:27:30 +01:00
];
}
2015-07-29 07:15:43 +02:00
/**
* Access the underlying php - uv extension loop resource
*
* This method provides access to the underlying php - uv event loop resource for
* code that wishes to interact with lower - level php - uv extension functionality .
*
* @ return resource
*/
public function getLoop () {
return $this -> loop ;
}
public function __debugInfo () {
return $this -> info ();
}
2014-11-26 22:27:30 +01:00
}