Skip to content

Added event loop based on pecl/event #248

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 26, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Added ExtEventLoop.
  • Loading branch information
jmalloc committed Dec 12, 2013
commit b72dbf4ebd2e3b93d8c062aa549b3900f439cbc2
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ before_script:
cd libevent-0.0.5 && phpize && ./configure && make && sudo make install;
echo "extension=libevent.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`;
fi"
- echo "yes" | pecl install event
- composer self-update
- composer install --dev --prefer-source

Expand Down
308 changes: 308 additions & 0 deletions src/React/EventLoop/ExtEventLoop.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
<?php

namespace React\EventLoop;

use Event;
use EventBase;
use React\EventLoop\Tick\NextTickQueue;
use React\EventLoop\Timer\Timer;
use React\EventLoop\Timer\TimerInterface;
use SplObjectStorage;

/**
* An ext-event based event-loop.
*/
class ExtEventLoop implements LoopInterface
{
private $eventBase;
private $nextTickQueue;
private $timerCallback;
private $timerEvents;
private $streamCallback;
private $streamEvents = [];
private $streamFlags = [];
private $readListeners = [];
private $writeListeners = [];
private $running;

public function __construct()
{
$this->eventBase = new EventBase();
$this->nextTickQueue = new NextTickQueue($this);
$this->timerEvents = new SplObjectStorage();

$this->createTimerCallback();
$this->createStreamCallback();
}

/**
* {@inheritdoc}
*/
public function addReadStream($stream, callable $listener)
{
$key = (int) $stream;

if (!isset($this->readListeners[$key])) {
$this->readListeners[$key] = $listener;
$this->subscribeStreamEvent($stream, Event::READ);
}
}

/**
* {@inheritdoc}
*/
public function addWriteStream($stream, callable $listener)
{
$key = (int) $stream;

if (!isset($this->writeListeners[$key])) {
$this->writeListeners[$key] = $listener;
$this->subscribeStreamEvent($stream, Event::WRITE);
}
}

/**
* {@inheritdoc}
*/
public function removeReadStream($stream)
{
$key = (int) $stream;

if (isset($this->readListeners[$key])) {
unset($this->readListeners[$key]);
$this->unsubscribeStreamEvent($stream, Event::READ);
}
}

/**
* {@inheritdoc}
*/
public function removeWriteStream($stream)
{
$key = (int) $stream;

if (isset($this->writeListeners[$key])) {
unset($this->writeListeners[$key]);
$this->unsubscribeStreamEvent($stream, Event::WRITE);
}
}

/**
* {@inheritdoc}
*/
public function removeStream($stream)
{
$key = (int) $stream;

if (isset($this->streamEvents[$key])) {
$this->streamEvents[$key]->free();

unset(
$this->streamFlags[$key],
$this->streamEvents[$key],
$this->readListeners[$key],
$this->writeListeners[$key]
);
}
}

/**
* {@inheritdoc}
*/
public function addTimer($interval, callable $callback)
{
$timer = new Timer($this, $interval, $callback, false);

$this->scheduleTimer($timer);

return $timer;
}

/**
* {@inheritdoc}
*/
public function addPeriodicTimer($interval, callable $callback)
{
$timer = new Timer($this, $interval, $callback, true);

$this->scheduleTimer($timer);

return $timer;
}

/**
* {@inheritdoc}
*/
public function cancelTimer(TimerInterface $timer)
{
if ($this->isTimerActive($timer)) {
$this->timerEvents[$timer]->free();
$this->timerEvents->detach($timer);
}
}

/**
* {@inheritdoc}
*/
public function isTimerActive(TimerInterface $timer)
{
return $this->timerEvents->contains($timer);
}

/**
* {@inheritdoc}
*/
public function nextTick(callable $listener)
{
$this->nextTickQueue->add($listener);
}

/**
* {@inheritdoc}
*/
public function tick()
{
$this->nextTickQueue->tick();

// @-suppression: https://github.com/reactphp/react/pull/234#discussion-diff-7759616R226
@$this->eventBase->loop(EventBase::LOOP_ONCE | EventBase::LOOP_NONBLOCK);
}

/**
* {@inheritdoc}
*/
public function run()
{
$this->running = true;

while ($this->running) {
$this->nextTickQueue->tick();

if (!$this->streamEvents && !$this->timerEvents->count()) {
break;
}

// @-suppression: https://github.com/reactphp/react/pull/234#discussion-diff-7759616R226
@$this->eventBase->loop(EventBase::LOOP_ONCE);
}
}

/**
* {@inheritdoc}
*/
public function stop()
{
$this->running = false;
}

/**
* Schedule a timer for execution.
*
* @param TimerInterface $timer
*/
private function scheduleTimer(TimerInterface $timer)
{
$flags = Event::TIMEOUT;

if ($timer->isPeriodic()) {
$flags |= Event::PERSIST;
}

$event = new Event($this->eventBase, -1, $flags, $this->timerCallback, $timer);
$this->timerEvents[$timer] = $event;

$event->add($timer->getInterval());
}

/**
* Create a new ext-event Event object, or update the existing one.
*
* @param stream $stream
* @param integer $flag Event::READ or Event::WRITE
*/
private function subscribeStreamEvent($stream, $flag)
{
$key = (int) $stream;

if (isset($this->streamEvents[$key])) {
$event = $this->streamEvents[$key];
$flags = ($this->streamFlags[$key] |= $flag);

$event->del();
$event->set($this->eventBase, $stream, Event::PERSIST | $flags, $this->streamCallback);
} else {
$event = new Event($this->eventBase, $stream, Event::PERSIST | $flag, $this->streamCallback);

$this->streamEvents[$key] = $event;
$this->streamFlags[$key] = $flag;
}

$event->add();
}

/**
* 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
*/
private function unsubscribeStreamEvent($stream, $flag)
{
$key = (int) $stream;

$flags = $this->streamFlags[$key] &= ~$flag;

if (0 === $flags) {
$this->removeStream($stream);

return;
}

$event = $this->streamEvents[$key];

$event->del();
$event->set($this->eventBase, $stream, Event::PERSIST | $flags, $this->streamCallback);
$event->add();
}

/**
* Create a callback used as the target of timer events.
*
* A reference is kept to the callback for the lifetime of the loop
* to prevent "Cannot destroy active lambda function" fatal error from
* the event extension.
*/
private function createTimerCallback()
{
$this->timerCallback = function ($_, $_, $timer) {
call_user_func($timer->getCallback(), $timer);

if (!$timer->isPeriodic() && $this->isTimerActive($timer)) {
$this->cancelTimer($timer);
}
};
}

/**
* Create a callback used as the target of stream events.
*
* A reference is kept to the callback for the lifetime of the loop
* to prevent "Cannot destroy active lambda function" fatal error from
* the event extension.
*/
private function createStreamCallback()
{
$this->streamCallback = function ($stream, $flags) {
$key = (int) $stream;

if (Event::READ === (Event::READ & $flags) && isset($this->readListeners[$key])) {
call_user_func($this->readListeners[$key], $stream, $this);
}

if (Event::WRITE === (Event::WRITE & $flags) && isset($this->writeListeners[$key])) {
call_user_func($this->writeListeners[$key], $stream, $this);
}
};
}
}
2 changes: 2 additions & 0 deletions src/React/EventLoop/LibEventLoop.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace React\EventLoop;

