(timeoutMs: number = 100)
| 295 | |
| 296 | return { |
| 297 | read(timeoutMs: number = 100): ArrayBuffer | null { |
| 298 | const MAX_CAS_RETRIES = 3; |
| 299 | |
| 300 | for (let attempt = 0; attempt < MAX_CAS_RETRIES; attempt++) { |
| 301 | const shutdownFlag = Atomics.load(controlView, CONTROL_SHUTDOWN); |
| 302 | if (shutdownFlag) { |
| 303 | return null; |
| 304 | } |
| 305 | |
| 306 | const readIdx = Atomics.load(controlView, CONTROL_READ_INDEX); |
| 307 | const slotMetaIdx = |
| 308 | (metadataOffset + readIdx * METADATA_ENTRY_SIZE) / 4; |
| 309 | |
| 310 | let state = Atomics.load(metadataView, slotMetaIdx + META_SLOT_STATE); |
| 311 | |
| 312 | if (state !== SLOT_STATE.READY) { |
| 313 | const waitResult = Atomics.wait( |
| 314 | metadataView, |
| 315 | slotMetaIdx + META_SLOT_STATE, |
| 316 | state, |
| 317 | timeoutMs, |
| 318 | ); |
| 319 | if (waitResult === "timed-out") { |
| 320 | return null; |
| 321 | } |
| 322 | |
| 323 | const shutdownCheck = Atomics.load(controlView, CONTROL_SHUTDOWN); |
| 324 | if (shutdownCheck) { |
| 325 | return null; |
| 326 | } |
| 327 | |
| 328 | state = Atomics.load(metadataView, slotMetaIdx + META_SLOT_STATE); |
| 329 | if (state !== SLOT_STATE.READY) { |
| 330 | continue; |
| 331 | } |
| 332 | } |
| 333 | |
| 334 | const exchangedState = Atomics.compareExchange( |
| 335 | metadataView, |
| 336 | slotMetaIdx + META_SLOT_STATE, |
| 337 | SLOT_STATE.READY, |
| 338 | SLOT_STATE.READING, |
| 339 | ); |
| 340 | if (exchangedState !== SLOT_STATE.READY) { |
| 341 | continue; |
| 342 | } |
| 343 | |
| 344 | const frameSize = Atomics.load( |
| 345 | metadataView, |
| 346 | slotMetaIdx + META_FRAME_SIZE, |
| 347 | ); |
| 348 | const slotDataOffset = dataOffset + readIdx * slotSize; |
| 349 | |
| 350 | if ( |
| 351 | !Number.isInteger(frameSize) || |
| 352 | frameSize < 0 || |
| 353 | frameSize > slotSize || |
| 354 | slotDataOffset < 0 || |
no test coverage detected