Skip to content

Commit 5aeaad2

Browse files
committed
Fix referenced watcher hanging event loop if worker is not joined
Fixes amphp/file#75.
1 parent 7b53bad commit 5aeaad2

File tree

1 file changed

+40
-24
lines changed

1 file changed

+40
-24
lines changed

src/Context/Internal/AbstractContext.php

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Amp\Parallel\Context\Internal;
44

55
use Amp\Cancellation;
6+
use Amp\CancelledException;
67
use Amp\ForbidCloning;
78
use Amp\ForbidSerialization;
89
use Amp\Future;
@@ -24,33 +25,13 @@ abstract class AbstractContext implements Context
2425
use ForbidCloning;
2526
use ForbidSerialization;
2627

27-
/** @var Future<ExitResult> */
28-
private readonly Future $result;
28+
/** @var Future<ExitResult<TResult>>|null */
29+
private ?Future $result = null;
2930

3031
protected function __construct(
3132
private readonly Channel $ipcChannel,
32-
Channel $resultChannel,
33+
private readonly Channel $resultChannel,
3334
) {
34-
$this->result = async(static function () use ($resultChannel): ExitResult {
35-
try {
36-
$data = $resultChannel->receive();
37-
} catch (\Throwable $exception) {
38-
throw new ContextException("Failed to receive result from context", previous: $exception);
39-
} finally {
40-
$resultChannel->close();
41-
}
42-
43-
if (!$data instanceof ExitResult) {
44-
throw new ContextException(\sprintf(
45-
"The context sent data instead of exiting: %s",
46-
flattenArgument($data),
47-
));
48-
}
49-
50-
return $data;
51-
});
52-
53-
$this->result->ignore();
5435
}
5536

5637
public function receive(?Cancellation $cancellation = null): mixed
@@ -116,6 +97,41 @@ public function onClose(\Closure $onClose): void
11697

11798
protected function receiveExitResult(?Cancellation $cancellation = null): ExitResult
11899
{
119-
return $this->result->await($cancellation);
100+
while ($this->result) {
101+
try {
102+
$this->result->await($cancellation);
103+
} catch (CancelledException) {
104+
// Ignore cancellation from a prior join request, throw only if this request was cancelled.
105+
$cancellation?->throwIfRequested();
106+
}
107+
}
108+
109+
$this->result = async(function () use ($cancellation): ExitResult {
110+
try {
111+
$data = $this->resultChannel->receive($cancellation);
112+
$this->resultChannel->close();
113+
} catch (CancelledException $exception) {
114+
throw $exception;
115+
} catch (\Throwable $exception) {
116+
$this->resultChannel->close();
117+
throw new ContextException("Failed to receive result from context", previous: $exception);
118+
}
119+
120+
if (!$data instanceof ExitResult) {
121+
throw new ContextException(\sprintf(
122+
"The context sent data instead of exiting: %s",
123+
flattenArgument($data),
124+
));
125+
}
126+
127+
return $data;
128+
});
129+
130+
try {
131+
return $this->result->await();
132+
} catch (CancelledException $exception) {
133+
$this->result = null;
134+
throw $exception;
135+
}
120136
}
121137
}

0 commit comments

Comments
 (0)