most of the deadlock issue is fixed -- need revision and cleanup...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2026-07-25 01:32:52 +03:00
parent 1411a2cf6d
commit 0257273be6

View File

@ -647,31 +647,15 @@ module.BaseParser = {
&& (body = this.expand(page, body, state, nested_handlers)) && (body = this.expand(page, body, state, nested_handlers))
// call macro... // call macro...
// XXX since this can be async and if so it will be mixed
// into the next .wait*, we'll need to explicitly pass
// and then thread the current .wait* promises before
// overwriting, to avoid wait-for-self deadlocks...
// ...the question is how to thread it...
// one way to do this is to split state into:
// state - current state of eval, mutable for all
// context - the context of evaluation of the curent
// macro, unique per macro, this would
// also be good to store things like
// line numbers and the like...
// ways to organize this:
// - context.state - nesting
// - state, context - sepatrate args
// - state.__proto__ - protorype
// ...technically need a way to call .exec(..) and wait
// for the correct promise from inside a macro...
//* XXX LOCAL_STATE //* XXX LOCAL_STATE
// XXX think this is the best way to go, but needs the rest // NOTE: here we separate local macro state and global/parent
// of the code refactored + still need to experiment... // state via the prototpype...
// XXX part of the problem is that this mechanism is not used // This is mainly needed to sparate promises, since the
// later (.resolve(..) and .finalize(..)) which creates // inicial execution is done on order of occurenca while
// the same sync problems there too -- need to unify // each macro can wait async for an arbitrary amount of
// both mechanisms... // time it only should care about what was promised
var res = that.callMacro( page, name, args, body, { // before it neglecting what came after.
var res = that.callMacro(page, name, args, body, {
// global state... // global state...
__proto__: state, __proto__: state,
state, state,
@ -707,9 +691,13 @@ module.BaseParser = {
res.then( res.then(
function(value){ function(value){
if(state.waitAll === all){ if(state.waitAll === all){
delete state.waitAll } // XXX LOCAL_STATE_CLEANUP
state.waitAll = undefined }
//delete state.waitAll }
if(state.waitNested === nested){ if(state.waitNested === nested){
delete state.waitNested } // XXX LOCAL_STATE_CLEANUP
state.waitNested = undefined }
//delete state.waitNested }
delete elem.resolving delete elem.resolving
elem.value = value elem.value = value
// XXX should we resolve to value or elem??? // XXX should we resolve to value or elem???
@ -734,11 +722,15 @@ module.BaseParser = {
.then(function(){ .then(function(){
// only cleanup our own mess =) // only cleanup our own mess =)
waitAll === state.waitAll waitAll === state.waitAll
&& (delete state.waitAll) }) // XXX LOCAL_STATE_CLEANUP
&& (state.waitAll = undefined) })
//&& (delete state.waitAll) })
&& (wait = state.wait = waitAll && (wait = state.wait = waitAll
.then(function(){ .then(function(){
wait === state.wait wait === state.wait
&& (delete state.wait) // XXX LOCAL_STATE_CLEANUP
&& (state.wait = null)
//&& (delete state.wait)
return elems })) return elems }))
var waitNested = state.waitNested var waitNested = state.waitNested
state.waitNested state.waitNested
@ -746,7 +738,9 @@ module.BaseParser = {
.then(function(){ .then(function(){
// only cleanup our own mess =) // only cleanup our own mess =)
waitNested === state.waitNested waitNested === state.waitNested
&& (delete state.waitNested) }) // XXX LOCAL_STATE_CLEANUP
&& (state.waitNested = undefined) })
//&& (delete state.waitNested) })
return elems }, return elems },
@ -767,6 +761,29 @@ module.BaseParser = {
// XXX can we prevent reaces over state.unresolved??? // XXX can we prevent reaces over state.unresolved???
// it can be deleted when calling .exec(..) / .execNested(..) // it can be deleted when calling .exec(..) / .execNested(..)
// while parsing, for example from within a macro... // while parsing, for example from within a macro...
// XXX this somehow blocks the execution of .expand(..)
// This fullly runs:
// .expand(.., '@echo(A)@source(/async/echo)@echo(B)@include(/async/echo)@echo(C)', ..)
// printing:
// ---- A
// -- A
// ---- B
// ---- C
// ---- Page
// -- Page
// ---- Page
// -- B
// -- Page
// -- C
// This blocks:
// .resolve(.., '@echo(A)@source(/async/echo)@echo(B)@include(/async/echo)@echo(C)', ..)
// printing:
// ---- A
// -- A
// ---- B
// ---- C
// ...looks like we are still getting a deadlock...
// -> the issue seems to be in a nested call to .execNested(..) -> .finalize(..)
resolve: function(page, ast, state={}, nested_handlers={}){ resolve: function(page, ast, state={}, nested_handlers={}){
var that = this var that = this
ast = typeof(ast) != 'object' ? ast = typeof(ast) != 'object' ?
@ -777,6 +794,7 @@ module.BaseParser = {
// XXX LOCAL_STATE // XXX LOCAL_STATE
//state.unresolved = [] //state.unresolved = []
var unresolved = []
// merge resolved elements into the last item of elems... // merge resolved elements into the last item of elems...
var elems = [] var elems = []
@ -790,6 +808,11 @@ module.BaseParser = {
Promise.awaitOrRun( Promise.awaitOrRun(
// if not everything is resolved, delay the stage II // if not everything is resolved, delay the stage II
// callbacks till .wait is done... // callbacks till .wait is done...
// NOTE: this depends on that JS is single thread
// and we can't have state.wait resolve in
// the middle of this loop.
// NOTE: waiting promises resolving is also done in
// FIFO orderm thus maintaining order of execution
state.wait, state.wait,
function(){ function(){
return elem = e.value = return elem = e.value =
@ -814,7 +837,10 @@ module.BaseParser = {
// nested macro with no value set -- skip... // nested macro with no value set -- skip...
if(that.macros[elem.name] instanceof Array){ if(that.macros[elem.name] instanceof Array){
continue } continue }
state.unresolved // XXX LOCAL_STATE...
// ...use the same mechanism here as in .expand(..)
//state.unresolved
unresolved
.push(elem.resolving instanceof Promise ? .push(elem.resolving instanceof Promise ?
elem.resolving elem.resolving
: elem) : elem)
@ -823,6 +849,14 @@ module.BaseParser = {
// are the responsibility of the respective macros... // are the responsibility of the respective macros...
elems.push(elem) } elems.push(elem) }
var resolving =
state.wait =
Promise.all([state.wait, ...unresolved])
// cleanup...
.then(function(){
if(state.wait === resolving){
delete state.wait } })
return elems }, return elems },
isResolved: function(ast){ isResolved: function(ast){
@ -838,6 +872,7 @@ module.BaseParser = {
// Merge and apply global filters (stage III)... // Merge and apply global filters (stage III)...
// //
// - ensure the ast is fully resolved // - ensure the ast is fully resolved
// resolve and re-resolve untill all done
// - apply stage III pre handlers // - apply stage III pre handlers
// - apply global filters // - apply global filters
// - apply stage III post handlers // - apply stage III post handlers
@ -858,25 +893,33 @@ module.BaseParser = {
.flat() } .flat() }
var resolve = function(ast){ var resolve = function(ast){
return Promise.awaitOrRun( return Promise.awaitOrRun(
...(state.unresolved ?? []), // XXX LOCAL_STATE
//...(state.unresolved ?? []),
state.wait,
function(){ function(){
delete state.unresolved //delete state.unresolved
// re-resolve... // re-resolve...
ast = that.resolve(page, ast, state, nested_handlers) ast = that.resolve(page, ast, state, nested_handlers)
// NOTE: this is essentially running in the same frame // NOTE: this is essentially running in the same frame
// as .resolve(..) above so there should not be // as .resolve(..) above so there should not be
// any races to delete .unresolved... // any races to delete .unresolved...
return state.unresolved ? //return state.unresolved ?
//return state.hasOwnProperty('wait') ?
return !that.isResolved(ast) ?
resolve(ast) resolve(ast)
: ast }) } : ast }) }
ast = this.resolve(page, ast, state, nested_handlers) ast = this.resolve(page, ast, state, nested_handlers)
return Promise.awaitOrRun( return Promise.awaitOrRun(
// XXX LOCAL_STATE
// XXX do we actually need to wait here??? // XXX do we actually need to wait here???
// ...each macro should already be waiting... // ...each macro should already be waiting...
...[state[wait]].flat(), //...[state[wait]].flat(),
state.hasOwnProperty(wait) ?
state[wait]
: null,
function(){ function(){
// NOTE: in an async world where any promised macro can // NOTE: in an async world where any promised macro can
// call .exec(..) / .execNested(..) we can't trust // call .exec(..) / .execNested(..) we can't trust
@ -884,8 +927,10 @@ module.BaseParser = {
return Promise.awaitOrRun( return Promise.awaitOrRun(
// XXX LOCAL_STATE if we are nested we should not wait // XXX LOCAL_STATE if we are nested we should not wait
// for anything after the caller... // for anything after the caller...
(state.unresolved //(state.unresolved
|| !that.isResolved(ast)) ? // || !that.isResolved(ast)) ?
//state.hasOwnProperty('wait') ?
!that.isResolved(ast) ?
resolve(ast) resolve(ast)
: ast, : ast,
function(ast){ function(ast){
@ -907,6 +952,7 @@ module.BaseParser = {
// XXX // XXX
execNested: function(page, ast, state={}, nested_handlers={}){ execNested: function(page, ast, state={}, nested_handlers={}){
//return ast
/* XXX LOCAL_STATE waitNested here deadlocks the parser -- not sure why... /* XXX LOCAL_STATE waitNested here deadlocks the parser -- not sure why...
//return this.exec(page, ast, state, nested_handlers, 'unresolved') }, //return this.exec(page, ast, state, nested_handlers, 'unresolved') },
return this.exec(page, ast, state, nested_handlers, 'waitNested') }, return this.exec(page, ast, state, nested_handlers, 'waitNested') },
@ -998,7 +1044,7 @@ module.parser = {
console.log(['----', ...Object.keys(args), body ?? ''].join(' ').gray) console.log(['----', ...Object.keys(args), body ?? ''].join(' ').gray)
return Promise.awaitOrRun( return Promise.awaitOrRun(
// XXX this works and sequences correctly... // XXX this works and sequences correctly...
//state.waitNested, state.waitNested,
// XXX this deadlocks... // XXX this deadlocks...
// calling .execNested(..) from any macro (@include(..) // calling .execNested(..) from any macro (@include(..)
// in the example code) will deadlock the execution // in the example code) will deadlock the execution
@ -1008,7 +1054,7 @@ module.parser = {
// - run: // - run:
// @echo(A)@source(/async/echo)@echo(B)@include(/async/echo)@echo(C) // @echo(A)@source(/async/echo)@echo(B)@include(/async/echo)@echo(C)
// -> need a way to call .exex(..) while evaluating... // -> need a way to call .exex(..) while evaluating...
this.execNested(page, '', state), //this.execNested(page, '', state),
// XXX this also does not work... // XXX this also does not work...
//this.resolve(page, '', state), //this.resolve(page, '', state),
function(){ function(){
@ -1150,7 +1196,7 @@ module.parser = {
if(!name){ if(!name){
return '' } return '' }
/* XXX LOCAL_STATE //* XXX LOCAL_STATE
var vars = state.state.vars ??= {} var vars = state.state.vars ??= {}
/*/ /*/
var vars = state.vars ??= {} var vars = state.vars ??= {}
@ -1299,7 +1345,7 @@ module.parser = {
var that = this var that = this
var name = args.name var name = args.name
/* XXX LOCAL_STATE //* XXX LOCAL_STATE
var slots = state.state.slots ??= {} var slots = state.state.slots ??= {}
/*/ /*/
var slots = state.slots ??= {} var slots = state.slots ??= {}
@ -1662,7 +1708,7 @@ module.parser = {
function(page, args, body, state){ function(page, args, body, state){
var that = this var that = this
/* XXX LOCAL_STATE //* XXX LOCAL_STATE
var macros = state.state.macros ??= {} var macros = state.state.macros ??= {}
/*/ /*/
var macros = state.macros ??= {} var macros = state.macros ??= {}