33namespace  Amp \Parallel \Context \Internal ;
44
55use  Amp \Cancellation ;
6+ use  Amp \CancelledException ;
67use  Amp \ForbidCloning ;
78use  Amp \ForbidSerialization ;
89use  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