From 4118bc63d2f9c71959b2d22207b48db88cd04af5 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilev Date: Sat, 15 Jul 2023 17:59:58 +0300 Subject: [PATCH] fix record io bug --- src/record_io.js | 8 +++++--- src/runtime.js | 53 +++++++++++++++++++++++++++++++----------------- test/test.js | 24 ++++++++++++++++++++++ 3 files changed, 63 insertions(+), 22 deletions(-) diff --git a/src/record_io.js b/src/record_io.js index d98dc54..874ae78 100644 --- a/src/record_io.js +++ b/src/record_io.js @@ -154,8 +154,10 @@ const make_patched_method = (original, name, use_context) => { ) ){ cxt.io_trace_is_replay_aborted = true - // Try to finish fast - // TODO invoke callback to notify that code must be restarted? + cxt.io_trace_abort_replay() + // throw error to prevent further code execution. It + // is not necesseary, becuase execution would not have + // any effects anyway const error = new Error('io replay aborted') error.__ignore = true throw error @@ -188,7 +190,7 @@ const make_patched_method = (original, name, use_context) => { const next_event = cxt.io_trace[cxt.io_trace_index] if(next_event.type == 'call') { cxt.io_trace_is_replay_aborted = true - // TODO reject? Test for never resolved + cxt.io_trace_abort_replay() } else { while( cxt.io_trace_index < cxt.io_trace.length diff --git a/src/runtime.js b/src/runtime.js index 07072c9..13e311b 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -29,9 +29,18 @@ const gen_to_promise = gen_fn => { } } +const make_promise_with_rejector = cxt => { + let rejector + const p = new cxt.window.Promise(r => rejector = r) + return [p, rejector] +} + const do_run = function*(module_fns, cxt, io_trace){ let calltree + const [replay_aborted_promise, io_trace_abort_replay] = + make_promise_with_rejector(cxt) + cxt = (io_trace == null || io_trace.length == 0) // TODO group all io_trace_ properties to single object? ? {...cxt, @@ -46,32 +55,38 @@ const do_run = function*(module_fns, cxt, io_trace){ // Map of (index in io_trace) -> resolve io_trace_resolvers: new Map(), io_trace_index: 0, + io_trace_abort_replay, } apply_promise_patch(cxt) set_current_context(cxt) for(let {module, fn} of module_fns) { - cxt.found_call = null - cxt.children = null - calltree = { - toplevel: true, - module, - id: cxt.call_counter++ - } + cxt.found_call = null + cxt.children = null + calltree = { + toplevel: true, + module, + id: cxt.call_counter++ + } - try { - cxt.modules[module] = {} - yield fn(cxt, __trace, __trace_call, __do_await) - calltree.ok = true - } catch(error) { - calltree.ok = false - calltree.error = error - } - calltree.children = cxt.children - if(!calltree.ok) { - break - } + try { + cxt.modules[module] = {} + const result = fn(cxt, __trace, __trace_call, __do_await) + if(result instanceof cxt.window.Promise) { + yield cxt.window.Promise.race([replay_aborted_promise, result]) + } else { + yield result + } + calltree.ok = true + } catch(error) { + calltree.ok = false + calltree.error = error + } + calltree.children = cxt.children + if(!calltree.ok) { + break + } } cxt.is_recording_deferred_calls = true diff --git a/test/test.js b/test/test.js index c734e7b..350853c 100644 --- a/test/test.js +++ b/test/test.js @@ -3416,4 +3416,28 @@ const y = x()` 'object', ) }), + + test('record io hangs bug', async () => { + patch_builtin( + 'fetch', + () => new Promise(resolve => original_setTimeout(resolve, 0)) + ) + + const code = ` + const p = fetch('') + Math.random() + await p + ` + + const i = await test_initial_state_async(code) + + assert_equal(i.io_trace.length, 3) + + const next_code = `await fetch('')` + + const state = await command_input_async(i, next_code, 0) + assert_equal(state.io_trace.length, 2) + + patch_builtin('fetch', null) + }), ]