2
2
3
3
namespace Amp \Parallel \Ipc ;
4
4
5
+ use Amp \Cache \LocalCache ;
5
6
use Amp \Cancellation ;
6
- use Amp \DeferredFuture ;
7
+ use Amp \CancelledException ;
7
8
use Amp \ForbidCloning ;
8
9
use Amp \ForbidSerialization ;
10
+ use Amp \NullCancellation ;
9
11
use Amp \Socket ;
10
12
use Amp \Socket \ResourceSocket ;
11
13
use Amp \Socket \SocketAddressType ;
@@ -20,22 +22,19 @@ final class SocketIpcHub implements IpcHub
20
22
public const DEFAULT_KEY_RECEIVE_TIMEOUT = 5 ;
21
23
public const DEFAULT_KEY_LENGTH = 64 ;
22
24
23
- private int $ nextId = 0 ;
24
-
25
25
/** @var non-empty-string */
26
26
private readonly string $ uri ;
27
27
28
- /** @var array<string, int> */
29
- private array $ keys = [];
30
-
31
- /** @var array<int, DeferredFuture> */
32
- private array $ pending = [];
28
+ /** @var array<string, EventLoop\Suspension> */
29
+ private array $ waitingByKey = [];
33
30
34
31
/** @var \Closure(): void */
35
32
private readonly \Closure $ accept ;
36
33
37
34
private bool $ queued = false ;
38
35
36
+ private LocalCache $ clientsByKey ;
37
+
39
38
/**
40
39
* @param float $keyReceiveTimeout Timeout to receive the key on accepted connections.
41
40
* @param positive-int $keyLength Length of the random key exchanged on the IPC channel when connecting.
@@ -51,24 +50,26 @@ public function __construct(
51
50
SocketAddressType::Internet => 'tcp:// ' . $ address ->toString (),
52
51
};
53
52
53
+ $ this ->clientsByKey = new LocalCache (1024 , $ keyReceiveTimeout );
54
+
54
55
$ queued = &$ this ->queued ;
55
- $ keys = &$ this ->keys ;
56
- $ pending = &$ this ->pending ;
56
+ $ waitingByKey = &$ this ->waitingByKey ;
57
+ $ clientsByKey = &$ this ->clientsByKey ;
57
58
$ this ->accept = static function () use (
58
59
&$ queued ,
59
- &$ keys ,
60
- &$ pending ,
60
+ &$ waitingByKey ,
61
+ &$ clientsByKey ,
61
62
$ server ,
62
63
$ keyReceiveTimeout ,
63
64
$ keyLength ,
64
65
): void {
65
- while ($ pending ) {
66
+ while ($ waitingByKey ) {
66
67
$ client = $ server ->accept ();
67
68
if (!$ client ) {
68
69
$ queued = false ;
69
70
$ exception = new Socket \SocketException ('IPC socket closed before the client connected ' );
70
- foreach ($ pending as $ deferred ) {
71
- $ deferred -> error ($ exception );
71
+ foreach ($ waitingByKey as $ suspension ) {
72
+ $ suspension -> throw ($ exception );
72
73
}
73
74
return ;
74
75
}
@@ -80,22 +81,12 @@ public function __construct(
80
81
continue ; // Ignore possible foreign connection attempt.
81
82
}
82
83
83
- $ id = $ keys [$ received ] ?? null ;
84
-
85
- if ( $ id === null ) {
86
- $ client -> close ();
87
- continue ; // Ignore possible foreign connection attempt.
84
+ if ( isset ( $ waitingByKey [$ received ])) {
85
+ $ waitingByKey [ $ received ]-> resume ( $ client );
86
+ unset( $ waitingByKey [ $ received ]);
87
+ } else {
88
+ $ clientsByKey -> set ( $ received , $ client );
88
89
}
89
-
90
- $ deferred = $ pending [$ id ] ?? null ;
91
- unset($ pending [$ id ], $ keys [$ received ]);
92
-
93
- if ($ deferred === null ) {
94
- $ client ->close ();
95
- continue ; // Client accept cancelled.
96
- }
97
-
98
- $ deferred ->complete ($ client );
99
90
}
100
91
101
92
$ queued = false ;
@@ -116,13 +107,13 @@ public function close(): void
116
107
{
117
108
$ this ->server ->close ();
118
109
119
- if (!$ this ->pending ) {
110
+ if (!$ this ->waitingByKey ) {
120
111
return ;
121
112
}
122
113
123
114
$ exception = new Socket \SocketException ('IPC socket closed before the client connected ' );
124
- foreach ($ this ->pending as $ deferred ) {
125
- $ deferred -> error ($ exception );
115
+ foreach ($ this ->waitingByKey as $ suspension ) {
116
+ $ suspension -> throw ($ exception );
126
117
}
127
118
}
128
119
@@ -158,24 +149,34 @@ public function accept(string $key, ?Cancellation $cancellation = null): Resourc
158
149
));
159
150
}
160
151
161
- if (isset ($ this ->keys [$ key ])) {
152
+ if (isset ($ this ->waitingByKey [$ key ])) {
162
153
throw new \Error ("An accept is already pending for the given key " );
163
154
}
164
155
165
- $ id = $ this ->nextId ++;
156
+ $ client = $ this ->clientsByKey ->get ($ key );
157
+ if ($ client ) {
158
+ $ this ->clientsByKey ->delete ($ key );
159
+
160
+ return $ client ;
161
+ }
166
162
167
163
if (!$ this ->queued ) {
168
164
EventLoop::queue ($ this ->accept );
169
165
$ this ->queued = true ;
170
166
}
171
167
172
- $ this ->keys [$ key ] = $ id ;
173
- $ this ->pending [$ id ] = $ deferred = new DeferredFuture ();
168
+ $ this ->waitingByKey [$ key ] = $ suspension = EventLoop::getSuspension ();
169
+
170
+ $ cancellation = $ cancellation ?? new NullCancellation ();
171
+ $ cancellationId = $ cancellation ->subscribe (function (CancelledException $ exception ) use ($ suspension ) {
172
+ $ suspension ->throw ($ exception );
173
+ });
174
174
175
175
try {
176
- $ client = $ deferred -> getFuture ()-> await ( $ cancellation );
176
+ $ client = $ suspension -> suspend ( );
177
177
} finally {
178
- unset($ this ->pending [$ id ], $ this ->keys [$ key ]);
178
+ $ cancellation ->unsubscribe ($ cancellationId );
179
+ unset($ this ->waitingByKey [$ key ]);
179
180
}
180
181
181
182
return $ client ;
0 commit comments