import * as asyncHooks from "async_hooks" ; import EnvoyContext from "./envoy-context" ; /**
 * this will store the information of a node
 */ export class NodeInfo { /**
     * the reference count of this info,
     * init will be 1,
     * when this async execution trigger another execution,
     *  this will increase 1,
     * when this execution or one of its child execution is destroyed,
     *  this will decrease 1
     */ referenceCount = 1 ; /**
     * who trigger this async execution
     */ readonly triggerAsyncId : number ; /**
     * the context set in this execution
     */ context ? : EnvoyContext = undefined ; constructor ( triggerAsyncId ) { this . triggerAsyncId = triggerAsyncId ; } } /**
 * Eliminate Store
 * using two map for context storage, to avoid holding too many data
 * it will eliminate the old data
 */ export class EliminateStore { private old = new Map < number , NodeInfo > ( ) ; private current = new Map < number , NodeInfo > ( ) ; private lastEliminateTime = Date . now ( ) ; param asyncId get ( asyncId ) { const infoFromCurrent = this . current . get ( asyncId ) ; if ( infoFromCurrent !== undefined ) { return infoFromCurrent ; } const infoFromOld = this . old . get ( asyncId ) ; if ( infoFromOld !== undefined ) { this . current . set ( asyncId , infoFromOld ) ; } return infoFromOld ; } param asyncId param info set ( asyncId , info ) { this . current . set ( asyncId , info ) ; } param asyncId delete ( asyncId ) { this . current . delete ( asyncId ) ; this . old . delete ( asyncId ) ; } /**
     * clear all data
     */ clear ( ) { this . old . clear ( ) ; this . current . clear ( ) ; } /**
     * eliminate the old data
     */ eliminate ( ) { this . old = this . current ; this . current = new Map < number , NodeInfo > ( ) ; this . lastEliminateTime = Date . now ( ) ; } /**
     * get last eliminate time
     */ getLastEliminateTime ( ) { return this . lastEliminateTime ; } /**
     * the current size
     */ size ( ) { return this . current . size ; } /**
     * the old store size
     */ oldSize ( ) { return this . old . size ; } } const store = new EliminateStore ( ) ; let enabled = false ; let eliminateInterval = 300 * 1000 ; let notDestroyedHooks = 0 ; param interval function setEliminateInterval ( interval ) { eliminateInterval = interval ; } /**
 * get eliminate interval
 */ function getEliminateInterval ( ) { return eliminateInterval ; } param asyncId function storeCleanUp ( asyncId ) { const info = store . get ( asyncId ) ; if ( info === undefined ) { return ; } info . referenceCount -- ; if ( info . referenceCount === 0 ) { store . delete ( asyncId ) ; storeCleanUp ( info . triggerAsyncId ) ; } } const asyncHook = asyncHooks . createHook ( { init ( asyncId , type , triggerAsyncId , resource ) { notDestroyedHooks ++ ; if ( Date . now ( ) - store . getLastEliminateTime ( ) > eliminateInterval ) { store . eliminate ( ) ; } let triggerInfo = store . get ( triggerAsyncId ) ; if ( ! triggerInfo ) { triggerInfo = new NodeInfo ( - 1 ) ; store . set ( triggerAsyncId , triggerInfo ) ; } triggerInfo . referenceCount ++ ; const info = new NodeInfo ( triggerAsyncId ) ; store . set ( asyncId , info ) ; } , destroy ( asyncId ) { notDestroyedHooks -- ; storeCleanUp ( asyncId ) ; } , } ) ; /**
 * Enable the context store,
 * you should call this function as early as possible,
 * i.e. put it in your application's start.
 */ function enable ( ) { if ( ! enabled ) { asyncHook . enable ( ) ; enabled = true ; } else { console . trace ( "[envoy-node] You want to enable the enabled store" ) ; } } /**
 * Disable the context store,
 * all data will be clean up as well.
 * This function is not intended to be call in the application life cycle.
 */ function disable ( ) { if ( enabled ) { asyncHook . disable ( ) ; store . clear ( ) ; enabled = false ; } else { console . trace ( "[envoy-node] You want to disable the disabled store" ) ; } } function markContext ( triggerAsyncId , context ) { const triggerInfo = store . get ( triggerAsyncId ) ; if ( triggerInfo === undefined ) { return ; } if ( triggerInfo . context ) { return ; } triggerInfo . context = context ; markContext ( triggerInfo . triggerAsyncId , context ) ; } param context function set ( context ) { if ( ! enabled ) { console . trace ( "[envoy-node] cannot set context when store is not enabled." ) ; return ; } const asyncId = asyncHooks . executionAsyncId ( ) ; const info = store . get ( asyncId ) ; if ( info === undefined ) { console . trace ( "[envoy-node] Cannot find info of current execution, have you enabled the context store correctly?" ) ; return ; } info . context = context ; markContext ( info . triggerAsyncId , context ) ; } param asyncId function getContext ( asyncId ) { const info = store . get ( asyncId ) ; if ( ! info ) { return undefined ; } if ( ! info . context ) { info . context = getContext ( info . triggerAsyncId ) ; } return info . context ; } /**
 * get the context previous set in the store of the current execution
 */ function get ( ) { if ( ! enabled ) { console . trace ( "[envoy-node] cannot get context when store is not enabled." ) ; return undefined ; } const asyncId = asyncHooks . executionAsyncId ( ) ; const context = getContext ( asyncId ) ; if ( context === undefined ) { console . trace ( "[envoy-node] Cannot find info of current execution, have you enabled and set the context store correctly?" ) ; } return context ; } /**
 * return if the store is enabled
 */ function isEnabled ( ) { return enabled ; } /**
 * return the instance of the store
 */ function getStoreImpl ( ) { return store ; } /**
 * return debug info about the store
 */ function getDebugInfo ( ) { return `current size: ${ store . size ( ) }, old size: ${ store . oldSize ( ) }, not destroyed hooks: ${ notDestroyedHooks }` ; } export default { enable , disable , set , get , isEnabled , getStoreImpl , getEliminateInterval , setEliminateInterval , getDebugInfo , } ;