mirror of
https://github.com/leporello-js/leporello-js
synced 2026-01-14 05:14:28 -08:00
finishing record io
This commit is contained in:
116
src/record_io.js
116
src/record_io.js
@@ -1,7 +1,5 @@
|
||||
import {set_record_call} from './runtime.js'
|
||||
|
||||
// TODO remove all console.log
|
||||
|
||||
const get_object_to_patch = (cxt, path) => {
|
||||
let obj = cxt.window
|
||||
for(let i = 0; i < path.length - 1; i++) {
|
||||
@@ -22,33 +20,28 @@ const io_patch = (cxt, path, use_context = false) => {
|
||||
|
||||
const original = obj[method]
|
||||
obj[method] = function(...args) {
|
||||
// TODO if called from previous version of code (calltree_changed_token is
|
||||
// different), then do not call IO function and throw error to finish
|
||||
// previous run ASAP
|
||||
|
||||
// TODO remove
|
||||
/*
|
||||
console.error('patched method', name, {
|
||||
io_cache_is_recording: cxt.io_cache_is_recording,
|
||||
io_cache_is_replay_aborted: cxt.io_cache_is_replay_aborted,
|
||||
io_cache_index: cxt.io_cache_is_recording
|
||||
? cxt.io_cache.length
|
||||
: cxt.io_cache_index
|
||||
})
|
||||
*/
|
||||
|
||||
// TODO if called from prev execution, then throw to finish it
|
||||
// ASAP
|
||||
if(cxt.io_cache_is_replay_aborted) {
|
||||
// Try to finish fast
|
||||
throw new Error('io replay aborted')
|
||||
}
|
||||
|
||||
const has_new_target = new.target != null
|
||||
|
||||
if(cxt.is_recording_deferred_calls) {
|
||||
return has_new_target
|
||||
? new original(...args)
|
||||
: original.apply(this, args)
|
||||
}
|
||||
|
||||
if(cxt.io_cache_is_recording) {
|
||||
let ok, value, error
|
||||
const has_new_target = new.target != null
|
||||
try {
|
||||
// TODO. Do we need it here? Only need for IO calls view. And also
|
||||
// for expand_call and find_call, to not use cache on expand call
|
||||
// and find_call
|
||||
// save call, so on expand_call and find_call IO functions would not be
|
||||
// called.
|
||||
// TODO: we have a problem when IO function is called from third-party
|
||||
// lib and async context is lost
|
||||
set_record_call(cxt)
|
||||
|
||||
const index = cxt.io_cache.length
|
||||
@@ -57,27 +50,32 @@ const io_patch = (cxt, path, use_context = false) => {
|
||||
args = args.slice()
|
||||
// Patch callback
|
||||
const cb = args[0]
|
||||
args[0] = function() {
|
||||
// TODO guard calls from prev runs
|
||||
// TODO guard io_cache_is_replay_aborted
|
||||
args[0] = Object.defineProperty(function() {
|
||||
// TODO if called from prev execution, then throw to
|
||||
// finish it ASAP
|
||||
if(cxt.io_cache_is_replay_aborted) {
|
||||
// Non necessary
|
||||
return
|
||||
}
|
||||
cxt.io_cache.push({type: 'resolution', index})
|
||||
cb()
|
||||
}
|
||||
}, 'name', {value: cb.name})
|
||||
}
|
||||
|
||||
value = has_new_target
|
||||
? new original(...args)
|
||||
: original.apply(this, args)
|
||||
|
||||
// TODO remove
|
||||
//console.log('value', value)
|
||||
|
||||
if(value instanceof cxt.window.Promise) {
|
||||
// TODO use cxt.promise_then, not finally which calls
|
||||
// patched 'then'?
|
||||
value = value.finally(() => {
|
||||
// TODO guard calls from prev runs
|
||||
// TODO guard io_cache_is_replay_aborted
|
||||
// TODO if called from prev execution, then throw to
|
||||
// finish it ASAP
|
||||
if(cxt.io_cache_is_replay_aborted) {
|
||||
// Non necessary
|
||||
return
|
||||
}
|
||||
cxt.io_cache.push({type: 'resolution', index})
|
||||
})
|
||||
}
|
||||
@@ -106,65 +104,47 @@ const io_patch = (cxt, path, use_context = false) => {
|
||||
} else {
|
||||
const call = cxt.io_cache[cxt.io_cache_index]
|
||||
|
||||
/* TODO remove
|
||||
console.log(
|
||||
call.type != 'call'
|
||||
, call == null
|
||||
, call.has_new_target != (new.target != null)
|
||||
, call.use_context && (call.context != this)
|
||||
, call.name != name
|
||||
, JSON.stringify(call.args) != JSON.stringify(args)
|
||||
)
|
||||
*/
|
||||
|
||||
// TODO if call.type != 'call', and there are no more calls, should
|
||||
// we abort, or just record one more call?
|
||||
|
||||
// TODO if call == null or call.type == 'resolution', then do not discard
|
||||
// cache, instead switch to record mode and append new calls to the
|
||||
// cache?
|
||||
if(
|
||||
call == null
|
||||
|| call.type != 'call'
|
||||
|| call.has_new_target != (new.target != null)
|
||||
// TODO test
|
||||
|| call.has_new_target != has_new_target
|
||||
|| call.use_context && (call.context != this)
|
||||
|| call.name != name
|
||||
|| (
|
||||
// TODO for setTimeout, compare last arg (timeout)
|
||||
name != 'setTimeout'
|
||||
&&
|
||||
JSON.stringify(call.args) != JSON.stringify(args)
|
||||
(name == 'setTimeout' && (args[1] != call.args[1])) /* compares timeout*/
|
||||
||
|
||||
(
|
||||
name != 'setTimeout'
|
||||
&&
|
||||
JSON.stringify(call.args) != JSON.stringify(args)
|
||||
)
|
||||
)
|
||||
){
|
||||
//TODO remove console.error('DISCARD cache', call)
|
||||
cxt.io_cache_is_replay_aborted = true
|
||||
// Try to finish fast
|
||||
throw new Error('io replay aborted')
|
||||
} else {
|
||||
// TODO remove console.log('cached call found', call)
|
||||
|
||||
const next_resolution = cxt.io_cache.find((e, i) =>
|
||||
e.type == 'resolution' && i > cxt.io_cache_index
|
||||
)
|
||||
|
||||
if(next_resolution != null && !cxt.io_cache_resolver_is_set) {
|
||||
console.error('set resolver')
|
||||
const original_setTimeout = cxt.window.setTimeout.__original
|
||||
cxt.io_cache_resolver_is_set = true
|
||||
|
||||
original_setTimeout(() => {
|
||||
// TODO if called from prev execution, then throw to finish it ASAP
|
||||
|
||||
if(cxt.io_cache_is_replay_aborted) {
|
||||
console.error('RESOLVER ABORTED')
|
||||
return
|
||||
}
|
||||
|
||||
// TODO guard from previous run
|
||||
console.error('resolver', {
|
||||
io_cache_is_replay_aborted: cxt.io_cache_is_replay_aborted,
|
||||
io_cache_index: cxt.io_cache_index,
|
||||
})
|
||||
|
||||
cxt.io_cache_resolver_is_set = false
|
||||
|
||||
// TODO check if call from prev run
|
||||
|
||||
// Sanity check
|
||||
if(cxt.io_cache_index >= cxt.io_cache.length) {
|
||||
throw new Error('illegal state')
|
||||
@@ -172,7 +152,6 @@ const io_patch = (cxt, path, use_context = false) => {
|
||||
|
||||
const next_event = cxt.io_cache[cxt.io_cache_index]
|
||||
if(next_event.type == 'call') {
|
||||
// TODO Call not happened, replay?
|
||||
cxt.io_cache_is_replay_aborted = true
|
||||
} else {
|
||||
while(
|
||||
@@ -190,7 +169,6 @@ const io_patch = (cxt, path, use_context = false) => {
|
||||
} else {
|
||||
resolver(cxt.io_cache[resolution.index].value)
|
||||
}
|
||||
// TODO remove console.log('RESOLVE', cxt.io_cache_index, resolution.index)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,15 +221,20 @@ const Response_methods = [
|
||||
'text',
|
||||
]
|
||||
|
||||
// TODO bare IO functions should not be exposed at all, to allow calling it
|
||||
// only from patched versions. Especially setInterval which can cause leaks
|
||||
export const apply_io_patches = cxt => {
|
||||
io_patch(cxt, ['Math', 'random'])
|
||||
|
||||
io_patch(cxt, ['setTimeout'])
|
||||
// TODO test
|
||||
// TODO if call setTimeout and then clearTimeout, cache it and remove call of
|
||||
// clearTimeout, and make only setTimeout, then it would never be called when
|
||||
// replaying from cache
|
||||
io_patch(cxt, ['clearTimeout'])
|
||||
|
||||
|
||||
// TODO test
|
||||
// TODO patch setInterval to only cleanup all intervals on finish
|
||||
|
||||
const Date = cxt.window.Date
|
||||
io_patch(cxt, ['Date'])
|
||||
cxt.window.Date.parse = Date.parse
|
||||
@@ -273,7 +256,6 @@ export const remove_io_patches = cxt => {
|
||||
io_patch_remove(cxt, ['Math', 'random'])
|
||||
|
||||
io_patch_remove(cxt, ['setTimeout'])
|
||||
// TODO test
|
||||
io_patch_remove(cxt, ['clearTimeout'])
|
||||
|
||||
io_patch_remove(cxt, ['Date'])
|
||||
|
||||
Reference in New Issue
Block a user