diff --git a/.gitignore b/.gitignore index 987e2a25..81b92580 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ composer.lock +phpunit.xml vendor diff --git a/src/ExtEvLoop.php b/src/ExtEvLoop.php new file mode 100644 index 00000000..95f41880 --- /dev/null +++ b/src/ExtEvLoop.php @@ -0,0 +1,244 @@ +loop = new \EvLoop(); + $this->timers = new SplObjectStorage(); + $this->nextTickQueue = new NextTickQueue($this); + $this->futureTickQueue = new FutureTickQueue($this); + $this->timers = new SplObjectStorage(); + } + + /** + * {@inheritdoc} + */ + public function addReadStream($stream, callable $listener) + { + $this->addStream($stream, $listener, \Ev::READ); + } + + /** + * {@inheritdoc} + */ + public function addWriteStream($stream, callable $listener) + { + $this->addStream($stream, $listener, \Ev::WRITE); + } + + /** + * {@inheritdoc} + */ + public function removeReadStream($stream) + { + $key = (int) $stream; + if (isset($this->readEvents[$key])) { + $this->readEvents[$key]->stop(); + unset($this->readEvents[$key]); + } + } + + /** + * {@inheritdoc} + */ + public function removeWriteStream($stream) + { + $key = (int) $stream; + if (isset($this->writeEvents[$key])) { + $this->writeEvents[$key]->stop(); + unset($this->writeEvents[$key]); + } + } + + /** + * {@inheritdoc} + */ + public function removeStream($stream) + { + $this->removeReadStream($stream); + $this->removeWriteStream($stream); + } + + /** + * Wraps the listener in a callback which will pass the + * stream to the listener then registers the stream with + * the eventloop. + * + * @param resource $stream PHP Stream resource + * @param callable $listener stream callback + * @param int $flags flag bitmask + */ + private function addStream($stream, callable $listener, $flags) + { + $listener = function ($event) use ($stream, $listener) { + call_user_func($listener, $stream, $this); + }; + + $event = $this->loop->io($stream, $flags, $listener); + + if (($flags & \Ev::READ) === $flags) { + $this->readEvents[(int)$stream] = $event; + } elseif (($flags & \Ev::WRITE) === $flags) { + $this->writeEvents[(int)$stream] = $event; + } + } + + /** + * {@inheritdoc} + */ + public function addTimer($interval, callable $callback) + { + $timer = new Timer($this, $interval, $callback, false); + $this->setupTimer($timer); + + return $timer; + } + + /** + * {@inheritdoc} + */ + public function addPeriodicTimer($interval, callable $callback) + { + $timer = new Timer($this, $interval, $callback, true); + $this->setupTimer($timer); + + return $timer; + } + + /** + * {@inheritdoc} + */ + public function cancelTimer(TimerInterface $timer) + { + if (isset($this->timers[$timer])) { + /* stop EvTimer */ + $this->timers[$timer]->stop(); + + /* defer timer */ + $this->nextTick(function() use ($timer) { + $this->timers->detach($timer); + }); + } + } + + /** + * Add timer object as + * @param TimerInterface $timer [description] + * @return [type] [description] + */ + private function setupTimer(TimerInterface $timer) + { + $callback = function () use ($timer) { + call_user_func($timer->getCallback(), $timer); + + if (!$timer->isPeriodic()) { + $timer->cancel(); + } + }; + + $interval = $timer->getInterval(); + + $libevTimer = $this->loop->timer($interval, $interval, $callback); + + $this->timers->attach($timer, $libevTimer); + + return $timer; + } + + /** + * {@inheritdoc} + */ + public function isTimerActive(TimerInterface $timer) + { + return $this->timers->contains($timer); + } + + + /** + * {@inheritdoc} + */ + public function nextTick(callable $listener) + { + $this->nextTickQueue->add($listener); + } + + /** + * {@inheritdoc} + */ + public function futureTick(callable $listener) + { + $this->futureTickQueue->add($listener); + } + + /** + * {@inheritdoc} + */ + public function tick() + { + $this->nextTickQueue->tick(); + $this->futureTickQueue->tick(); + + $flags = \Ev::RUN_ONCE; + if (!$this->running || !$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) { + $flags |= \Ev::RUN_NOWAIT; + } elseif (!$this->readEvents && !$this->writeEvents && !$this->timers->count()) { + $this->running = false; + return; + } + $this->loop->run($flags); + } + + /** + * {@inheritdoc} + */ + public function run() + { + $this->running = true; + + while($this->running) { + $this->tick(); + } + } + + /** + * {@inheritdoc} + */ + public function stop() + { + $this->running = false; + } + + public function __destruct() + { + // mannually stop all watchers + foreach ($this->timers as $timer) { + $this->timers[$timer]->stop(); + } + + foreach ($this->readEvents as $event) { + $event->stop(); + } + + foreach ($this->writeEvents as $event) { + $event->stop(); + } + } +} diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index 40b860ae..48657f96 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -236,8 +236,8 @@ private function scheduleTimer(TimerInterface $timer) /** * Create a new ext-event Event object, or update the existing one. * - * @param stream $stream - * @param integer $flag Event::READ or Event::WRITE + * @param resource $stream + * @param integer $flag Event::READ or Event::WRITE */ private function subscribeStreamEvent($stream, $flag) { @@ -263,8 +263,8 @@ private function subscribeStreamEvent($stream, $flag) * Update the ext-event Event object for this stream to stop listening to * the given event type, or remove it entirely if it's no longer needed. * - * @param stream $stream - * @param integer $flag Event::READ or Event::WRITE + * @param resource $stream + * @param integer $flag Event::READ or Event::WRITE */ private function unsubscribeStreamEvent($stream, $flag) { diff --git a/src/Factory.php b/src/Factory.php index 207bc13c..9a481e35 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -9,9 +9,9 @@ public static function create() // @codeCoverageIgnoreStart if (function_exists('event_base_new')) { return new LibEventLoop(); - } else if (class_exists('libev\EventLoop', false)) { + } elseif (class_exists('libev\EventLoop', false)) { return new LibEvLoop; - } else if (class_exists('EventBase', false)) { + } elseif (class_exists('EventBase', false)) { return new ExtEventLoop; } diff --git a/src/LibEventLoop.php b/src/LibEventLoop.php index a55d6104..6fbc8269 100644 --- a/src/LibEventLoop.php +++ b/src/LibEventLoop.php @@ -237,8 +237,8 @@ private function scheduleTimer(TimerInterface $timer) /** * Create a new ext-libevent event resource, or update the existing one. * - * @param stream $stream - * @param integer $flag EV_READ or EV_WRITE + * @param resource $stream + * @param integer $flag EV_READ or EV_WRITE */ private function subscribeStreamEvent($stream, $flag) { @@ -267,8 +267,8 @@ private function subscribeStreamEvent($stream, $flag) * Update the ext-libevent event resource for this stream to stop listening to * the given event type, or remove it entirely if it's no longer needed. * - * @param stream $stream - * @param integer $flag EV_READ or EV_WRITE + * @param resource $stream + * @param integer $flag EV_READ or EV_WRITE */ private function unsubscribeStreamEvent($stream, $flag) { diff --git a/src/LoopInterface.php b/src/LoopInterface.php index 24a80a3d..d046526c 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -9,7 +9,7 @@ interface LoopInterface /** * Register a listener to be notified when a stream is ready to read. * - * @param stream $stream The PHP stream resource to check. + * @param resource $stream The PHP stream resource to check. * @param callable $listener Invoked when the stream is ready. */ public function addReadStream($stream, callable $listener); @@ -17,7 +17,7 @@ public function addReadStream($stream, callable $listener); /** * Register a listener to be notified when a stream is ready to write. * - * @param stream $stream The PHP stream resource to check. + * @param resource $stream The PHP stream resource to check. * @param callable $listener Invoked when the stream is ready. */ public function addWriteStream($stream, callable $listener); @@ -25,21 +25,21 @@ public function addWriteStream($stream, callable $listener); /** * Remove the read event listener for the given stream. * - * @param stream $stream The PHP stream resource. + * @param resource $stream The PHP stream resource. */ public function removeReadStream($stream); /** * Remove the write event listener for the given stream. * - * @param stream $stream The PHP stream resource. + * @param resource $stream The PHP stream resource. */ public function removeWriteStream($stream); /** * Remove all listeners for the given stream. * - * @param stream $stream The PHP stream resource. + * @param resource $stream The PHP stream resource. */ public function removeStream($stream); @@ -49,8 +49,8 @@ public function removeStream($stream); * The execution order of timers scheduled to execute at the same time is * not guaranteed. * - * @param numeric $interval The number of seconds to wait before execution. - * @param callable $callback The callback to invoke. + * @param int|float $interval The number of seconds to wait before execution. + * @param callable $callback The callback to invoke. * * @return TimerInterface */ @@ -62,8 +62,8 @@ public function addTimer($interval, callable $callback); * The execution order of timers scheduled to execute at the same time is * not guaranteed. * - * @param numeric $interval The number of seconds to wait before execution. - * @param callable $callback The callback to invoke. + * @param int|float $interval The number of seconds to wait before execution. + * @param callable $callback The callback to invoke. * * @return TimerInterface */ diff --git a/src/Timer/Timer.php b/src/Timer/Timer.php index ac64d2b0..f670ab3c 100644 --- a/src/Timer/Timer.php +++ b/src/Timer/Timer.php @@ -14,6 +14,15 @@ class Timer implements TimerInterface protected $periodic; protected $data; + /** + * Constructor initializes the fields of the Timer + * + * @param LoopInterface $loop The loop with which this timer is associated + * @param float $interval The interval after which this timer will execute, in seconds + * @param callable $callback The callback that will be executed when this timer elapses + * @param bool $periodic Whether the time is periodic + * @param mixed $data Arbitrary data associated with timer + */ public function __construct(LoopInterface $loop, $interval, callable $callback, $periodic = false, $data = null) { if ($interval < self::MIN_INTERVAL) { @@ -27,41 +36,65 @@ public function __construct(LoopInterface $loop, $interval, callable $callback, $this->data = null; } + /** + * {@inheritdoc} + */ public function getLoop() { return $this->loop; } + /** + * {@inheritdoc} + */ public function getInterval() { return $this->interval; } + /** + * {@inheritdoc} + */ public function getCallback() { return $this->callback; } + /** + * {@inheritdoc} + */ public function setData($data) { $this->data = $data; } + /** + * {@inheritdoc} + */ public function getData() { return $this->data; } + /** + * {@inheritdoc} + */ public function isPeriodic() { return $this->periodic; } + /** + * {@inheritdoc} + */ public function isActive() { return $this->loop->isTimerActive($this); } + /** + * {@inheritdoc} + */ public function cancel() { $this->loop->cancelTimer($this); diff --git a/src/Timer/TimerInterface.php b/src/Timer/TimerInterface.php index 5982b314..d066f369 100644 --- a/src/Timer/TimerInterface.php +++ b/src/Timer/TimerInterface.php @@ -2,14 +2,61 @@ namespace React\EventLoop\Timer; +use React\EventLoop\LoopInterface; + interface TimerInterface { + /** + * Get the loop with which this timer is associated + * + * @return LoopInterface + */ public function getLoop(); + + /** + * Get the interval after which this timer will execute, in seconds + * + * @return float + */ public function getInterval(); + + /** + * Get the callback that will be executed when this timer elapses + * + * @return callable + */ public function getCallback(); + + /** + * Set arbitrary data associated with timer + * + * @param mixed $data + */ public function setData($data); + + /** + * Get arbitrary data associated with timer + * + * @return mixed + */ public function getData(); + + /** + * Determine whether the time is periodic + * + * @return bool + */ public function isPeriodic(); + + /** + * Determine whether the time is active + * + * @return bool + */ public function isActive(); + + /** + * Cancel this timer + */ public function cancel(); } diff --git a/tests/ExtEvLoopTest.php b/tests/ExtEvLoopTest.php new file mode 100644 index 00000000..d05dbba3 --- /dev/null +++ b/tests/ExtEvLoopTest.php @@ -0,0 +1,40 @@ +markTestSkipped('ev tests skipped because pecl/ev is not installed.'); + } + + return new ExtEvLoop(); + } + + public function tearDown() + { + if (file_exists($this->file)) { + unlink($this->file); + } + } + + public function createStream() + { + $this->file = tempnam(sys_get_temp_dir(), 'react-'); + + $stream = fopen($this->file, 'r+'); + + return $stream; + } + + public function testEvConstructor() + { + $loop = new ExtEvLoop(); + } +} diff --git a/tests/ExtEventLoopTest.php b/tests/ExtEventLoopTest.php index 93408ad9..80daaf24 100644 --- a/tests/ExtEventLoopTest.php +++ b/tests/ExtEventLoopTest.php @@ -17,7 +17,7 @@ public function createLoop($readStreamCompatible = false) } $cfg = null; - if($readStreamCompatible) { + if ($readStreamCompatible) { $cfg = new \EventConfig(); $cfg->requireFeatures(\EventConfig::FEATURE_FDS); } diff --git a/tests/Timer/AbstractTimerTest.php b/tests/Timer/AbstractTimerTest.php index bb26f52e..57689658 100644 --- a/tests/Timer/AbstractTimerTest.php +++ b/tests/Timer/AbstractTimerTest.php @@ -3,7 +3,6 @@ namespace React\Tests\EventLoop\Timer; use React\Tests\EventLoop\TestCase; -use React\EventLoop\Timer\Timers; abstract class AbstractTimerTest extends TestCase { diff --git a/travis-init.sh b/travis-init.sh index 63deeb85..6aca97b5 100755 --- a/travis-init.sh +++ b/travis-init.sh @@ -21,6 +21,18 @@ if [[ "$TRAVIS_PHP_VERSION" != "hhvm" && popd echo "extension=libevent.so" >> "$(php -r 'echo php_ini_loaded_file();')" + # install 'pecl-ev' PHP extension + git clone http://bitbucket.org/osmanov/pecl-ev.git + # 0.2.12 + pushd pecl-ev + phpize + ./configure + make + # make test + make install + popd + echo "extension=ev.so" >> "$(php -r 'echo php_ini_loaded_file();')" + # install 'libev' PHP extension git clone --recursive https://github.com/m4rw3r/php-libev pushd php-libev