@@ -3,7 +3,12 @@ import path from 'node:path'
3
3
4
4
import { jest } from '@jest/globals'
5
5
6
- import { _dirname , testIf , tsUseEsmSupported } from './helpers.js'
6
+ import {
7
+ _dirname ,
8
+ setupReceiveMessageOnPortMock ,
9
+ testIf ,
10
+ tsUseEsmSupported ,
11
+ } from './helpers.js'
7
12
import type { AsyncWorkerFn } from './types.js'
8
13
9
14
import { createSyncFn } from 'synckit'
@@ -12,6 +17,7 @@ const { SYNCKIT_TIMEOUT } = process.env
12
17
13
18
beforeEach ( ( ) => {
14
19
jest . resetModules ( )
20
+ jest . restoreAllMocks ( )
15
21
16
22
delete process . env . SYNCKIT_GLOBAL_SHIMS
17
23
@@ -104,6 +110,114 @@ test('timeout', async () => {
104
110
)
105
111
} )
106
112
113
+ test ( 'subsequent executions after timeout' , async ( ) => {
114
+ const executionTimeout = 30
115
+ const longRunningTaskDuration = executionTimeout * 10
116
+ process . env . SYNCKIT_TIMEOUT = executionTimeout . toString ( )
117
+
118
+ const { createSyncFn } = await import ( 'synckit' )
119
+ const syncFn = createSyncFn < AsyncWorkerFn > ( workerCjsPath )
120
+
121
+ // start an execution in worker that will definitely time out
122
+ expect ( ( ) => syncFn ( 1 , longRunningTaskDuration ) ) . toThrow ( )
123
+
124
+ // wait for timed out execution to finish inside worker
125
+ await new Promise ( resolve => setTimeout ( resolve , longRunningTaskDuration ) )
126
+
127
+ // subsequent executions should work correctly
128
+ expect ( syncFn ( 2 , 1 ) ) . toBe ( 2 )
129
+ expect ( syncFn ( 3 , 1 ) ) . toBe ( 3 )
130
+ } )
131
+
132
+ test ( 'handling of outdated message from worker' , async ( ) => {
133
+ const executionTimeout = 60
134
+ process . env . SYNCKIT_TIMEOUT = executionTimeout . toString ( )
135
+ const receiveMessageOnPortMock = await setupReceiveMessageOnPortMock ( )
136
+
137
+ jest . spyOn ( Atomics , 'wait' ) . mockReturnValue ( 'ok' )
138
+
139
+ receiveMessageOnPortMock
140
+ . mockReturnValueOnce ( { message : { id : - 1 } } )
141
+ . mockReturnValueOnce ( { message : { id : 0 , result : 1 } } )
142
+
143
+ const { createSyncFn } = await import ( 'synckit' )
144
+ const syncFn = createSyncFn < AsyncWorkerFn > ( workerCjsPath )
145
+ expect ( syncFn ( 1 ) ) . toBe ( 1 )
146
+ expect ( receiveMessageOnPortMock ) . toHaveBeenCalledTimes ( 2 )
147
+ } )
148
+
149
+ test ( 'propagation of undefined timeout' , async ( ) => {
150
+ delete process . env . SYNCKIT_TIMEOUT
151
+ const receiveMessageOnPortMock = await setupReceiveMessageOnPortMock ( )
152
+
153
+ const atomicsWaitSpy = jest . spyOn ( Atomics , 'wait' ) . mockReturnValue ( 'ok' )
154
+
155
+ receiveMessageOnPortMock
156
+ . mockReturnValueOnce ( { message : { id : - 1 } } )
157
+ . mockReturnValueOnce ( { message : { id : 0 , result : 1 } } )
158
+
159
+ const { createSyncFn } = await import ( 'synckit' )
160
+ const syncFn = createSyncFn < AsyncWorkerFn > ( workerCjsPath )
161
+ expect ( syncFn ( 1 ) ) . toBe ( 1 )
162
+ expect ( receiveMessageOnPortMock ) . toHaveBeenCalledTimes ( 2 )
163
+
164
+ const [ firstAtomicsWaitArgs , secondAtomicsWaitArgs ] =
165
+ atomicsWaitSpy . mock . calls
166
+ const [ , , , firstAtomicsWaitCallTimeout ] = firstAtomicsWaitArgs
167
+ const [ , , , secondAtomicsWaitCallTimeout ] = secondAtomicsWaitArgs
168
+
169
+ expect ( typeof firstAtomicsWaitCallTimeout ) . toBe ( 'undefined' )
170
+ expect ( typeof secondAtomicsWaitCallTimeout ) . toBe ( 'undefined' )
171
+ } )
172
+
173
+ test ( 'reduction of waiting time' , async ( ) => {
174
+ const synckitTimeout = 60
175
+ process . env . SYNCKIT_TIMEOUT = synckitTimeout . toString ( )
176
+ const receiveMessageOnPortMock = await setupReceiveMessageOnPortMock ( )
177
+
178
+ const atomicsWaitSpy = jest . spyOn ( Atomics , 'wait' ) . mockImplementation ( ( ) => {
179
+ const start = Date . now ( )
180
+ // simulate waiting 10ms for worker to respond
181
+ while ( Date . now ( ) - start < 10 ) {
182
+ continue
183
+ }
184
+
185
+ return 'ok'
186
+ } )
187
+
188
+ receiveMessageOnPortMock
189
+ . mockReturnValueOnce ( { message : { id : - 1 } } )
190
+ . mockReturnValueOnce ( { message : { id : 0 , result : 1 } } )
191
+
192
+ const { createSyncFn } = await import ( 'synckit' )
193
+ const syncFn = createSyncFn < AsyncWorkerFn > ( workerCjsPath )
194
+ expect ( syncFn ( 1 ) ) . toBe ( 1 )
195
+ expect ( receiveMessageOnPortMock ) . toHaveBeenCalledTimes ( 2 )
196
+
197
+ const [ firstAtomicsWaitArgs , secondAtomicsWaitArgs ] =
198
+ atomicsWaitSpy . mock . calls
199
+ const [ , , , firstAtomicsWaitCallTimeout ] = firstAtomicsWaitArgs
200
+ const [ , , , secondAtomicsWaitCallTimeout ] = secondAtomicsWaitArgs
201
+
202
+ expect ( typeof firstAtomicsWaitCallTimeout ) . toBe ( 'number' )
203
+ expect ( firstAtomicsWaitCallTimeout ) . toBe ( synckitTimeout )
204
+ expect ( typeof secondAtomicsWaitCallTimeout ) . toBe ( 'number' )
205
+ expect ( secondAtomicsWaitCallTimeout ) . toBeLessThan ( synckitTimeout )
206
+ } )
207
+
208
+ test ( 'unexpected message from worker' , async ( ) => {
209
+ jest . spyOn ( Atomics , 'wait' ) . mockReturnValue ( 'ok' )
210
+
211
+ const receiveMessageOnPortMock = await setupReceiveMessageOnPortMock ( )
212
+ receiveMessageOnPortMock . mockReturnValueOnce ( { message : { id : 100 } } )
213
+
214
+ const { createSyncFn } = await import ( 'synckit' )
215
+ const syncFn = createSyncFn < AsyncWorkerFn > ( workerCjsPath )
216
+ expect ( ( ) => syncFn ( 1 ) ) . toThrow (
217
+ 'Internal error: Expected id 0 but got id 100' ,
218
+ )
219
+ } )
220
+
107
221
test ( 'globalShims env' , async ( ) => {
108
222
process . env . SYNCKIT_GLOBAL_SHIMS = '1'
109
223
0 commit comments