Skip to content

Commit

Permalink
Added initialize/finalize events to CoroutineInterface, added KernelA…
Browse files Browse the repository at this point in the history
…piInterface::finally_() (fixes #64)
  • Loading branch information
jmalloc committed Jan 5, 2014
1 parent 6c9ab92 commit 072715b
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 1 deletion.
6 changes: 5 additions & 1 deletion src/Recoil/Coroutine/CoroutineInterface.php
@@ -1,13 +1,17 @@
<?php
namespace Recoil\Coroutine;

use Evenement\EventEmitterInterface;
use Exception;
use Recoil\Kernel\Strand\StrandInterface;

/**
* A coroutine represents a unit of work that can be suspended and resumed.
*
* @event initialize The coroutine has been initialized and pushed onto the call stack.
* @event finalize The coroutine has been finalized and popped from the call stack.
*/
interface CoroutineInterface
interface CoroutineInterface extends EventEmitterInterface
{
/**
* Initialize the coroutine.
Expand Down
3 changes: 3 additions & 0 deletions src/Recoil/Coroutine/CoroutineTrait.php
@@ -1,6 +1,7 @@
<?php
namespace Recoil\Coroutine;

use Evenement\EventEmitterTrait;
use Exception;
use Recoil\Kernel\Strand\StrandInterface;

Expand All @@ -9,6 +10,8 @@
*/
trait CoroutineTrait
{
use EventEmitterTrait;

/**
* Initialize the coroutine.
*
Expand Down
17 changes: 17 additions & 0 deletions src/Recoil/Kernel/Api/KernelApi.php
Expand Up @@ -61,6 +61,23 @@ public function throw_(StrandInterface $strand, Exception $exception)
$strand->throwException($exception);
}

/**
* Register a callback to be invoked when the current coroutine is popped
* from the call stack.
*
* The callback is guaranteed to be called even if a reference to the
* coroutine is held elsewhere (unlike a PHP finally block in a generator).
*
* @param StrandInterface $strand The currently executing strand.
* @param callable $callback The callback to invoke.
*/
public function finally_(StrandInterface $strand, callable $callback)
{
$strand->current()->once('finalize', $callback);

$strand->resumeWithValue(null);
}

/**
* Terminate execution of the strand.
*
Expand Down
12 changes: 12 additions & 0 deletions src/Recoil/Kernel/Api/KernelApiInterface.php
Expand Up @@ -46,6 +46,18 @@ public function return_(StrandInterface $strand, $value = null);
*/
public function throw_(StrandInterface $strand, Exception $exception);

/**
* Register a callback to be invoked when the current coroutine is popped
* from the call stack.
*
* The callback is guaranteed to be called even if a reference to the
* coroutine is held elsewhere (unlike a PHP finally block in a generator).
*
* @param StrandInterface $strand The currently executing strand.
* @param callable $callback The callback to invoke.
*/
public function finally_(StrandInterface $strand, callable $callback);

/**
* Terminate execution of the strand.
*
Expand Down
3 changes: 3 additions & 0 deletions src/Recoil/Kernel/Strand/Strand.php
Expand Up @@ -71,6 +71,7 @@ public function push($coroutine)
->adapt($this, $coroutine);

$coroutine->initialize($this);
$coroutine->emit('initialize', [$this, $coroutine]);

$this->stack->push($coroutine);

Expand All @@ -87,6 +88,8 @@ public function pop()
$coroutine = $this->stack->pop();

$coroutine->finalize($this);
$coroutine->emit('finalize', [$this, $coroutine]);
$coroutine->removeAllListeners();

return $coroutine;
}
Expand Down
68 changes: 68 additions & 0 deletions test/suite/Kernel/Api/KernelApiTest.php
Expand Up @@ -96,6 +96,74 @@ public function testThrow()
$this->kernel->eventLoop()->run();
}

public function testFinally()
{
$this->expectOutputString('12');

$coroutine = function () {
$strand = (yield Recoil::strand());
$coroutine = $strand->current();

yield Recoil::finally_(
function ($s, $c) use ($strand, $coroutine) {
$this->assertSame($strand, $s);
$this->assertSame($coroutine, $c);
echo 2;
}
);

echo 1;
};

$this->kernel->execute($coroutine());

$this->kernel->eventLoop()->run();
}

public function testFinallyWithException()
{
$this->expectOutputString('12');

$coroutine = function () {
yield Recoil::finally_(
function () {
echo 2;
}
);

echo 1;

throw new Exception('This is the exception.');
};

$this->kernel->execute($coroutine());

$this->setExpectedException('Exception', 'This is the exception.');

$this->kernel->eventLoop()->run();
}

public function testFinallyWithTermination()
{
$this->expectOutputString('12');

$coroutine = function () {
yield Recoil::finally_(
function () {
echo 2;
}
);

echo 1;

yield Recoil::terminate();
};

$this->kernel->execute($coroutine());

$this->kernel->eventLoop()->run();
}

public function testTerminate()
{
$this->expectOutputString('12');
Expand Down

0 comments on commit 072715b

Please sign in to comment.