Skip to content

Commit c6b3c0c

Browse files
committed
wip prototyping queue, tasks and timer
1 parent 5d90a03 commit c6b3c0c

42 files changed

Lines changed: 2617 additions & 11 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package-lock.json

Lines changed: 18 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
"pako": "^1.0.11",
106106
"prompts": "^2.4.1",
107107
"readable-stream": "^3.6.0",
108+
"real-cancellable-promise": "^1.1.1",
108109
"resource-counter": "^1.2.4",
109110
"threads": "^1.6.5",
110111
"utp-native": "^2.5.3",
@@ -117,7 +118,7 @@
117118
"@types/google-protobuf": "^3.7.4",
118119
"@types/jest": "^28.1.3",
119120
"@types/nexpect": "^0.4.31",
120-
"@types/node": "^16.11.7",
121+
"@types/node": "^16.11.49",
121122
"@types/node-forge": "^0.10.4",
122123
"@types/pako": "^1.0.2",
123124
"@types/prompts": "^2.0.13",
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// let's attempt the cancellable one as well
2+
// it requires the promise
3+
// we can avoid needing to use this in EFS for now
4+
// it's specific to PK

src/contexts/decorators/context.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as contextsUtils from '../utils';
2+
3+
/**
4+
* Context parameter decorator
5+
* It is only allowed to be used once
6+
*/
7+
function context(target: Object, key: string | symbol, index: number) {
8+
const targetName = (target['name'] ?? target.constructor.name);
9+
const method = target[key];
10+
if (contextsUtils.contexts.has(method)) {
11+
throw new TypeError(
12+
`\`${targetName}.${key.toString()}\` redeclares \`@context\` decorator`
13+
);
14+
}
15+
contextsUtils.contexts.set(method, index);
16+
}
17+
18+
export default context;

src/contexts/decorators/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export { default as context } from './context';
2+
// export { default as cancellable }, * from './cancellable';
3+
export { default as timed } from './timed';
4+
// export { default as transactional }, * from './transactional';

