8
8
MessageChannel ,
9
9
MessagePort ,
10
10
TransferListItem ,
11
+ Worker ,
11
12
receiveMessageOnPort ,
12
13
} from 'worker_threads' ;
13
14
@@ -36,6 +37,33 @@ enum BufferState {
36
37
Closed = 0b10 ,
37
38
}
38
39
40
+ /**
41
+ * Options that can be passed to {@link SyncMessagePort.receiveMessage}.
42
+ */
43
+ export interface ReceiveMessageOptions {
44
+ /**
45
+ * The time (in milliseconds) to wait for a message before returning {@link
46
+ * timeoutValue} (if set) or throwing a [TimeoutException] otherwise.
47
+ */
48
+ timeout ?: number ;
49
+
50
+ /**
51
+ * If a message isn't received within {@link timeout} milliseconds, this value
52
+ * is returned. Ignored if {@link timeout} is not set.
53
+ */
54
+ timeoutValue ?: unknown ;
55
+ }
56
+
57
+ /**
58
+ * An exception thrown by {@link SyncMessagePort.receiveMessage} if a message
59
+ * isn't received within {@link ReceivedMessageOptions.timeout} milliseconds.
60
+ */
61
+ export class TimeoutException extends Error {
62
+ constructor ( message : string ) {
63
+ super ( message ) ;
64
+ }
65
+ }
66
+
39
67
/**
40
68
* A communication port that can receive messages synchronously from another
41
69
* `SyncMessagePort`.
@@ -132,7 +160,6 @@ export class SyncMessagePort extends EventEmitter {
132
160
}
133
161
134
162
// TODO(nex3):
135
- // * Add a timeout option to `receiveMessage()`
136
163
// * Add an option to `receiveMessage()` to return a special value if the
137
164
// channel is closed.
138
165
@@ -143,7 +170,7 @@ export class SyncMessagePort extends EventEmitter {
143
170
* Throws an error if the channel is closed, including if it closes while this
144
171
* is waiting for a message.
145
172
*/
146
- receiveMessage ( ) : unknown {
173
+ receiveMessage ( options ?: ReceiveMessageOptions ) : unknown {
147
174
if ( this . listenerCount ( 'message' ) ) {
148
175
throw new Error (
149
176
'SyncMessageChannel.receiveMessage() may not be called while there ' +
@@ -156,14 +183,13 @@ export class SyncMessagePort extends EventEmitter {
156
183
// `receiveMessageOnPort` and the call to `Atomics.wait()`, we won't
157
184
// overwrite it. Use `Atomics.compareExchange` so that we don't overwrite
158
185
// the "closed" state.
159
- if (
160
- Atomics . compareExchange (
161
- this . buffer ,
162
- 0 ,
163
- BufferState . MessageSent ,
164
- BufferState . AwaitingMessage ,
165
- ) === BufferState . Closed
166
- ) {
186
+ const previousState = Atomics . compareExchange (
187
+ this . buffer ,
188
+ 0 ,
189
+ BufferState . MessageSent ,
190
+ BufferState . AwaitingMessage ,
191
+ ) ;
192
+ if ( previousState === BufferState . Closed ) {
167
193
throw new Error ( "The SyncMessagePort's channel is closed." ) ;
168
194
}
169
195
@@ -173,10 +199,20 @@ export class SyncMessagePort extends EventEmitter {
173
199
// If there's no new message, wait for the other port to flip the "new
174
200
// message" indicator to 1. If it's been set to 1 since we stored 0, this
175
201
// will terminate immediately.
176
- Atomics . wait ( this . buffer , 0 , BufferState . AwaitingMessage ) ;
202
+ const result = Atomics . wait (
203
+ this . buffer ,
204
+ 0 ,
205
+ BufferState . AwaitingMessage ,
206
+ options ?. timeout ,
207
+ ) ;
177
208
message = receiveMessageOnPort ( this . port ) ;
178
209
if ( message ) return message . message ;
179
210
211
+ if ( result === 'timed-out' ) {
212
+ if ( 'timeoutValue' in options ! ) return options . timeoutValue ;
213
+ throw new TimeoutException ( 'SyncMessagePort.receiveMessage() timed out.' ) ;
214
+ }
215
+
180
216
// Update the state to 0b10 after the last message is consumed.
181
217
const oldState = Atomics . and ( this . buffer , 0 , BufferState . Closed ) ;
182
218
// Assert the old state was either 0b10 or 0b11.
@@ -187,6 +223,7 @@ export class SyncMessagePort extends EventEmitter {
187
223
/** See `MessagePort.close()`. */
188
224
close ( ) : void {
189
225
Atomics . or ( this . buffer , 0 , BufferState . Closed ) ;
226
+ Atomics . notify ( this . buffer , 0 ) ;
190
227
this . port . close ( ) ;
191
228
}
192
229
}
0 commit comments