fix: fix parallel child process execution
This commit is contained in:
parent
e9025efa22
commit
748cb6f8f5
@ -3,7 +3,8 @@
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "yarn g:tsc -p tsconfig.build.json",
|
||||
"clean": "rm -rf dist",
|
||||
"build": "yarn clean && yarn g:tsc -p tsconfig.build.json",
|
||||
"dev": "yarn g:tsc -p tsconfig.build.json --watch",
|
||||
"lint": "yarn g:lint .",
|
||||
"publish:lib": "node scripts/publish.js"
|
||||
|
||||
@ -11,7 +11,7 @@ import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take';
|
||||
import { getCallParameters, getContractIssuer } from 'lib/onchain';
|
||||
|
||||
import { ExecutorMethodArgs, ResolvedOp } from '../../types';
|
||||
import { pushAwaitedTask } from '../promisifiedFeatures';
|
||||
import { pushAwaitedTask } from '../runtime';
|
||||
|
||||
import { extractOps } from './extractOps';
|
||||
|
||||
|
||||
@ -1,69 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Context, NewTx, getMethodArguments, isSelfCall } from '@coinweb/contract-kit';
|
||||
|
||||
import { Task } from '../types';
|
||||
|
||||
import { context, getRawContext, handleContext } from './context';
|
||||
import { getAwaitedTasks } from './promisifiedFeatures';
|
||||
import { pushResolvedOp } from './promisifiedFeatures/runtime/resolvedOps';
|
||||
import { constructTx } from './utils';
|
||||
|
||||
let abortExecution: ((result: { isFullyExecuted: boolean; awaitedTasks: Task[] }) => void) | null = null;
|
||||
|
||||
export const executor = (method: (...args: any[]) => Promise<void>) => {
|
||||
return async (ctx: Context): Promise<NewTx[]> => {
|
||||
console.log('executor-start >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>');
|
||||
const shouldRestart = handleContext(ctx);
|
||||
pushResolvedOp(context.ops);
|
||||
|
||||
if (getMethodArguments(getRawContext()).length > 2 && !isSelfCall(ctx)) {
|
||||
throw new Error('Wrong contract call, check the call arguments');
|
||||
}
|
||||
|
||||
if (shouldRestart) {
|
||||
const awaitedTasks = getAwaitedTasks();
|
||||
|
||||
return constructTx(awaitedTasks, false);
|
||||
}
|
||||
|
||||
const execution = new Promise<{ isFullyExecuted: boolean; awaitedTasks: Task[] }>((resolve, reject) => {
|
||||
abortExecution = resolve;
|
||||
|
||||
method(...context.initialArgs).then(
|
||||
() => {
|
||||
console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< executor-resolved');
|
||||
const awaitedTasks = getAwaitedTasks();
|
||||
resolve({ isFullyExecuted: true, awaitedTasks });
|
||||
},
|
||||
(error) => {
|
||||
console.log(' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<executor-rejected');
|
||||
console.log(error);
|
||||
reject(error);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
//@ts-expect-error
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
os.setTimeout(() => {
|
||||
const awaitedTasks = getAwaitedTasks();
|
||||
abortExecution?.({ isFullyExecuted: false, awaitedTasks });
|
||||
}, 0);
|
||||
|
||||
try {
|
||||
const { isFullyExecuted, awaitedTasks } = await execution;
|
||||
|
||||
return constructTx(awaitedTasks, isFullyExecuted);
|
||||
} catch (error) {
|
||||
console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< executor-error');
|
||||
console.log((error as Error).message);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const stopExecution = () => {
|
||||
console.log('stopExecution');
|
||||
const awaitedTasks = getAwaitedTasks();
|
||||
abortExecution?.({ isFullyExecuted: false, awaitedTasks });
|
||||
};
|
||||
@ -1,8 +1,10 @@
|
||||
import { Task } from '../../../types';
|
||||
import { getAwaitedTasks } from '../../runtime';
|
||||
|
||||
import { prepareTx } from './prepareTxs';
|
||||
|
||||
export const constructTx = (awaitedTasks: Task[], isFullyExecuted: boolean) => {
|
||||
export const constructTx = (isFullyExecuted: boolean) => {
|
||||
const awaitedTasks = getAwaitedTasks();
|
||||
|
||||
const { calls, txFee } = prepareTx({ awaitedTasks, isFullyExecuted, txFee: 0n });
|
||||
|
||||
const { txs } = prepareTx({ awaitedTasks, isFullyExecuted, txFee, callsCount: calls });
|
||||
@ -22,8 +22,8 @@ import {
|
||||
isPreparedStoreOp,
|
||||
isPreparedTakeOp,
|
||||
isPreparedUnlockOp,
|
||||
} from '../typeGuards';
|
||||
import { uuid } from '../uuid';
|
||||
} from '../../utils/opTypeGuards';
|
||||
import { uuid } from '../../utils/uuid';
|
||||
|
||||
export const prepareInThreadTxs = ({
|
||||
cwebPerCall,
|
||||
@ -18,7 +18,7 @@ import {
|
||||
isPreparedStoreOp,
|
||||
isPreparedTakeOp,
|
||||
isPreparedUnlockOp,
|
||||
} from '../typeGuards';
|
||||
} from '../../utils/opTypeGuards';
|
||||
|
||||
export const prepareOutThreadTxs = ({
|
||||
ops,
|
||||
42
packages/cwait/src/onchain/executor/executor.ts
Normal file
42
packages/cwait/src/onchain/executor/executor.ts
Normal file
@ -0,0 +1,42 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Context, NewTx, getMethodArguments, isSelfCall } from '@coinweb/contract-kit';
|
||||
|
||||
import { context, getRawContext, handleContext } from '../context';
|
||||
import { execLoop, setNextExec, stopExecution } from '../runtime';
|
||||
import { pushResolvedOp } from '../runtime/resolvedOps';
|
||||
|
||||
import { constructTx } from './constructTx';
|
||||
|
||||
export const executor = (method: (...args: any[]) => Promise<void>) => {
|
||||
return async (ctx: Context): Promise<NewTx[]> => {
|
||||
console.log('executor-start >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>');
|
||||
const shouldRestart = handleContext(ctx);
|
||||
pushResolvedOp(context.ops);
|
||||
|
||||
if (getMethodArguments(getRawContext()).length > 2 && !isSelfCall(ctx)) {
|
||||
throw new Error('Wrong contract call, check the call arguments');
|
||||
}
|
||||
|
||||
if (shouldRestart) {
|
||||
console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< executor-finish');
|
||||
return constructTx(false);
|
||||
}
|
||||
|
||||
//@ts-expect-error
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
os.setTimeout(() => {
|
||||
stopExecution();
|
||||
}, 0);
|
||||
|
||||
try {
|
||||
setNextExec(() => method(...context.initialArgs));
|
||||
const isFullyExecuted = await execLoop();
|
||||
console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< executor-finish');
|
||||
return constructTx(isFullyExecuted);
|
||||
} catch (error) {
|
||||
console.log((error as Error).message);
|
||||
console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< executor-error');
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
};
|
||||
1
packages/cwait/src/onchain/executor/index.ts
Normal file
1
packages/cwait/src/onchain/executor/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './executor';
|
||||
@ -2,10 +2,9 @@ import { ContractHandlers as ContractHandlersOrig, SELF_REGISTER_HANDLER_NAME }
|
||||
import { selfRegisterHandler } from '@coinweb/self-register';
|
||||
import { queue } from 'lib/onchain';
|
||||
|
||||
import { addMethodHandler, ContractHandlers, executeHandler } from '../contract-kit';
|
||||
|
||||
import { executor } from './executor';
|
||||
import { mutexMethods } from './mutex';
|
||||
import { addMethodHandler, ContractHandlers, executeHandler } from '../../../contract-kit';
|
||||
import { executor } from '../../executor';
|
||||
import { mutexMethods } from '../../mutex';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const constructCwebMain = (methods: Record<string, (...args: any[]) => Promise<void>>) => async () => {
|
||||
@ -0,0 +1 @@
|
||||
export * from './constructCwebMain';
|
||||
@ -1,12 +1,23 @@
|
||||
import { constructStore } from '@coinweb/contract-kit/dist/esm/operations/store';
|
||||
|
||||
import { constructResultClaim } from '../../../claims/result';
|
||||
import { stopExecution } from '../../../executor';
|
||||
import { opMarker } from '../../../global';
|
||||
import { isResolvedChildOp, isResolvedExecOp, isResolvedSlotOp } from '../../../utils';
|
||||
import { uuid } from '../../../utils';
|
||||
import { getAwaitedTasksCount, pushAwaitedTask } from '../../runtime/awaitedTasks';
|
||||
import { getUsedOps, saveUsedOps, shiftResolvedOp } from '../../runtime/resolvedOps';
|
||||
import { constructResultClaim } from '../../claims/result';
|
||||
import { opMarker } from '../../globals/promise';
|
||||
import { setNextExec, stopExecution } from '../../runtime';
|
||||
import {
|
||||
freezeAwaitedTasks,
|
||||
getAwaitedTasksCount,
|
||||
pushAwaitedTask,
|
||||
unfreezeAwaitedTasks,
|
||||
} from '../../runtime/awaitedTasks';
|
||||
import {
|
||||
freezeResolvedOps,
|
||||
getUsedOps,
|
||||
saveUsedOps,
|
||||
shiftResolvedOp,
|
||||
unfreezeResolvedOps,
|
||||
} from '../../runtime/resolvedOps';
|
||||
import { isResolvedChildOp, isResolvedExecOp, isResolvedSlotOp } from '../../utils';
|
||||
import { uuid } from '../../utils';
|
||||
|
||||
let isRootDetected = false;
|
||||
|
||||
@ -44,13 +55,21 @@ export const cwait = <TAsyncCallback extends (...args: any[]) => Promise<unknown
|
||||
}
|
||||
|
||||
if (isResolvedExecOp(op)) {
|
||||
saveUsedOps();
|
||||
freezeAwaitedTasks();
|
||||
freezeResolvedOps();
|
||||
|
||||
asyncCallback(...args);
|
||||
setNextExec(async () => {
|
||||
unfreezeAwaitedTasks();
|
||||
unfreezeResolvedOps();
|
||||
|
||||
if (!getAwaitedTasksCount()) {
|
||||
pushAwaitedTask(constructStore(constructResultClaim(op.ExecOp.id, getUsedOps())));
|
||||
}
|
||||
saveUsedOps();
|
||||
|
||||
await asyncCallback(...args);
|
||||
|
||||
if (!getAwaitedTasksCount()) {
|
||||
pushAwaitedTask(constructStore(constructResultClaim(op.ExecOp.id, getUsedOps())));
|
||||
}
|
||||
});
|
||||
|
||||
stopExecution();
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export * from './cwait';
|
||||
export * from './mutex';
|
||||
export * from './ops';
|
||||
export * from './constructCwebMain';
|
||||
@ -1,8 +1,8 @@
|
||||
import { getTime } from 'lib/onchain';
|
||||
|
||||
import { opMarker } from '../../../global';
|
||||
import { LockedKey } from '../../../mutex';
|
||||
import { isResolvedLockOp, isResolvedSlotOp, isResolvedUnlockOp, uuid } from '../../../utils';
|
||||
import { opMarker } from '../../globals/promise';
|
||||
import { LockedKey } from '../../mutex';
|
||||
import { isResolvedLockOp, isResolvedSlotOp, isResolvedUnlockOp, uuid } from '../../utils';
|
||||
import { pushAwaitedTask, shiftResolvedOp } from '../../runtime';
|
||||
|
||||
const unlock = (lockId: string, timestamp: number) => {
|
||||
@ -1,7 +1,7 @@
|
||||
import { BlockFilter, constructBlock, extractBlock } from '@coinweb/contract-kit';
|
||||
|
||||
import { opMarker } from '../../../global';
|
||||
import { isResolvedBlockOp, isResolvedSlotOp } from '../../../utils';
|
||||
import { opMarker } from '../../globals/promise';
|
||||
import { isResolvedBlockOp, isResolvedSlotOp } from '../../utils';
|
||||
import { pushAwaitedTask, shiftResolvedOp } from '../../runtime';
|
||||
|
||||
export const blockOp = (filters: BlockFilter[]) => {
|
||||
@ -1,10 +1,10 @@
|
||||
import { Claim, ClaimKey, constructRangeRead, extractRead } from '@coinweb/contract-kit';
|
||||
import { ClaimRange } from '@coinweb/contract-kit/dist/types/operations/read';
|
||||
|
||||
import { TypedClaim } from '../../../../types';
|
||||
import { context } from '../../../context';
|
||||
import { opMarker } from '../../../global';
|
||||
import { isResolvedReadOp, isResolvedSlotOp } from '../../../utils';
|
||||
import { TypedClaim } from '../../../types';
|
||||
import { context } from '../../context';
|
||||
import { opMarker } from '../../globals/promise';
|
||||
import { isResolvedReadOp, isResolvedSlotOp } from '../../utils';
|
||||
import { pushAwaitedTask, shiftResolvedOp } from '../../runtime';
|
||||
|
||||
export const rangeReadOp = <TClaims extends Claim[] = TypedClaim[]>(
|
||||
@ -1,9 +1,9 @@
|
||||
import { Claim, ClaimKey, constructRead, extractRead } from '@coinweb/contract-kit';
|
||||
|
||||
import { TypedClaim } from '../../../../types';
|
||||
import { context } from '../../../context';
|
||||
import { opMarker } from '../../../global';
|
||||
import { isResolvedReadOp, isResolvedSlotOp } from '../../../utils';
|
||||
import { TypedClaim } from '../../../types';
|
||||
import { context } from '../../context';
|
||||
import { opMarker } from '../../globals/promise';
|
||||
import { isResolvedReadOp, isResolvedSlotOp } from '../../utils';
|
||||
import { pushAwaitedTask, shiftResolvedOp } from '../../runtime';
|
||||
|
||||
export const readOp = <TClaim extends Claim = TypedClaim>(key: ClaimKey) => {
|
||||
@ -1,8 +1,8 @@
|
||||
import { Claim, constructStore } from '@coinweb/contract-kit';
|
||||
import { extractStore } from '@coinweb/contract-kit/dist/esm/operations/store';
|
||||
|
||||
import { opMarker } from '../../../global';
|
||||
import { isResolvedSlotOp, isResolvedStoreOp } from '../../../utils';
|
||||
import { opMarker } from '../../globals/promise';
|
||||
import { isResolvedSlotOp, isResolvedStoreOp } from '../../utils';
|
||||
import { pushAwaitedTask, shiftResolvedOp } from '../../runtime';
|
||||
|
||||
export const storeOp = (claim: Claim, storeCweb?: bigint) => {
|
||||
@ -1,8 +1,8 @@
|
||||
import { Claim, ClaimKey, constructTake, extractTake } from '@coinweb/contract-kit';
|
||||
|
||||
import { TypedClaim } from '../../../../types';
|
||||
import { opMarker } from '../../../global';
|
||||
import { isResolvedSlotOp, isResolvedTakeOp } from '../../../utils';
|
||||
import { TypedClaim } from '../../../types';
|
||||
import { opMarker } from '../../globals/promise';
|
||||
import { isResolvedSlotOp, isResolvedTakeOp } from '../../utils';
|
||||
import { pushAwaitedTask, shiftResolvedOp } from '../../runtime';
|
||||
|
||||
export const takeOp = <TClaim extends Claim = TypedClaim>(key: ClaimKey) => {
|
||||
1
packages/cwait/src/onchain/globals/index.ts
Normal file
1
packages/cwait/src/onchain/globals/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './promise';
|
||||
@ -1,5 +1,8 @@
|
||||
import { markTaskBatch } from './promisifiedFeatures/runtime/awaitedTasks';
|
||||
import { getBatchId } from './utils';
|
||||
import { markTaskBatch } from '../runtime/awaitedTasks';
|
||||
|
||||
let batchId = 0;
|
||||
|
||||
const getBatchId = () => batchId++;
|
||||
|
||||
export const opMarker = Symbol('opMarker');
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
export * from './context';
|
||||
export * from './promisifiedFeatures/features';
|
||||
export * from './constructCwebMain';
|
||||
import './globals';
|
||||
|
||||
export * from './features';
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
export * from './runtime';
|
||||
export * from './features';
|
||||
@ -1,8 +1,13 @@
|
||||
import { PreparedOp, Task } from '../../../types';
|
||||
import { PreparedOp, Task } from '../../types';
|
||||
|
||||
const awaitedTasks: Task[] = [];
|
||||
let isFreezed = false;
|
||||
|
||||
export const pushAwaitedTask = (op: PreparedOp) => {
|
||||
if (isFreezed) {
|
||||
return;
|
||||
}
|
||||
|
||||
awaitedTasks.push({ op, batchId: -1 });
|
||||
};
|
||||
|
||||
@ -15,3 +20,11 @@ export const markTaskBatch = (count: number, batchId: number) => {
|
||||
};
|
||||
|
||||
export const getAwaitedTasksCount = () => awaitedTasks.length;
|
||||
|
||||
export const freezeAwaitedTasks = () => {
|
||||
isFreezed = true;
|
||||
};
|
||||
|
||||
export const unfreezeAwaitedTasks = () => {
|
||||
isFreezed = false;
|
||||
};
|
||||
41
packages/cwait/src/onchain/runtime/execLoop.ts
Normal file
41
packages/cwait/src/onchain/runtime/execLoop.ts
Normal file
@ -0,0 +1,41 @@
|
||||
let abortExecution: ((isFullyExecuted: boolean) => void) | null = null;
|
||||
|
||||
export const stopExecution = (isFullyExecuted = false) => {
|
||||
console.log('stopExecution');
|
||||
abortExecution?.(isFullyExecuted);
|
||||
};
|
||||
|
||||
type ExecTask = () => Promise<void>;
|
||||
|
||||
let execQueue: ExecTask[] = [];
|
||||
export const setNextExec = (task: ExecTask) => (execQueue = [task]);
|
||||
|
||||
export const execLoop = async (): Promise<boolean> => {
|
||||
const nextExec = execQueue.pop();
|
||||
|
||||
if (nextExec) {
|
||||
const execution = new Promise<boolean>((resolve, reject) => {
|
||||
abortExecution = resolve;
|
||||
|
||||
nextExec().then(
|
||||
() => {
|
||||
resolve(true);
|
||||
},
|
||||
(error) => {
|
||||
console.log(error);
|
||||
reject(error);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const isFullyExecuted = await execution;
|
||||
|
||||
if (isFullyExecuted) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return execLoop();
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
@ -1,2 +1,3 @@
|
||||
export * from './awaitedTasks';
|
||||
export * from './execLoop';
|
||||
export * from './resolvedOps';
|
||||
@ -1,11 +1,16 @@
|
||||
import { ResolvedOp } from '../../../types';
|
||||
import { ResolvedOp } from '../../types';
|
||||
|
||||
const resolvedOps: ResolvedOp[] = [];
|
||||
|
||||
let usedOps: ResolvedOp[] = [];
|
||||
let isSavingUsed = false;
|
||||
let isFreezed = false;
|
||||
|
||||
export const pushResolvedOp = (op: ResolvedOp | ResolvedOp[]) => {
|
||||
if (isFreezed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(op)) {
|
||||
resolvedOps.push(...op);
|
||||
} else {
|
||||
@ -15,8 +20,8 @@ export const pushResolvedOp = (op: ResolvedOp | ResolvedOp[]) => {
|
||||
|
||||
export const shiftResolvedOp = () => {
|
||||
const result = {
|
||||
isOp: resolvedOps.length > 0,
|
||||
op: resolvedOps.shift(),
|
||||
isOp: resolvedOps.length > 0 && !isFreezed,
|
||||
op: isFreezed ? undefined : resolvedOps.shift(),
|
||||
} as
|
||||
| {
|
||||
isOp: true;
|
||||
@ -40,3 +45,11 @@ export const saveUsedOps = () => {
|
||||
usedOps = [];
|
||||
isSavingUsed = true;
|
||||
};
|
||||
|
||||
export const freezeResolvedOps = () => {
|
||||
isFreezed = true;
|
||||
};
|
||||
|
||||
export const unfreezeResolvedOps = () => {
|
||||
isFreezed = false;
|
||||
};
|
||||
@ -1,3 +0,0 @@
|
||||
let batchId = 0;
|
||||
|
||||
export const getBatchId = () => batchId++;
|
||||
@ -1,10 +0,0 @@
|
||||
export const getStack = ({ skip = 0 }: { skip?: number } = {}) =>
|
||||
new Error().stack
|
||||
?.split('\n')
|
||||
.slice(2 + skip)
|
||||
.map((line) => {
|
||||
const match = line.match(/at\s+([^\s(]+)/);
|
||||
return match ? match[1] : '';
|
||||
})
|
||||
.filter((name) => name && name !== 'Promise')
|
||||
.join('@') || '';
|
||||
@ -1,5 +1,2 @@
|
||||
export * from './batchId';
|
||||
export * from './callstack';
|
||||
export * from './constructTx';
|
||||
export * from './typeGuards';
|
||||
export * from './opTypeGuards';
|
||||
export * from './uuid';
|
||||
|
||||
@ -9,5 +9,5 @@
|
||||
"esModuleInterop": true,
|
||||
"types": ["vitest/globals"]
|
||||
},
|
||||
"include": ["**/*.ts", "vitest.*.ts", "__tests__", "scripts/publish.js", "src/onchain/utils"]
|
||||
"include": ["**/*.ts", "vitest.*.ts", "__tests__", "scripts/publish.js", "src/onchain/utils", "src/onchain/executor/constructTx"]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user