src/contexts/decorators/timed.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import * as contextsUtils from '../utils';
2+
import * as contextsErrors from '../errors';
3+
import Timer from '../../timer/Timer';
4+
import * as timerErrors from '../../timer/errors';
5+
import {
6+
AsyncFunction,
7+
GeneratorFunction,
8+
AsyncGeneratorFunction
9+
} from '../../utils';
10+
11+
/**
12+
* Timed method decorator
13+
*/
14+
function timed(delay: number = Infinity) {
15+
return (
16+
target: any,
17+
key: string | symbol,
18+
descriptor: TypedPropertyDescriptor<(...params: any[]) => any>
19+
): TypedPropertyDescriptor<(...params: any[]) => any> => {
20+
const targetName = (target['name'] ?? target.constructor.name);
21+
const f = descriptor['value'];
22+
if (typeof f !== 'function') {
23+
throw new TypeError(`\`${targetName}.${key.toString()}\` is not a function`);
24+
}
25+
const contextIndex = contextsUtils.contexts.get(target[key]);
26+
if (contextIndex == null) {
27+
throw new TypeError(`\`${targetName}.${key.toString()}\` does not have a \`@context\` parameter decorator`);
28+
}
29+
const wrap = (that: any, params: Array<any>) => {
30+
const context = params[contextIndex];
31+
if (context !== undefined && (typeof context !== 'object' || context === null)) {
32+
throw new TypeError(
33+
`\`${targetName}.${key.toString()}\` decorated \`@context\` parameter is not a context object`
34+
);
35+
}
36+
if (context?.timer !== undefined && !(context.timer instanceof Timer)) {
37+
throw new TypeError(
38+
`\`${targetName}.${key.toString()}\` decorated \`@context\` parameter's \`timer\` property is not an instance of \`Timer\``
39+
);
40+
}
41+
if (context?.signal !== undefined && !(context.signal instanceof AbortSignal)) {
42+
throw new TypeError(
43+
`\`${targetName}.${key.toString()}\` decorated \`@context\` parameter's \`signal\` property is not an instance of \`AbortSignal\``
44+
);
45+
}
46+
// Now `context: { timer: Timer | undefined; signal: AbortSignal | undefined } | undefined`
47+
if (
48+
context === undefined ||
49+
context.timer === undefined && context.signal === undefined
50+
) {
51+
const abortController = new AbortController();
52+
const timer = new Timer({
53+
delay,
54+
handler: () => void abortController.abort(new contextsErrors.ErrorContextsTimerExpired)
55+
});
56+
params[contextIndex] = (context !== undefined) ? context : {};
57+
params[contextIndex].signal = abortController.signal;
58+
params[contextIndex].timer = timer;
59+
const result = f.apply(that, params);
60+
timer.catch((e) => {
61+
// Ignore cancellation
62+
if (!(e instanceof timerErrors.ErrorTimerCancelled)) {
63+
throw e;
64+
}
65+
});
66+
timer.cancel();
67+
return result;
68+
} else if (
69+
context.timer === undefined &&
70+
context.signal instanceof AbortSignal
71+
) {
72+
const abortController = new AbortController();
73+
const timer = new Timer({
74+
delay,
75+
handler: () => void abortController.abort(new contextsErrors.ErrorContextsTimerExpired)
76+
});
77+
context.signal.onabort = () => void abortController.abort(context.signal.reason);
78+
params[contextIndex].signal = abortController.signal;
79+
params[contextIndex].timer = timer;
80+
const result = f.apply(that, params);
81+
timer.catch((e) => {
82+
// Ignore cancellation
83+
if (!(e instanceof timerErrors.ErrorTimerCancelled)) {
84+
throw e;
85+
}
86+
});
87+
timer.cancel();
88+
return result;
89+
} else if (
90+
context.timer instanceof Timer &&
91+
context.signal === undefined
92+
) {
93+
const abortController = new AbortController();
94+
context.timer.then(() => void abortController.abort(new contextsErrors.ErrorContextsTimerExpired));
95+
params[contextIndex].signal = abortController.signal;
96+
return f.apply(that, params);
97+
} else if (
98+
context.timer instanceof Timer && context.signal instanceof AbortSignal
99+
) {
100+
return f.apply(that, params);
101+
}
102+
};
103+
if (f instanceof AsyncFunction) {
104+
descriptor['value'] = async function (...params) {
105+
return wrap(this, params);
106+
};
107+
} else if (f instanceof GeneratorFunction) {
108+
descriptor['value'] = function* (...params) {
109+
return yield* wrap(this, params);
110+
};
111+
} else if (f instanceof AsyncGeneratorFunction) {
112+
descriptor['value'] = async function* (...params) {
113+
return yield* wrap(this, params);
114+
};
115+
} else {
116+
descriptor['value'] = function (...params) {
117+
return wrap(this, params);
118+
};
119+
}
120+
// Preserve the name
121+
Object.defineProperty(descriptor['value'], 'name', {
122+
value: (typeof key === 'symbol') ? `[${key.description}]` : key
123+
});
124+
return descriptor;
125+
};
126+
}
127+
128+
export default timed;

src/contexts/decorators/transactional.ts

Whitespace-only changes.

src/contexts/errors.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { ErrorPolykey, sysexits } from '../errors';
2+
3+
class ErrorContexts<T> extends ErrorPolykey<T> {}
4+
5+
class ErrorContextsTimerExpired<T> extends ErrorContexts<T> {
6+
static description = 'Aborted due to timer expiration';
7+
exitCode = sysexits.UNAVAILABLE;
8+
}
9+
10+
export {
11+
ErrorContexts,
12+
ErrorContextsTimerExpired
13+
};

src/contexts/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from './decorators';
2+
export * from './utils';
3+
export * as types from './types';
4+
export * as errors from './errors';

src/contexts/types.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { DBTransaction } from '@matrixai/db';
2+
import type Timer from '../timer/Timer';
3+
4+
type ContextCancellable = {
5+
signal: AbortSignal;
6+
};
7+
8+
type ContextTimed = ContextCancellable & {
9+
timer: Timer;
10+
};
11+
12+
type ContextTransactional = {
13+
tran: DBTransaction;
14+
};
15+
16+
export type {
17+
ContextCancellable,
18+
ContextTimed,
19+
ContextTransactional
20+
};

0 commit comments

Comments
 (0)