use Event;
use EventBase;
use React\EventLoop\Tick\NextTickQueue;
use React\EventLoop\Timer\Timer;
use React\EventLoop\Timer\TimerInterface;
Expand Down
59 changes: 59 additions & 0 deletions tests/React/Tests/EventLoop/ExtEventLoopTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace React\Tests\EventLoop;

use React\EventLoop\ExtEventLoop;

class ExtEventLoopTest extends AbstractLoopTest
{
public function createLoop()
{
if ('Linux' === PHP_OS && !extension_loaded('posix')) {
$this->markTestSkipped('libevent tests skipped on linux due to linux epoll issues.');
}

if (!extension_loaded('event')) {
$this->markTestSkipped('ext-event tests skipped because ext-event is not installed.');
}

return new ExtEventLoop();
}

public function createStream()
{
// Use a FIFO on linux to get around lack of support for disk-based file
// descriptors when using the EPOLL back-end.
if ('Linux' === PHP_OS) {
$this->fifoPath = tempnam(sys_get_temp_dir(), 'react-');

unlink($this->fifoPath);

posix_mkfifo($this->fifoPath, 0600);

$stream = fopen($this->fifoPath, 'r+');

// ext-event (as of 1.8.1) does not yet support in-memory temporary
// streams. Setting maxmemory:0 and performing a write forces PHP to
// back this temporary stream with a real file.
//
// This problem is mentioned at https://bugs.php.net/bug.php?id=64652&edit=3
// but remains unresolved (despite that issue being closed).
} else {
$stream = fopen('php://temp/maxmemory:0', 'r+');

fwrite($stream, 'x');
ftruncate($stream, 0);
}

return $stream;
}

public function writeToStream($stream, $content)
{
if ('Linux' !== PHP_OS) {
return parent::writeToStream($stream, $content);
}

fwrite($stream, $content);
}
}
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy