Skip to content

Commit

Permalink
Merge pull request #123 from recoilphp/immediate-resume
Browse files Browse the repository at this point in the history
Change `Api::resume()` and `throw()` to resume the suspended strand immediately.
  • Loading branch information
jmalloc authored Jul 15, 2016
2 parents 7bd4607 + b7cc50d commit bfdca65
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 167 deletions.
2 changes: 1 addition & 1 deletion examples/strand-suspend
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ ReactKernel::start(function () {
// Perform some work, possibly asynchronously, and then resume the
// strand. The value used to the resume the strand is returned by the
// suspend call.
$strand->resume('The strand was resumed!');
$strand->send('The strand was resumed!');
});

echo $value . PHP_EOL;
Expand Down
8 changes: 4 additions & 4 deletions src/Kernel/Api.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ public function suspend(
* This causes the suspended strand's blocking suspend() call to return
* $value.
*
* The implementation must delay execution of the resumed strand until the
* next 'tick' of the kernel.
* The implementation must resume the suspended strand before resuming the
* calling strand.
*
* @param Strand $strand The strand executing the API call.
* @param Strand $suspended The suspended strand.
Expand All @@ -162,8 +162,8 @@ public function resume(
* This causes the suspended strand's blocking suspend() call to throw
* $exception.
*
* The implementation must delay execution of the resumed strand until the
* next 'tick' of the kernel.
* The implementation must resume the suspended strand before resuming the
* calling strand.
*
* @param Strand $strand The strand executing the API call.
* @param Strand $suspended The suspended strand.
Expand Down
48 changes: 48 additions & 0 deletions src/Kernel/ApiTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,54 @@ public function suspend(
}
}

/**
* Resume execution of a suspended strand.
*
* This causes the suspended strand's blocking suspend() call to return
* $value.
*
* The implementation must resume the suspended strand before resuming the
* calling strand.
*
* @param Strand $strand The strand executing the API call.
* @param Strand $suspended The suspended strand.
* @param mixed $value The value to pass to the resumed strand.
*
* @return Generator|null
*/
public function resume(
Strand $strand,
Strand $suspended,
$value = null
) {
$suspended->send($value, $strand);
$strand->send();
}

/**
* Resume execution of a suspended strand with an error.
*
* This causes the suspended strand's blocking suspend() call to throw
* $exception.
*
* The implementation must resume the suspended strand before resuming the
* calling strand.
*
* @param Strand $strand The strand executing the API call.
* @param Strand $suspended The suspended strand.
* @param Throwable $exception The exception to pass to the resumed strand.
*
* @return Generator|null
*/
public function throw(
Strand $strand,
Strand $suspended,
Throwable $exception
) {
$suspended->throw($exception, $strand);
$strand->send();
}

/**
* Terminate the calling strand.
*
Expand Down
59 changes: 0 additions & 59 deletions src/React/ReactApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
use Recoil\Kernel\Api;
use Recoil\Kernel\ApiTrait;
use Recoil\Kernel\Strand;
use Throwable;

/**
* A kernel API based on the React event loop.
Expand Down Expand Up @@ -91,64 +90,6 @@ public function timeout(Strand $strand, float $seconds, $coroutine)
(new StrandTimeout($this->eventLoop, $seconds, $substrand))->await($strand, $this);
}

/**
* Resume execution of a suspended strand.
*
* This causes the suspended strand's blocking suspend() call to return
* $value.
*
* The implementation must delay execution of the resumed strand until the
* next 'tick' of the kernel.
*
* @param Strand $strand The strand executing the API call.
* @param Strand $suspended The suspended strand.
* @param mixed $value The value to pass to the resumed strand.
*
* @return Generator|null
*/
public function resume(
Strand $strand,
Strand $suspended,
$value = null
) {
$this->eventLoop->futureTick(
function () use ($strand, $suspended, $value) {
$suspended->send($value, $strand);
}
);

$strand->send();
}

/**
* Resume execution of a suspended strand with an error.
*
* This causes the suspended strand's blocking suspend() call to throw
* $exception.
*
* The implementation must delay execution of the resumed strand until the
* next 'tick' of the kernel.
*
* @param Strand $strand The strand executing the API call.
* @param Strand $suspended The suspended strand.
* @param Throwable $exception The exception to pass to the resumed strand.
*
* @return Generator|null
*/
public function throw(
Strand $strand,
Strand $suspended,
Throwable $exception
) {
$this->eventLoop->futureTick(
function () use ($strand, $suspended, $exception) {
$suspended->throw($exception, $strand);
}
);

$strand->send();
}

/**
* Read data from a stream resource, blocking until a specified amount of
* data is available.
Expand Down
85 changes: 55 additions & 30 deletions test/suite-functional/api/functional.suspend.spec.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,49 +48,74 @@ function ($strand) use ($expected) {
$strand->terminate();
});

rit('can be resumed', function () {
$resumed = false;
$strand = yield Recoil::execute(function () use (&$resumed) {
yield Recoil::suspend();
$resumed = true;
});
context('when resumed with a value', function () {
rit('executes the resumed strand before resume() returns', function () {
$suspended = false;
$strand = yield Recoil::execute(function () use (&$suspended) {
$suspended = true;
yield Recoil::suspend();
$suspended = false;
});

yield; // yield once to allow the other strand to run
yield; // yield once to allow the other strand to run

yield Recoil::resume($strand);
expect($suspended)->to->be->true;

expect($resumed)->to->be->false;
yield Recoil::resume($strand);

yield; // yield again to allow the other strand to resume
expect($suspended)->to->be->false;
});

expect($resumed)->to->be->true;
});
rit('receives the value', function () {
$value = null;
$strand = yield Recoil::execute(function () use (&$value) {
$value = yield Recoil::suspend();
});

rit('can be resumed with a value', function () {
$strand = yield Recoil::execute(function () {
return yield Recoil::suspend();
yield; // yield to allow the other strand to run

yield Recoil::resume($strand, '<value>');

expect($value)->to->equal('<value>');
});
});

yield; // yield to allow the other strand to run
context('when resumed with an error', function () {
rit('executes the resumed strand before resume() returns', function () {
$suspended = false;
$strand = yield Recoil::execute(function () use (&$suspended) {
try {
$suspended = true;
yield Recoil::suspend();
} catch (Exception $e) {
$suspended = false;
}
});

yield Recoil::resume($strand, '<value>');
yield; // yield once to allow the other strand to run

expect(yield $strand)->to->equal('<value>');
});
expect($suspended)->to->be->true;

rit('can be resumed with error', function () {
$strand = yield Recoil::execute(function () {
try {
yield Recoil::suspend();
} catch (Exception $e) {
return $e;
}
yield Recoil::throw($strand, new Exception('<exception>'));

expect($suspended)->to->be->false;
});

yield; // yield to allow the other strand to run
rit('receives the exception', function () {
$exception = null;
$strand = yield Recoil::execute(function () use (&$exception) {
try {
yield Recoil::suspend();
} catch (Exception $e) {
$exception = $e;
}
});

yield; // yield to allow the other strand to run

$exception = new Exception('<exception>');
yield Recoil::throw($strand, $exception);
$expected = new Exception('<exception>');
yield Recoil::throw($strand, $expected);

expect(yield $strand)->to->equal($exception);
expect($exception)->to->equal($expected);
});
});
32 changes: 32 additions & 0 deletions test/suite/Kernel/ApiTrait.spec.php
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,38 @@
});
});

describe('->resume()', function () {
it('resumes the suspended strand, then the calling strand', function () {
$this->subject->get()->resume(
$this->strand->get(),
$this->substrand1->get(),
'<value>'
);

Phony::inOrder(
$this->substrand1->send->calledWith('<value>', $this->strand),
$this->strand->send->calledWith()
);
});
});

describe('->throw()', function () {
it('resumes the suspended strand, then the calling strand', function () {
$exception = Phony::mock(Throwable::class)->get();

$this->subject->get()->throw(
$this->strand->get(),
$this->substrand1->get(),
$exception
);

Phony::inOrder(
$this->substrand1->throw->calledWith($exception, $this->strand),
$this->strand->send->calledWith()
);
});
});

describe('->terminate()', function () {
it('terminates the strand', function () {
$this->subject->get()->terminate(
Expand Down
73 changes: 0 additions & 73 deletions test/suite/React/ReactApi.spec.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
use React\EventLoop\Timer\TimerInterface;
use Recoil\Kernel\Kernel;
use Recoil\Kernel\Strand;
use Throwable;

describe(ReactApi::class, function () {

Expand Down Expand Up @@ -129,78 +128,6 @@
});
});

describe('->resume()', function () {
it('resumes the suspend strand on the next tick', function () {
$this->subject->resume(
$this->strand->get(),
$this->substrand->get(),
'<value>'
);

$fn = $this->eventLoop->futureTick->calledWith('~')->firstCall()->argument();
expect($fn)->to->satisfy('is_callable');

$this->substrand->noInteraction();

$fn();

$this->substrand->send->calledWith(
'<value>',
$this->strand->get()
);
});

it('resumes the calling strand immediately', function () {
$this->subject->resume(
$this->strand->get(),
$this->substrand->get(),
'<value>'
);

Phony::inOrder(
$this->eventLoop->futureTick->called(),
$this->strand->send->calledWith()
);
});
});

describe('->throw()', function () {
it('resumes the suspend strand on the next tick', function () {
$exception = Phony::mock(Throwable::class)->get();

$this->subject->throw(
$this->strand->get(),
$this->substrand->get(),
$exception
);

$fn = $this->eventLoop->futureTick->calledWith('~')->firstCall()->argument();
expect($fn)->to->satisfy('is_callable');

$this->substrand->noInteraction();

$fn();

$this->substrand->throw->calledWith(
$exception,
$this->strand->get()
);
});

it('resumes the calling strand immediately', function () {
$this->subject->throw(
$this->strand->get(),
$this->substrand->get(),
Phony::mock(Throwable::class)->get()
);

Phony::inOrder(
$this->eventLoop->futureTick->called(),
$this->strand->send->calledWith()
);
});
});

describe('->eventLoop()', function () {
it('resumes the strand with the internal event loop', function () {
$this->subject->eventLoop(
Expand Down

0 comments on commit bfdca65

Please sign in to comment.