diff --git a/.travis.yml b/.travis.yml index 51ab408..d7a9939 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,13 +6,36 @@ php: - 5.5 - 5.6 - 7.0 + - 7.1 - nightly install: - - php -r 'exit((int) (PHP_MAJOR_VERSION < 7));' || (yes "" | pecl install "channel://pecl.php.net/libevent-0.1.0") - - php -r 'exit((int) (PHP_MAJOR_VERSION < 7));' || (yes "" | pecl install ev) - - php -r 'exit((int) (PHP_MAJOR_VERSION >= 7));' || (mkdir libuv && (curl -L https://github.com/libuv/libuv/archive/v1.6.1.tar.gz | tar xzf -) && cd libuv-1.6.1 && ./autogen.sh && ./configure --prefix=$(readlink -f `pwd`/../libuv) && make && make install && cd ..) - - php -r 'exit((int) (PHP_MAJOR_VERSION >= 7));' || (git clone https://github.com/bwoebi/php-uv && cd php-uv && phpize && ./configure --with-uv=$(readlink -f `pwd`/../libuv) && make install && (echo "extension = uv.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini) && cd ..) + - if [ "$TRAVIS_PHP_VERSION" != "5.5" ] && [ "$TRAVIS_PHP_VERSION" != "5.6" ] ; then + git clone https://github.com/libuv/libuv; + pushd libuv; + git checkout $(git describe --tags); + ./autogen.sh; + ./configure --prefix=$(dirname `pwd`)/libuv-install; + make; + make install; + popd; + git clone https://github.com/bwoebi/php-uv.git; + pushd php-uv; + phpize; + ./configure --with-uv=$(dirname `pwd`)/libuv-install; + make; + make install; + popd; + echo "extension=uv.so" >> "$(php -r 'echo php_ini_loaded_file();')"; + fi; + - curl -LS https://pecl.php.net/get/ev | tar -xz; + pushd ev-*; + phpize; + ./configure; + make; + make install; + popd; + echo "extension=ev.so" >> "$(php -r 'echo php_ini_loaded_file();')"; - composer self-update - composer install --no-interaction --prefer-source @@ -22,8 +45,8 @@ script: after_script: - composer require satooshi/php-coveralls dev-master - - php vendor/bin/coveralls -v - + - php vendor/bin/coveralls -v --exclude-no-stmt + cache: directories: - $HOME/.composer/cache diff --git a/composer.json b/composer.json index 0be9916..2c74f94 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ "homepage": "http://amphp.org", "license": "MIT", "require": { - "async-interop/event-loop": "^0.3" + "async-interop/event-loop": "dev-master" }, "require-dev": { "async-interop/event-loop-test": "dev-master", diff --git a/lib/Loop.php b/lib/Loop.php index 372cba3..21a4c1b 100644 --- a/lib/Loop.php +++ b/lib/Loop.php @@ -364,13 +364,15 @@ abstract class Loop extends Driver { * {@inheritdoc} */ public function setErrorHandler(callable $callback = null) { + $previous = $this->errorHandler; $this->errorHandler = $callback; + return $previous; } /** * {@inheritdoc} */ - public function info() { + public function getInfo() { $watchers = [ "referenced" => 0, "unreferenced" => 0, diff --git a/lib/UvLoop.php b/lib/UvLoop.php new file mode 100644 index 0000000..e53a66d --- /dev/null +++ b/lib/UvLoop.php @@ -0,0 +1,279 @@ +handle = \uv_loop_new(); + + $this->ioCallback = function ($event, $status, $events, $resource) { + switch ($status) { + case 0: // OK + break; + + // If $status is a severe error, stop the poll and throw an exception. + case \UV::EACCES: + case \UV::EBADF: + case \UV::EINVAL: + case \UV::ENOTSOCK: + throw new \RuntimeException( + \sprintf("UV_%s: %s", \uv_err_name($status), \ucfirst(\uv_strerror($status))) + ); + + default: // Ignore other (probably) trivial warnings and continuing polling. + return; + } + + $watchers = $this->watchers[(int) $event]; + + foreach ($watchers as $watcher) { + $callback = $watcher->callback; + $callback($watcher->id, $resource, $watcher->data); + } + }; + + $this->timerCallback = function ($event) { + $watcher = $this->watchers[(int) $event]; + + if ($watcher->type & Watcher::DELAY) { + $this->cancel($watcher->id); + } + + $callback = $watcher->callback; + $callback($watcher->id, $watcher->data); + }; + + $this->signalCallback = function ($event, $signo) { + $watcher = $this->watchers[(int) $event]; + + $callback = $watcher->callback; + $callback($watcher->id, $signo, $watcher->data); + }; + } + + /** + * {@inheritdoc} + */ + public function stop() { + \uv_stop($this->handle); + parent::stop(); + } + + /** + * {@inheritdoc} + */ + protected function dispatch($blocking) { + \uv_run($this->handle, $blocking ? \UV::RUN_ONCE : \UV::RUN_NOWAIT); + } + + /** + * {@inheritdoc} + */ + protected function activate(array $watchers) { + foreach ($watchers as $watcher) { + $id = $watcher->id; + + switch ($watcher->type) { + case Watcher::READABLE: + $streamId = (int) $watcher->value; + + if (isset($this->read[$streamId])) { + $event = $this->read[$streamId]; + } elseif (isset($this->events[$id])) { + $event = $this->read[$streamId] = $this->events[$id]; + } else { + $event = $this->read[$streamId] = \uv_poll_init_socket($this->handle, $watcher->value); + } + + $this->events[$id] = $event; + $this->watchers[(int) $event][$id] = $watcher; + + if (!\uv_is_active($event)) { + \uv_poll_start($event, \UV::READABLE, $this->ioCallback); + } + break; + + case Watcher::WRITABLE: + $streamId = (int) $watcher->value; + + if (isset($this->write[$streamId])) { + $event = $this->write[$streamId]; + } elseif (isset($this->events[$id])) { + $event = $this->write[$streamId] = $this->events[$id]; + } else { + $event = $this->write[$streamId] = \uv_poll_init_socket($this->handle, $watcher->value); + } + + $this->events[$id] = $event; + $this->watchers[(int) $event][$id] = $watcher; + + + if (!\uv_is_active($event)) { + \uv_poll_start($event, \UV::WRITABLE, $this->ioCallback); + } + break; + + case Watcher::DELAY: + case Watcher::REPEAT: + if (isset($this->events[$id])) { + $event = $this->events[$id]; + } else { + $event = $this->events[$id] = \uv_timer_init($this->handle); + $this->watchers[(int) $event] = $watcher; + } + + \uv_timer_start( + $event, + $watcher->value, + $watcher->type & Watcher::REPEAT ? $watcher->value : 0, + $this->timerCallback + ); + break; + + case Watcher::SIGNAL: + if (isset($this->events[$id])) { + $event = $this->events[$id]; + } else { + $event = $this->events[$id] = \uv_signal_init($this->handle); + $this->watchers[(int) $event] = $watcher; + } + + \uv_signal_start($event, $this->signalCallback, $watcher->value); + break; + + default: throw new \DomainException("Unknown watcher type"); + } + } + } + + /** + * {@inheritdoc} + */ + public function deactivate(Watcher $watcher) { + $id = $watcher->id; + + if (!isset($this->events[$id])) { + return; + } + + $event = $this->events[$id]; + $eventId = (int) $event; + + switch ($watcher->type) { + case Watcher::READABLE: + unset($this->watchers[$eventId][$id]); + + if (empty($this->watchers[$eventId])) { + unset($this->watchers[$eventId]); + unset($this->read[(int) $watcher->value]); + if (\uv_is_active($event)) { + \uv_poll_stop($event); + } + } + break; + + case Watcher::WRITABLE: + unset($this->watchers[$eventId][$id]); + + if (empty($this->watchers[$eventId])) { + unset($this->watchers[$eventId]); + unset($this->write[(int) $watcher->value]); + if (\uv_is_active($event)) { + \uv_poll_stop($event); + } + } + break; + + case Watcher::DELAY: + case Watcher::REPEAT: + unset($this->watchers[$eventId]); + if (\uv_is_active($event)) { + \uv_timer_stop($event); + } + break; + + case Watcher::SIGNAL: + unset($this->watchers[$eventId]); + if (\uv_is_active($event)) { + \uv_signal_stop($event); + } + break; + + default: throw new \DomainException("Unknown watcher type"); + } + } + + /** + * {@inheritdoc} + */ + public function cancel($watcherIdentifier) { + parent::cancel($watcherIdentifier); + + if (!isset($this->events[$watcherIdentifier])) { + return; + } + + $event = $this->events[$watcherIdentifier]; + + if (empty($this->watchers[(int) $event])) { + \uv_close($event); + } + + unset($this->events[$watcherIdentifier]); + } + + /** + * {@inheritdoc} + */ + public function getHandle() { + return $this->handle; + } +} diff --git a/test/UvLoopTest.php b/test/UvLoopTest.php new file mode 100644 index 0000000..f7f7957 --- /dev/null +++ b/test/UvLoopTest.php @@ -0,0 +1,18 @@ +getMockBuilder(DriverFactory::class)->getMock(); + + $factory->method('create') + ->willReturn(new UvLoop()); + + return $factory; + } +}