Skip to content

Commit 09bebca

Browse files
committed
feat(async): throw if resource is closed while waiting
Signed-off-by: azjezz <azjezz@protonmail.com>
1 parent e692cc4 commit 09bebca

File tree

5 files changed

+45
-18
lines changed

5 files changed

+45
-18
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Psl\Async\Exception;
6+
7+
final class ResourceClosedException extends RuntimeException
8+
{
9+
}

src/Psl/Async/await_readable.php

+12
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,19 @@
44

55
namespace Psl\Async;
66

7+
use Psl\Async\Exception\ResourceClosedException;
78
use Revolt\EventLoop;
9+
use Throwable;
10+
11+
use function is_resource;
812

913
/**
1014
* Wait for the given resource to become readable in a non-blocking way.
1115
*
1216
* @param resource|object $resource
1317
*
1418
* @throws Exception\TimeoutException If $timeout is non-null, and the operation timed-out.
19+
* @throws Exception\ResourceClosedException If $resource was closed before it became readable.
1520
*
1621
* @codeCoverageIgnore
1722
*/
@@ -36,6 +41,13 @@ function await_readable(mixed $resource, bool $reference = true, ?float $timeout
3641

3742
try {
3843
$suspension->suspend();
44+
} catch (Throwable $e) {
45+
if (!is_resource($resource)) {
46+
throw new ResourceClosedException('Resource was closed before it became readable.');
47+
}
48+
49+
/** @psalm-suppress MissingThrowsDocblock */
50+
throw $e;
3951
} finally {
4052
Scheduler::cancel($watcher);
4153

src/Psl/Async/await_writable.php

+10
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44

55
namespace Psl\Async;
66

7+
use Psl\Async\Exception\ResourceClosedException;
78
use Revolt\EventLoop;
9+
use Throwable;
810

911
/**
1012
* Wait for the given resource to become writable in a non-blocking way.
1113
*
1214
* @param resource|object $resource
1315
*
1416
* @throws Exception\TimeoutException If $timeout is non-null, and the operation timed-out.
17+
* @throws Exception\ResourceClosedException If $resource was closed before it became writable.
1518
*
1619
* @codeCoverageIgnore
1720
*/
@@ -36,6 +39,13 @@ function await_writable(mixed $resource, bool $reference = true, ?float $timeout
3639

3740
try {
3841
$suspension->suspend();
42+
} catch (Throwable $e) {
43+
if (!is_resource($resource)) {
44+
throw new ResourceClosedException('Resource was closed before it became writable.');
45+
}
46+
47+
/** @psalm-suppress MissingThrowsDocblock */
48+
throw $e;
3949
} finally {
4050
Scheduler::cancel($watcher);
4151

src/Psl/Async/main.php

+6-18
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,23 @@
44

55
namespace Psl\Async;
66

7-
use Throwable;
8-
97
/**
108
* Execute the given callable in an async context, then exit with returned exit code.
119
*
1210
* If the callable returns an awaitable, the awaitable *MUST* resolve with an exit code.
1311
*
14-
* @param (callable(): int)|(callable(): Awaitable<int>) $callable
12+
* @param (callable(): int)|(callable(): Awaitable<int>)|(callable(): never)|(callable(): Awaitable<never>) $callable
1513
*
1614
* @codeCoverageIgnore
1715
*/
1816
function main(callable $callable): never
1917
{
20-
$main = Scheduler::createSuspension();
21-
22-
Scheduler::defer(static function () use ($callable, $main): void {
23-
try {
24-
$exit_code = $callable();
25-
$main->resume($exit_code);
26-
} catch (Throwable $throwable) {
27-
$main->throw($throwable);
28-
}
29-
});
18+
later();
3019

31-
/** @var int|Awaitable<int> $return */
32-
$return = $main->suspend();
33-
if ($return instanceof Awaitable) {
34-
$return = $return->await();
20+
$result = $callable();
21+
if ($result instanceof Awaitable) {
22+
$result = $result->await();
3523
}
3624

37-
exit($return);
25+
exit($result);
3826
}

src/Psl/IO/Internal/ResourceHandle.php

+8
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ public function write(string $bytes, ?float $timeout = null): int
124124
Async\await_writable($this->resource, timeout: $timeout);
125125
} catch (Async\Exception\TimeoutException) {
126126
throw new Exception\TimeoutException('reached timeout while the handle is still not writable.');
127+
} catch (Async\Exception\ResourceClosedException) {
128+
$this->resource = null;
129+
130+
throw new Exception\AlreadyClosedException('Handle has already been closed.');
127131
}
128132

129133
return $written + $this->writeImmediately($bytes);
@@ -215,6 +219,10 @@ public function read(?int $max_bytes = null, ?float $timeout = null): string
215219
Async\await_readable($this->resource, timeout: $timeout);
216220
} catch (Async\Exception\TimeoutException) {
217221
throw new Exception\TimeoutException('reached timeout while the handle is still not readable.');
222+
} catch (Async\Exception\ResourceClosedException) {
223+
$this->resource = null;
224+
225+
throw new Exception\AlreadyClosedException('Handle has already been closed.');
218226
}
219227

220228
return $this->readImmediately($max_bytes);

0 commit comments

Comments
 (0)