@dxos/context 0.5.3-main.d7fe7b5 → 0.5.3-main.e76d664
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/index.mjs +18 -48
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +18 -48
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/types/src/context.d.ts +7 -10
- package/dist/types/src/context.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/context.ts +24 -48
|
@@ -25,23 +25,25 @@ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/common/context/src/cont
|
|
|
25
25
|
var MAX_SAFE_DISPOSE_CALLBACKS = 300;
|
|
26
26
|
var _a, _b;
|
|
27
27
|
var Context = class _Context {
|
|
28
|
-
constructor({
|
|
28
|
+
constructor({ onError = (error) => {
|
|
29
29
|
if (error instanceof ContextDisposedError) {
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
32
|
void this.dispose();
|
|
33
33
|
throw error;
|
|
34
|
-
} } = {}) {
|
|
34
|
+
}, attributes = {}, parent } = {}) {
|
|
35
35
|
this._disposeCallbacks = [];
|
|
36
36
|
this._isDisposed = false;
|
|
37
37
|
this._disposePromise = void 0;
|
|
38
|
+
this._parent = null;
|
|
38
39
|
this.maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
|
|
39
40
|
this[_a] = "Context";
|
|
40
41
|
this[_b] = () => this.toString();
|
|
41
|
-
this._name = name;
|
|
42
|
-
this._parent = parent;
|
|
43
|
-
this._attributes = attributes;
|
|
44
42
|
this._onError = onError;
|
|
43
|
+
this._attributes = attributes;
|
|
44
|
+
if (parent !== void 0) {
|
|
45
|
+
this._parent = parent;
|
|
46
|
+
}
|
|
45
47
|
}
|
|
46
48
|
static {
|
|
47
49
|
_a = Symbol.toStringTag, _b = inspect.custom;
|
|
@@ -72,7 +74,7 @@ var Context = class _Context {
|
|
|
72
74
|
} catch (error) {
|
|
73
75
|
log.catch(error, void 0, {
|
|
74
76
|
F: __dxlog_file,
|
|
75
|
-
L:
|
|
77
|
+
L: 88,
|
|
76
78
|
S: this,
|
|
77
79
|
C: (f, a) => f(...a)
|
|
78
80
|
});
|
|
@@ -81,11 +83,12 @@ var Context = class _Context {
|
|
|
81
83
|
}
|
|
82
84
|
this._disposeCallbacks.push(callback);
|
|
83
85
|
if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {
|
|
84
|
-
log.warn("Context has a large number of dispose callbacks
|
|
85
|
-
count: this._disposeCallbacks.length
|
|
86
|
+
log.warn("Context has a large number of dispose callbacks. This might be a memory leak.", {
|
|
87
|
+
count: this._disposeCallbacks.length,
|
|
88
|
+
safeThreshold: this.maxSafeDisposeCallbacks
|
|
86
89
|
}, {
|
|
87
90
|
F: __dxlog_file,
|
|
88
|
-
L:
|
|
91
|
+
L: 95,
|
|
89
92
|
S: this,
|
|
90
93
|
C: (f, a) => f(...a)
|
|
91
94
|
});
|
|
@@ -104,7 +107,7 @@ var Context = class _Context {
|
|
|
104
107
|
* It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.
|
|
105
108
|
* Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.
|
|
106
109
|
*/
|
|
107
|
-
async dispose(
|
|
110
|
+
async dispose() {
|
|
108
111
|
if (this._disposePromise) {
|
|
109
112
|
return this._disposePromise;
|
|
110
113
|
}
|
|
@@ -115,52 +118,19 @@ var Context = class _Context {
|
|
|
115
118
|
});
|
|
116
119
|
const callbacks = Array.from(this._disposeCallbacks).reverse();
|
|
117
120
|
this._disposeCallbacks.length = 0;
|
|
118
|
-
if (this._name) {
|
|
119
|
-
log("disposing", {
|
|
120
|
-
context: this._name,
|
|
121
|
-
count: callbacks.length
|
|
122
|
-
}, {
|
|
123
|
-
F: __dxlog_file,
|
|
124
|
-
L: 139,
|
|
125
|
-
S: this,
|
|
126
|
-
C: (f, a) => f(...a)
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
let i = 0;
|
|
130
|
-
let clean = true;
|
|
131
121
|
for (const callback of callbacks) {
|
|
132
122
|
try {
|
|
133
123
|
await callback();
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
log.catch(err, {
|
|
137
|
-
context: this._name,
|
|
138
|
-
callback: i,
|
|
139
|
-
count: callbacks.length
|
|
140
|
-
}, {
|
|
124
|
+
} catch (error) {
|
|
125
|
+
log.catch(error, void 0, {
|
|
141
126
|
F: __dxlog_file,
|
|
142
|
-
L:
|
|
127
|
+
L: 136,
|
|
143
128
|
S: this,
|
|
144
129
|
C: (f, a) => f(...a)
|
|
145
130
|
});
|
|
146
|
-
clean = false;
|
|
147
|
-
if (throwOnError) {
|
|
148
|
-
throw err;
|
|
149
|
-
}
|
|
150
131
|
}
|
|
151
132
|
}
|
|
152
|
-
resolveDispose(
|
|
153
|
-
if (this._name) {
|
|
154
|
-
log("disposed", {
|
|
155
|
-
context: this._name
|
|
156
|
-
}, {
|
|
157
|
-
F: __dxlog_file,
|
|
158
|
-
L: 159,
|
|
159
|
-
S: this,
|
|
160
|
-
C: (f, a) => f(...a)
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
return clean;
|
|
133
|
+
resolveDispose();
|
|
164
134
|
}
|
|
165
135
|
/**
|
|
166
136
|
* Raise the error inside the context.
|
|
@@ -201,7 +171,7 @@ var Context = class _Context {
|
|
|
201
171
|
if (key in this._attributes) {
|
|
202
172
|
return this._attributes[key];
|
|
203
173
|
}
|
|
204
|
-
if (this._parent) {
|
|
174
|
+
if (this._parent !== null) {
|
|
205
175
|
return this._parent.getAttribute(key);
|
|
206
176
|
}
|
|
207
177
|
return void 0;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/context.ts", "../../../src/context-disposed-error.ts", "../../../src/promise-utils.ts", "../../../src/resource.ts"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2022 DXOS.org\n//\n\nimport { inspect } from 'node:util';\n\nimport { log } from '@dxos/log';\nimport { safeInstanceof } from '@dxos/util';\n\nimport { ContextDisposedError } from './context-disposed-error';\n\nexport type ContextErrorHandler = (error: Error) => void;\n\nexport type DisposeCallback = () => any | Promise<any>;\n\nexport type CreateContextParams = {\n name?: string;\n parent?: Context;\n attributes?: Record<string, any>;\n onError?: ContextErrorHandler;\n};\n\n/**\n * Maximum number of dispose callbacks before we start logging warnings.\n */\nconst MAX_SAFE_DISPOSE_CALLBACKS = 300;\n\n@safeInstanceof('Context')\nexport class Context {\n static default() {\n return new Context();\n }\n\n private readonly _disposeCallbacks: DisposeCallback[] = [];\n\n private readonly _name?: string;\n private readonly _parent?: Context;\n private readonly _attributes: Record<string, any>;\n private readonly _onError: ContextErrorHandler;\n\n private _isDisposed = false;\n private _disposePromise?: Promise<boolean> = undefined;\n\n public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;\n\n constructor({\n name, // TODO(burdon): Automate?\n parent,\n attributes = {},\n onError = (error) => {\n if (error instanceof ContextDisposedError) {\n return;\n }\n\n void this.dispose();\n\n // Will generate an unhandled rejection.\n throw error;\n },\n }: CreateContextParams = {}) {\n this._name = name;\n this._parent = parent;\n this._attributes = attributes;\n this._onError = onError;\n }\n\n get disposed() {\n return this._isDisposed;\n }\n\n get disposeCallbacksLength() {\n return this._disposeCallbacks.length;\n }\n\n /**\n * Schedules a callback to run when the context is disposed.\n * May be async, in this case the disposer might choose to wait for all resource to released.\n * Throwing an error inside the callback will result in the error being logged, but not re-thrown.\n *\n * NOTE: Will call the callback immediately if the context is already disposed.\n *\n * @returns A function that can be used to remove the callback from the dispose list.\n */\n onDispose(callback: DisposeCallback): () => void {\n if (this._isDisposed) {\n // Call the callback immediately if the context is already disposed.\n void (async () => {\n try {\n await callback();\n } catch (error: any) {\n log.catch(error);\n }\n })();\n }\n\n this._disposeCallbacks.push(callback);\n if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {\n log.warn('Context has a large number of dispose callbacks (this might be a memory leak).', {\n count: this._disposeCallbacks.length,\n });\n }\n\n // Remove handler.\n return () => {\n const index = this._disposeCallbacks.indexOf(callback);\n if (index !== -1) {\n this._disposeCallbacks.splice(index, 1);\n }\n };\n }\n\n /**\n * Runs all dispose callbacks.\n * Callbacks are run in the reverse order they were added.\n * This function never throws.\n * It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.\n * Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.\n */\n async dispose(throwOnError = false): Promise<boolean> {\n if (this._disposePromise) {\n return this._disposePromise;\n }\n\n // TODO(burdon): Probably should not be set until the dispose is complete, but causes tests to fail if moved.\n this._isDisposed = true;\n\n // Set the promise before running the callbacks.\n let resolveDispose!: (value: boolean) => void;\n this._disposePromise = new Promise<boolean>((resolve) => {\n resolveDispose = resolve;\n });\n\n // Process last first.\n // Clone the array so that any mutations to the original array don't affect the dispose process.\n const callbacks = Array.from(this._disposeCallbacks).reverse();\n this._disposeCallbacks.length = 0;\n\n if (this._name) {\n log('disposing', { context: this._name, count: callbacks.length });\n }\n\n let i = 0;\n let clean = true;\n for (const callback of callbacks) {\n try {\n await callback();\n i++;\n } catch (err: any) {\n log.catch(err, { context: this._name, callback: i, count: callbacks.length });\n clean = false;\n if (throwOnError) {\n throw err;\n }\n }\n }\n\n resolveDispose(clean);\n if (this._name) {\n log('disposed', { context: this._name });\n }\n\n return clean;\n }\n\n /**\n * Raise the error inside the context.\n * The error will be propagated to the error handler.\n * IF the error handler is not set, the error will dispose the context and cause an unhandled rejection.\n */\n raise(error: Error): void {\n if (this._isDisposed) {\n // TODO(dmaretskyi): Don't log those.\n // log.warn('Error in disposed context', error);\n return;\n }\n\n try {\n this._onError(error);\n } catch (err) {\n // Generate an unhandled rejection and stop the error propagation.\n void Promise.reject(err);\n }\n }\n\n derive({ onError, attributes }: CreateContextParams = {}): Context {\n const newCtx = new Context({\n // TODO(dmaretskyi): Optimize to not require allocating a new closure for every context.\n onError: async (error) => {\n if (!onError) {\n this.raise(error);\n } else {\n try {\n await onError(error);\n } catch {\n this.raise(error);\n }\n }\n },\n attributes,\n });\n\n const clearDispose = this.onDispose(() => newCtx.dispose());\n newCtx.onDispose(clearDispose);\n return newCtx;\n }\n\n getAttribute(key: string): any {\n if (key in this._attributes) {\n return this._attributes[key];\n }\n if (this._parent) {\n return this._parent.getAttribute(key);\n }\n\n return undefined;\n }\n\n [Symbol.toStringTag] = 'Context';\n [inspect.custom] = () => this.toString();\n\n toString() {\n return `Context(${this._isDisposed ? 'disposed' : 'active'})`;\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nexport class ContextDisposedError extends Error {\n constructor() {\n super('Context disposed.');\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { type Context } from './context';\nimport { ContextDisposedError } from './context-disposed-error';\n\n/**\n * @returns A promise that rejects when the context is disposed.\n */\n// TODO(dmaretskyi): Memory leak.\nexport const rejectOnDispose = (ctx: Context, error = new ContextDisposedError()): Promise<never> =>\n new Promise((resolve, reject) => {\n ctx.onDispose(() => reject(error));\n });\n\n/**\n * Rejects the promise if the context is disposed.\n */\nexport const cancelWithContext = <T>(ctx: Context, promise: Promise<T>): Promise<T> => {\n let clearDispose: () => void;\n return Promise.race([\n promise,\n new Promise<never>((resolve, reject) => {\n // Will be called before .finally() handlers.\n clearDispose = ctx.onDispose(() => reject(new ContextDisposedError()));\n }),\n ]).finally(() => clearDispose?.());\n};\n", "//\n// Copyright 2024 DXOS.org\n//\n\nimport '@dxos/util';\n\nimport { Context } from './context';\n\nexport enum LifecycleState {\n CLOSED = 'CLOSED',\n OPEN = 'OPEN',\n ERROR = 'ERROR',\n}\n\nexport interface Lifecycle {\n open?(ctx?: Context): Promise<any> | any;\n close?(): Promise<any> | any;\n}\n\n/**\n * Base class for resources that need to be opened and closed.\n */\nexport abstract class Resource implements Lifecycle {\n #lifecycleState = LifecycleState.CLOSED;\n #openPromise: Promise<void> | null = null;\n #closePromise: Promise<void> | null = null;\n\n /**\n * Managed internally by the resource.\n * Recreated on close.\n * Errors are propagated to the `_catch` method and the parent context.\n */\n #internalCtx: Context = this.#createContext();\n\n /**\n * Context that is used to bubble up errors that are not handled by the resource.\n * Provided in the open method.\n */\n #parentCtx: Context = new Context();\n\n protected get _lifecycleState() {\n return this.#lifecycleState;\n }\n\n protected get _ctx() {\n return this.#internalCtx;\n }\n\n /**\n * To be overridden by subclasses.\n */\n protected async _open(ctx: Context): Promise<void> {}\n\n /**\n * To be overridden by subclasses.\n */\n protected async _close(ctx: Context): Promise<void> {}\n\n /**\n * Error handler for errors that are caught by the context.\n * By default, errors are bubbled up to the parent context which is passed to the open method.\n */\n protected async _catch(err: Error): Promise<void> {\n throw err;\n }\n\n /**\n * Opens the resource.\n * If the resource is already open, it does nothing.\n * If the resource is in an error state, it throws an error.\n * If the resource is closed, it waits for it to close and then opens it.\n * @param ctx - Context to use for opening the resource. This context will receive errors that are not handled in `_catch`.\n */\n async open(ctx?: Context): Promise<this> {\n switch (this.#lifecycleState) {\n case LifecycleState.OPEN:\n return this;\n case LifecycleState.ERROR:\n throw new Error(`Invalid state: ${this.#lifecycleState}`);\n default:\n }\n\n await this.#closePromise;\n await (this.#openPromise ??= this.#open(ctx));\n\n return this;\n }\n\n /**\n * Closes the resource.\n * If the resource is already closed, it does nothing.\n */\n async close(ctx?: Context): Promise<this> {\n if (this.#lifecycleState === LifecycleState.CLOSED) {\n return this;\n }\n await this.#openPromise;\n await (this.#closePromise ??= this.#close(ctx));\n\n return this;\n }\n\n async [Symbol.asyncDispose]() {\n await this.close();\n }\n\n async #open(ctx?: Context) {\n this.#closePromise = null;\n if (ctx) {\n this.#parentCtx = ctx;\n }\n await this._open(this.#parentCtx);\n this.#lifecycleState = LifecycleState.OPEN;\n }\n\n async #close(ctx = new Context()) {\n this.#openPromise = null;\n await this.#internalCtx.dispose();\n await this._close(ctx);\n this.#internalCtx = this.#createContext();\n this.#lifecycleState = LifecycleState.CLOSED;\n }\n\n #createContext() {\n return new Context({\n onError: (error) =>\n queueMicrotask(async () => {\n try {\n await this._catch(error);\n } catch (err: any) {\n this.#lifecycleState = LifecycleState.ERROR;\n this.#parentCtx.raise(err);\n }\n }),\n });\n }\n}\n\nexport const openInContext = async <T extends Lifecycle>(ctx: Context, resource: T): Promise<T> => {\n await resource.open?.(ctx);\n ctx.onDispose(() => resource.close?.());\n\n return resource;\n};\n"],
|
|
5
|
-
"mappings": ";AAIA,SAASA,eAAe;AAExB,SAASC,WAAW;AACpB,SAASC,sBAAsB;;;ACHxB,IAAMC,uBAAN,cAAmCC,MAAAA;EACxCC,cAAc;AACZ,UAAM,mBAAA;EACR;AACF;;;;;;;;;;;;;;
|
|
6
|
-
"names": ["inspect", "log", "safeInstanceof", "ContextDisposedError", "Error", "constructor", "MAX_SAFE_DISPOSE_CALLBACKS", "Context", "constructor", "
|
|
4
|
+
"sourcesContent": ["//\n// Copyright 2022 DXOS.org\n//\n\nimport { inspect } from 'node:util';\n\nimport { log } from '@dxos/log';\nimport { safeInstanceof } from '@dxos/util';\n\nimport { ContextDisposedError } from './context-disposed-error';\n\nexport type ContextErrorHandler = (error: Error) => void;\n\nexport type DisposeCallback = () => any | Promise<any>;\n\nexport type CreateContextParams = {\n onError?: ContextErrorHandler;\n attributes?: Record<string, any>;\n parent?: Context;\n};\n\n/**\n * Maximum number of dispose callbacks before we start logging warnings.\n */\nconst MAX_SAFE_DISPOSE_CALLBACKS = 300;\n\n@safeInstanceof('Context')\nexport class Context {\n static default() {\n return new Context();\n }\n\n private readonly _onError: ContextErrorHandler;\n private readonly _disposeCallbacks: DisposeCallback[] = [];\n private _isDisposed = false;\n private _disposePromise?: Promise<void> = undefined;\n private _parent: Context | null = null;\n\n private _attributes: Record<string, any>;\n\n public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;\n\n constructor({\n onError = (error) => {\n if (error instanceof ContextDisposedError) {\n return;\n }\n\n void this.dispose();\n\n // Will generate an unhandled rejection.\n throw error;\n },\n attributes = {},\n parent,\n }: CreateContextParams = {}) {\n this._onError = onError;\n this._attributes = attributes;\n if (parent !== undefined) {\n this._parent = parent;\n }\n }\n\n get disposed() {\n return this._isDisposed;\n }\n\n get disposeCallbacksLength() {\n return this._disposeCallbacks.length;\n }\n\n /**\n * Schedules a callback to run when the context is disposed.\n * May be async, in this case the disposer might choose to wait for all resource to released.\n * Throwing an error inside the callback will result in the error being logged, but not re-thrown.\n *\n * NOTE: Will call the callback immediately if the context is already disposed.\n *\n * @returns A function that can be used to remove the callback from the dispose list.\n */\n onDispose(callback: DisposeCallback) {\n if (this._isDisposed) {\n // Call the callback immediately if the context is already disposed.\n void (async () => {\n try {\n await callback();\n } catch (error: any) {\n log.catch(error);\n }\n })();\n }\n\n this._disposeCallbacks.push(callback);\n if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {\n log.warn('Context has a large number of dispose callbacks. This might be a memory leak.', {\n count: this._disposeCallbacks.length,\n safeThreshold: this.maxSafeDisposeCallbacks,\n });\n }\n\n return () => {\n const index = this._disposeCallbacks.indexOf(callback);\n if (index !== -1) {\n this._disposeCallbacks.splice(index, 1);\n }\n };\n }\n\n /**\n * Runs all dispose callbacks.\n * Callbacks are run in the reverse order they were added.\n * This function never throws.\n * It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.\n * Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.\n */\n async dispose(): Promise<void> {\n if (this._disposePromise) {\n return this._disposePromise;\n }\n this._isDisposed = true;\n\n // Set the promise before running the callbacks.\n let resolveDispose!: () => void;\n this._disposePromise = new Promise<void>((resolve) => {\n resolveDispose = resolve;\n });\n\n // Clone the array so that any mutations to the original array don't affect the dispose process.\n const callbacks = Array.from(this._disposeCallbacks).reverse();\n this._disposeCallbacks.length = 0;\n\n for (const callback of callbacks) {\n try {\n await callback();\n } catch (error: any) {\n log.catch(error);\n }\n }\n resolveDispose();\n }\n\n /**\n * Raise the error inside the context.\n * The error will be propagated to the error handler.\n * IF the error handler is not set, the error will dispose the context and cause an unhandled rejection.\n */\n raise(error: Error): void {\n if (this._isDisposed) {\n // TODO(dmaretskyi): Don't log those.\n // log.warn('Error in disposed context', error);\n return;\n }\n\n try {\n this._onError(error);\n } catch (err) {\n // Generate an unhandled rejection and stop the error propagation.\n void Promise.reject(err);\n }\n }\n\n derive({ onError, attributes }: CreateContextParams = {}): Context {\n const newCtx = new Context({\n // TODO(dmaretskyi): Optimize to not require allocating a new closure for every context.\n onError: async (error) => {\n if (!onError) {\n this.raise(error);\n } else {\n try {\n await onError(error);\n } catch {\n this.raise(error);\n }\n }\n },\n attributes,\n });\n const clearDispose = this.onDispose(() => newCtx.dispose());\n newCtx.onDispose(clearDispose);\n return newCtx;\n }\n\n getAttribute(key: string): any {\n if (key in this._attributes) {\n return this._attributes[key];\n }\n if (this._parent !== null) {\n return this._parent.getAttribute(key);\n }\n return undefined;\n }\n\n [Symbol.toStringTag] = 'Context';\n\n [inspect.custom] = () => this.toString();\n\n toString() {\n return `Context(${this._isDisposed ? 'disposed' : 'active'})`;\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nexport class ContextDisposedError extends Error {\n constructor() {\n super('Context disposed.');\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { type Context } from './context';\nimport { ContextDisposedError } from './context-disposed-error';\n\n/**\n * @returns A promise that rejects when the context is disposed.\n */\n// TODO(dmaretskyi): Memory leak.\nexport const rejectOnDispose = (ctx: Context, error = new ContextDisposedError()): Promise<never> =>\n new Promise((resolve, reject) => {\n ctx.onDispose(() => reject(error));\n });\n\n/**\n * Rejects the promise if the context is disposed.\n */\nexport const cancelWithContext = <T>(ctx: Context, promise: Promise<T>): Promise<T> => {\n let clearDispose: () => void;\n return Promise.race([\n promise,\n new Promise<never>((resolve, reject) => {\n // Will be called before .finally() handlers.\n clearDispose = ctx.onDispose(() => reject(new ContextDisposedError()));\n }),\n ]).finally(() => clearDispose?.());\n};\n", "//\n// Copyright 2024 DXOS.org\n//\n\nimport '@dxos/util';\n\nimport { Context } from './context';\n\nexport enum LifecycleState {\n CLOSED = 'CLOSED',\n OPEN = 'OPEN',\n ERROR = 'ERROR',\n}\n\nexport interface Lifecycle {\n open?(ctx?: Context): Promise<any> | any;\n close?(): Promise<any> | any;\n}\n\n/**\n * Base class for resources that need to be opened and closed.\n */\nexport abstract class Resource implements Lifecycle {\n #lifecycleState = LifecycleState.CLOSED;\n #openPromise: Promise<void> | null = null;\n #closePromise: Promise<void> | null = null;\n\n /**\n * Managed internally by the resource.\n * Recreated on close.\n * Errors are propagated to the `_catch` method and the parent context.\n */\n #internalCtx: Context = this.#createContext();\n\n /**\n * Context that is used to bubble up errors that are not handled by the resource.\n * Provided in the open method.\n */\n #parentCtx: Context = new Context();\n\n protected get _lifecycleState() {\n return this.#lifecycleState;\n }\n\n protected get _ctx() {\n return this.#internalCtx;\n }\n\n /**\n * To be overridden by subclasses.\n */\n protected async _open(ctx: Context): Promise<void> {}\n\n /**\n * To be overridden by subclasses.\n */\n protected async _close(ctx: Context): Promise<void> {}\n\n /**\n * Error handler for errors that are caught by the context.\n * By default, errors are bubbled up to the parent context which is passed to the open method.\n */\n protected async _catch(err: Error): Promise<void> {\n throw err;\n }\n\n /**\n * Opens the resource.\n * If the resource is already open, it does nothing.\n * If the resource is in an error state, it throws an error.\n * If the resource is closed, it waits for it to close and then opens it.\n * @param ctx - Context to use for opening the resource. This context will receive errors that are not handled in `_catch`.\n */\n async open(ctx?: Context): Promise<this> {\n switch (this.#lifecycleState) {\n case LifecycleState.OPEN:\n return this;\n case LifecycleState.ERROR:\n throw new Error(`Invalid state: ${this.#lifecycleState}`);\n default:\n }\n\n await this.#closePromise;\n await (this.#openPromise ??= this.#open(ctx));\n\n return this;\n }\n\n /**\n * Closes the resource.\n * If the resource is already closed, it does nothing.\n */\n async close(ctx?: Context): Promise<this> {\n if (this.#lifecycleState === LifecycleState.CLOSED) {\n return this;\n }\n await this.#openPromise;\n await (this.#closePromise ??= this.#close(ctx));\n\n return this;\n }\n\n async [Symbol.asyncDispose]() {\n await this.close();\n }\n\n async #open(ctx?: Context) {\n this.#closePromise = null;\n if (ctx) {\n this.#parentCtx = ctx;\n }\n await this._open(this.#parentCtx);\n this.#lifecycleState = LifecycleState.OPEN;\n }\n\n async #close(ctx = new Context()) {\n this.#openPromise = null;\n await this.#internalCtx.dispose();\n await this._close(ctx);\n this.#internalCtx = this.#createContext();\n this.#lifecycleState = LifecycleState.CLOSED;\n }\n\n #createContext() {\n return new Context({\n onError: (error) =>\n queueMicrotask(async () => {\n try {\n await this._catch(error);\n } catch (err: any) {\n this.#lifecycleState = LifecycleState.ERROR;\n this.#parentCtx.raise(err);\n }\n }),\n });\n }\n}\n\nexport const openInContext = async <T extends Lifecycle>(ctx: Context, resource: T): Promise<T> => {\n await resource.open?.(ctx);\n ctx.onDispose(() => resource.close?.());\n\n return resource;\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["inspect", "log", "safeInstanceof", "ContextDisposedError", "Error", "constructor", "MAX_SAFE_DISPOSE_CALLBACKS", "Context", "constructor", "onError", "error", "ContextDisposedError", "dispose", "attributes", "parent", "_disposeCallbacks", "_isDisposed", "_disposePromise", "undefined", "_parent", "maxSafeDisposeCallbacks", "Symbol", "inspect", "toString", "_onError", "_attributes", "toStringTag", "custom", "default", "disposed", "disposeCallbacksLength", "length", "onDispose", "callback", "log", "catch", "push", "warn", "count", "safeThreshold", "index", "indexOf", "splice", "resolveDispose", "Promise", "resolve", "callbacks", "Array", "from", "reverse", "raise", "err", "reject", "derive", "newCtx", "clearDispose", "getAttribute", "key", "safeInstanceof", "rejectOnDispose", "ctx", "error", "ContextDisposedError", "Promise", "resolve", "reject", "onDispose", "cancelWithContext", "promise", "clearDispose", "race", "finally", "LifecycleState", "Resource", "Context", "_lifecycleState", "_ctx", "_open", "ctx", "_close", "_catch", "err", "open", "Error", "close", "Symbol", "asyncDispose", "dispose", "onError", "error", "queueMicrotask", "raise", "openInContext", "resource", "onDispose"]
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"inputs":{"packages/common/context/src/context-disposed-error.ts":{"bytes":817,"imports":[],"format":"esm"},"packages/common/context/src/context.ts":{"bytes":
|
|
1
|
+
{"inputs":{"packages/common/context/src/context-disposed-error.ts":{"bytes":817,"imports":[],"format":"esm"},"packages/common/context/src/context.ts":{"bytes":18959,"imports":[{"path":"@dxos/node-std/util","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"packages/common/context/src/context-disposed-error.ts","kind":"import-statement","original":"./context-disposed-error"}],"format":"esm"},"packages/common/context/src/promise-utils.ts":{"bytes":3040,"imports":[{"path":"packages/common/context/src/context-disposed-error.ts","kind":"import-statement","original":"./context-disposed-error"}],"format":"esm"},"packages/common/context/src/resource.ts":{"bytes":11744,"imports":[{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"packages/common/context/src/context.ts","kind":"import-statement","original":"./context"}],"format":"esm"},"packages/common/context/src/index.ts":{"bytes":777,"imports":[{"path":"packages/common/context/src/context.ts","kind":"import-statement","original":"./context"},{"path":"packages/common/context/src/promise-utils.ts","kind":"import-statement","original":"./promise-utils"},{"path":"packages/common/context/src/context-disposed-error.ts","kind":"import-statement","original":"./context-disposed-error"},{"path":"packages/common/context/src/resource.ts","kind":"import-statement","original":"./resource"}],"format":"esm"}},"outputs":{"packages/common/context/dist/lib/browser/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":16013},"packages/common/context/dist/lib/browser/index.mjs":{"imports":[{"path":"@dxos/node-std/util","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"exports":["Context","ContextDisposedError","LifecycleState","Resource","cancelWithContext","openInContext","rejectOnDispose"],"entryPoint":"packages/common/context/src/index.ts","inputs":{"packages/common/context/src/context.ts":{"bytesInOutput":5323},"packages/common/context/src/context-disposed-error.ts":{"bytesInOutput":106},"packages/common/context/src/index.ts":{"bytesInOutput":0},"packages/common/context/src/promise-utils.ts":{"bytesInOutput":410},"packages/common/context/src/resource.ts":{"bytesInOutput":2979}},"bytes":9222}}}
|
package/dist/lib/node/index.cjs
CHANGED
|
@@ -50,23 +50,25 @@ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/common/context/src/cont
|
|
|
50
50
|
var MAX_SAFE_DISPOSE_CALLBACKS = 300;
|
|
51
51
|
var _a, _b;
|
|
52
52
|
var Context = class _Context {
|
|
53
|
-
constructor({
|
|
53
|
+
constructor({ onError = (error) => {
|
|
54
54
|
if (error instanceof ContextDisposedError) {
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
57
|
void this.dispose();
|
|
58
58
|
throw error;
|
|
59
|
-
} } = {}) {
|
|
59
|
+
}, attributes = {}, parent } = {}) {
|
|
60
60
|
this._disposeCallbacks = [];
|
|
61
61
|
this._isDisposed = false;
|
|
62
62
|
this._disposePromise = void 0;
|
|
63
|
+
this._parent = null;
|
|
63
64
|
this.maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
|
|
64
65
|
this[_a] = "Context";
|
|
65
66
|
this[_b] = () => this.toString();
|
|
66
|
-
this._name = name;
|
|
67
|
-
this._parent = parent;
|
|
68
|
-
this._attributes = attributes;
|
|
69
67
|
this._onError = onError;
|
|
68
|
+
this._attributes = attributes;
|
|
69
|
+
if (parent !== void 0) {
|
|
70
|
+
this._parent = parent;
|
|
71
|
+
}
|
|
70
72
|
}
|
|
71
73
|
static {
|
|
72
74
|
_a = Symbol.toStringTag, _b = import_node_util.inspect.custom;
|
|
@@ -97,7 +99,7 @@ var Context = class _Context {
|
|
|
97
99
|
} catch (error) {
|
|
98
100
|
import_log.log.catch(error, void 0, {
|
|
99
101
|
F: __dxlog_file,
|
|
100
|
-
L:
|
|
102
|
+
L: 88,
|
|
101
103
|
S: this,
|
|
102
104
|
C: (f, a) => f(...a)
|
|
103
105
|
});
|
|
@@ -106,11 +108,12 @@ var Context = class _Context {
|
|
|
106
108
|
}
|
|
107
109
|
this._disposeCallbacks.push(callback);
|
|
108
110
|
if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {
|
|
109
|
-
import_log.log.warn("Context has a large number of dispose callbacks
|
|
110
|
-
count: this._disposeCallbacks.length
|
|
111
|
+
import_log.log.warn("Context has a large number of dispose callbacks. This might be a memory leak.", {
|
|
112
|
+
count: this._disposeCallbacks.length,
|
|
113
|
+
safeThreshold: this.maxSafeDisposeCallbacks
|
|
111
114
|
}, {
|
|
112
115
|
F: __dxlog_file,
|
|
113
|
-
L:
|
|
116
|
+
L: 95,
|
|
114
117
|
S: this,
|
|
115
118
|
C: (f, a) => f(...a)
|
|
116
119
|
});
|
|
@@ -129,7 +132,7 @@ var Context = class _Context {
|
|
|
129
132
|
* It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.
|
|
130
133
|
* Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.
|
|
131
134
|
*/
|
|
132
|
-
async dispose(
|
|
135
|
+
async dispose() {
|
|
133
136
|
if (this._disposePromise) {
|
|
134
137
|
return this._disposePromise;
|
|
135
138
|
}
|
|
@@ -140,52 +143,19 @@ var Context = class _Context {
|
|
|
140
143
|
});
|
|
141
144
|
const callbacks = Array.from(this._disposeCallbacks).reverse();
|
|
142
145
|
this._disposeCallbacks.length = 0;
|
|
143
|
-
if (this._name) {
|
|
144
|
-
(0, import_log.log)("disposing", {
|
|
145
|
-
context: this._name,
|
|
146
|
-
count: callbacks.length
|
|
147
|
-
}, {
|
|
148
|
-
F: __dxlog_file,
|
|
149
|
-
L: 139,
|
|
150
|
-
S: this,
|
|
151
|
-
C: (f, a) => f(...a)
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
let i = 0;
|
|
155
|
-
let clean = true;
|
|
156
146
|
for (const callback of callbacks) {
|
|
157
147
|
try {
|
|
158
148
|
await callback();
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
import_log.log.catch(err, {
|
|
162
|
-
context: this._name,
|
|
163
|
-
callback: i,
|
|
164
|
-
count: callbacks.length
|
|
165
|
-
}, {
|
|
149
|
+
} catch (error) {
|
|
150
|
+
import_log.log.catch(error, void 0, {
|
|
166
151
|
F: __dxlog_file,
|
|
167
|
-
L:
|
|
152
|
+
L: 136,
|
|
168
153
|
S: this,
|
|
169
154
|
C: (f, a) => f(...a)
|
|
170
155
|
});
|
|
171
|
-
clean = false;
|
|
172
|
-
if (throwOnError) {
|
|
173
|
-
throw err;
|
|
174
|
-
}
|
|
175
156
|
}
|
|
176
157
|
}
|
|
177
|
-
resolveDispose(
|
|
178
|
-
if (this._name) {
|
|
179
|
-
(0, import_log.log)("disposed", {
|
|
180
|
-
context: this._name
|
|
181
|
-
}, {
|
|
182
|
-
F: __dxlog_file,
|
|
183
|
-
L: 159,
|
|
184
|
-
S: this,
|
|
185
|
-
C: (f, a) => f(...a)
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
return clean;
|
|
158
|
+
resolveDispose();
|
|
189
159
|
}
|
|
190
160
|
/**
|
|
191
161
|
* Raise the error inside the context.
|
|
@@ -226,7 +196,7 @@ var Context = class _Context {
|
|
|
226
196
|
if (key in this._attributes) {
|
|
227
197
|
return this._attributes[key];
|
|
228
198
|
}
|
|
229
|
-
if (this._parent) {
|
|
199
|
+
if (this._parent !== null) {
|
|
230
200
|
return this._parent.getAttribute(key);
|
|
231
201
|
}
|
|
232
202
|
return void 0;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/context.ts", "../../../src/context-disposed-error.ts", "../../../src/promise-utils.ts", "../../../src/resource.ts"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2022 DXOS.org\n//\n\nimport { inspect } from 'node:util';\n\nimport { log } from '@dxos/log';\nimport { safeInstanceof } from '@dxos/util';\n\nimport { ContextDisposedError } from './context-disposed-error';\n\nexport type ContextErrorHandler = (error: Error) => void;\n\nexport type DisposeCallback = () => any | Promise<any>;\n\nexport type CreateContextParams = {\n name?: string;\n parent?: Context;\n attributes?: Record<string, any>;\n onError?: ContextErrorHandler;\n};\n\n/**\n * Maximum number of dispose callbacks before we start logging warnings.\n */\nconst MAX_SAFE_DISPOSE_CALLBACKS = 300;\n\n@safeInstanceof('Context')\nexport class Context {\n static default() {\n return new Context();\n }\n\n private readonly _disposeCallbacks: DisposeCallback[] = [];\n\n private readonly _name?: string;\n private readonly _parent?: Context;\n private readonly _attributes: Record<string, any>;\n private readonly _onError: ContextErrorHandler;\n\n private _isDisposed = false;\n private _disposePromise?: Promise<boolean> = undefined;\n\n public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;\n\n constructor({\n name, // TODO(burdon): Automate?\n parent,\n attributes = {},\n onError = (error) => {\n if (error instanceof ContextDisposedError) {\n return;\n }\n\n void this.dispose();\n\n // Will generate an unhandled rejection.\n throw error;\n },\n }: CreateContextParams = {}) {\n this._name = name;\n this._parent = parent;\n this._attributes = attributes;\n this._onError = onError;\n }\n\n get disposed() {\n return this._isDisposed;\n }\n\n get disposeCallbacksLength() {\n return this._disposeCallbacks.length;\n }\n\n /**\n * Schedules a callback to run when the context is disposed.\n * May be async, in this case the disposer might choose to wait for all resource to released.\n * Throwing an error inside the callback will result in the error being logged, but not re-thrown.\n *\n * NOTE: Will call the callback immediately if the context is already disposed.\n *\n * @returns A function that can be used to remove the callback from the dispose list.\n */\n onDispose(callback: DisposeCallback): () => void {\n if (this._isDisposed) {\n // Call the callback immediately if the context is already disposed.\n void (async () => {\n try {\n await callback();\n } catch (error: any) {\n log.catch(error);\n }\n })();\n }\n\n this._disposeCallbacks.push(callback);\n if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {\n log.warn('Context has a large number of dispose callbacks (this might be a memory leak).', {\n count: this._disposeCallbacks.length,\n });\n }\n\n // Remove handler.\n return () => {\n const index = this._disposeCallbacks.indexOf(callback);\n if (index !== -1) {\n this._disposeCallbacks.splice(index, 1);\n }\n };\n }\n\n /**\n * Runs all dispose callbacks.\n * Callbacks are run in the reverse order they were added.\n * This function never throws.\n * It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.\n * Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.\n */\n async dispose(throwOnError = false): Promise<boolean> {\n if (this._disposePromise) {\n return this._disposePromise;\n }\n\n // TODO(burdon): Probably should not be set until the dispose is complete, but causes tests to fail if moved.\n this._isDisposed = true;\n\n // Set the promise before running the callbacks.\n let resolveDispose!: (value: boolean) => void;\n this._disposePromise = new Promise<boolean>((resolve) => {\n resolveDispose = resolve;\n });\n\n // Process last first.\n // Clone the array so that any mutations to the original array don't affect the dispose process.\n const callbacks = Array.from(this._disposeCallbacks).reverse();\n this._disposeCallbacks.length = 0;\n\n if (this._name) {\n log('disposing', { context: this._name, count: callbacks.length });\n }\n\n let i = 0;\n let clean = true;\n for (const callback of callbacks) {\n try {\n await callback();\n i++;\n } catch (err: any) {\n log.catch(err, { context: this._name, callback: i, count: callbacks.length });\n clean = false;\n if (throwOnError) {\n throw err;\n }\n }\n }\n\n resolveDispose(clean);\n if (this._name) {\n log('disposed', { context: this._name });\n }\n\n return clean;\n }\n\n /**\n * Raise the error inside the context.\n * The error will be propagated to the error handler.\n * IF the error handler is not set, the error will dispose the context and cause an unhandled rejection.\n */\n raise(error: Error): void {\n if (this._isDisposed) {\n // TODO(dmaretskyi): Don't log those.\n // log.warn('Error in disposed context', error);\n return;\n }\n\n try {\n this._onError(error);\n } catch (err) {\n // Generate an unhandled rejection and stop the error propagation.\n void Promise.reject(err);\n }\n }\n\n derive({ onError, attributes }: CreateContextParams = {}): Context {\n const newCtx = new Context({\n // TODO(dmaretskyi): Optimize to not require allocating a new closure for every context.\n onError: async (error) => {\n if (!onError) {\n this.raise(error);\n } else {\n try {\n await onError(error);\n } catch {\n this.raise(error);\n }\n }\n },\n attributes,\n });\n\n const clearDispose = this.onDispose(() => newCtx.dispose());\n newCtx.onDispose(clearDispose);\n return newCtx;\n }\n\n getAttribute(key: string): any {\n if (key in this._attributes) {\n return this._attributes[key];\n }\n if (this._parent) {\n return this._parent.getAttribute(key);\n }\n\n return undefined;\n }\n\n [Symbol.toStringTag] = 'Context';\n [inspect.custom] = () => this.toString();\n\n toString() {\n return `Context(${this._isDisposed ? 'disposed' : 'active'})`;\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nexport class ContextDisposedError extends Error {\n constructor() {\n super('Context disposed.');\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { type Context } from './context';\nimport { ContextDisposedError } from './context-disposed-error';\n\n/**\n * @returns A promise that rejects when the context is disposed.\n */\n// TODO(dmaretskyi): Memory leak.\nexport const rejectOnDispose = (ctx: Context, error = new ContextDisposedError()): Promise<never> =>\n new Promise((resolve, reject) => {\n ctx.onDispose(() => reject(error));\n });\n\n/**\n * Rejects the promise if the context is disposed.\n */\nexport const cancelWithContext = <T>(ctx: Context, promise: Promise<T>): Promise<T> => {\n let clearDispose: () => void;\n return Promise.race([\n promise,\n new Promise<never>((resolve, reject) => {\n // Will be called before .finally() handlers.\n clearDispose = ctx.onDispose(() => reject(new ContextDisposedError()));\n }),\n ]).finally(() => clearDispose?.());\n};\n", "//\n// Copyright 2024 DXOS.org\n//\n\nimport '@dxos/util';\n\nimport { Context } from './context';\n\nexport enum LifecycleState {\n CLOSED = 'CLOSED',\n OPEN = 'OPEN',\n ERROR = 'ERROR',\n}\n\nexport interface Lifecycle {\n open?(ctx?: Context): Promise<any> | any;\n close?(): Promise<any> | any;\n}\n\n/**\n * Base class for resources that need to be opened and closed.\n */\nexport abstract class Resource implements Lifecycle {\n #lifecycleState = LifecycleState.CLOSED;\n #openPromise: Promise<void> | null = null;\n #closePromise: Promise<void> | null = null;\n\n /**\n * Managed internally by the resource.\n * Recreated on close.\n * Errors are propagated to the `_catch` method and the parent context.\n */\n #internalCtx: Context = this.#createContext();\n\n /**\n * Context that is used to bubble up errors that are not handled by the resource.\n * Provided in the open method.\n */\n #parentCtx: Context = new Context();\n\n protected get _lifecycleState() {\n return this.#lifecycleState;\n }\n\n protected get _ctx() {\n return this.#internalCtx;\n }\n\n /**\n * To be overridden by subclasses.\n */\n protected async _open(ctx: Context): Promise<void> {}\n\n /**\n * To be overridden by subclasses.\n */\n protected async _close(ctx: Context): Promise<void> {}\n\n /**\n * Error handler for errors that are caught by the context.\n * By default, errors are bubbled up to the parent context which is passed to the open method.\n */\n protected async _catch(err: Error): Promise<void> {\n throw err;\n }\n\n /**\n * Opens the resource.\n * If the resource is already open, it does nothing.\n * If the resource is in an error state, it throws an error.\n * If the resource is closed, it waits for it to close and then opens it.\n * @param ctx - Context to use for opening the resource. This context will receive errors that are not handled in `_catch`.\n */\n async open(ctx?: Context): Promise<this> {\n switch (this.#lifecycleState) {\n case LifecycleState.OPEN:\n return this;\n case LifecycleState.ERROR:\n throw new Error(`Invalid state: ${this.#lifecycleState}`);\n default:\n }\n\n await this.#closePromise;\n await (this.#openPromise ??= this.#open(ctx));\n\n return this;\n }\n\n /**\n * Closes the resource.\n * If the resource is already closed, it does nothing.\n */\n async close(ctx?: Context): Promise<this> {\n if (this.#lifecycleState === LifecycleState.CLOSED) {\n return this;\n }\n await this.#openPromise;\n await (this.#closePromise ??= this.#close(ctx));\n\n return this;\n }\n\n async [Symbol.asyncDispose]() {\n await this.close();\n }\n\n async #open(ctx?: Context) {\n this.#closePromise = null;\n if (ctx) {\n this.#parentCtx = ctx;\n }\n await this._open(this.#parentCtx);\n this.#lifecycleState = LifecycleState.OPEN;\n }\n\n async #close(ctx = new Context()) {\n this.#openPromise = null;\n await this.#internalCtx.dispose();\n await this._close(ctx);\n this.#internalCtx = this.#createContext();\n this.#lifecycleState = LifecycleState.CLOSED;\n }\n\n #createContext() {\n return new Context({\n onError: (error) =>\n queueMicrotask(async () => {\n try {\n await this._catch(error);\n } catch (err: any) {\n this.#lifecycleState = LifecycleState.ERROR;\n this.#parentCtx.raise(err);\n }\n }),\n });\n }\n}\n\nexport const openInContext = async <T extends Lifecycle>(ctx: Context, resource: T): Promise<T> => {\n await resource.open?.(ctx);\n ctx.onDispose(() => resource.close?.());\n\n return resource;\n};\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,uBAAwB;AAExB,iBAAoB;AACpB,kBAA+B;AGH/B,IAAAA,eAAO;AFAA,IAAMC,uBAAN,cAAmCC,MAAAA;EACxCC,cAAc;AACZ,UAAM,mBAAA;EACR;AACF;;;;;;;;;;;;
|
|
6
|
-
"names": ["import_util", "ContextDisposedError", "Error", "constructor", "MAX_SAFE_DISPOSE_CALLBACKS", "Context", "
|
|
4
|
+
"sourcesContent": ["//\n// Copyright 2022 DXOS.org\n//\n\nimport { inspect } from 'node:util';\n\nimport { log } from '@dxos/log';\nimport { safeInstanceof } from '@dxos/util';\n\nimport { ContextDisposedError } from './context-disposed-error';\n\nexport type ContextErrorHandler = (error: Error) => void;\n\nexport type DisposeCallback = () => any | Promise<any>;\n\nexport type CreateContextParams = {\n onError?: ContextErrorHandler;\n attributes?: Record<string, any>;\n parent?: Context;\n};\n\n/**\n * Maximum number of dispose callbacks before we start logging warnings.\n */\nconst MAX_SAFE_DISPOSE_CALLBACKS = 300;\n\n@safeInstanceof('Context')\nexport class Context {\n static default() {\n return new Context();\n }\n\n private readonly _onError: ContextErrorHandler;\n private readonly _disposeCallbacks: DisposeCallback[] = [];\n private _isDisposed = false;\n private _disposePromise?: Promise<void> = undefined;\n private _parent: Context | null = null;\n\n private _attributes: Record<string, any>;\n\n public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;\n\n constructor({\n onError = (error) => {\n if (error instanceof ContextDisposedError) {\n return;\n }\n\n void this.dispose();\n\n // Will generate an unhandled rejection.\n throw error;\n },\n attributes = {},\n parent,\n }: CreateContextParams = {}) {\n this._onError = onError;\n this._attributes = attributes;\n if (parent !== undefined) {\n this._parent = parent;\n }\n }\n\n get disposed() {\n return this._isDisposed;\n }\n\n get disposeCallbacksLength() {\n return this._disposeCallbacks.length;\n }\n\n /**\n * Schedules a callback to run when the context is disposed.\n * May be async, in this case the disposer might choose to wait for all resource to released.\n * Throwing an error inside the callback will result in the error being logged, but not re-thrown.\n *\n * NOTE: Will call the callback immediately if the context is already disposed.\n *\n * @returns A function that can be used to remove the callback from the dispose list.\n */\n onDispose(callback: DisposeCallback) {\n if (this._isDisposed) {\n // Call the callback immediately if the context is already disposed.\n void (async () => {\n try {\n await callback();\n } catch (error: any) {\n log.catch(error);\n }\n })();\n }\n\n this._disposeCallbacks.push(callback);\n if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {\n log.warn('Context has a large number of dispose callbacks. This might be a memory leak.', {\n count: this._disposeCallbacks.length,\n safeThreshold: this.maxSafeDisposeCallbacks,\n });\n }\n\n return () => {\n const index = this._disposeCallbacks.indexOf(callback);\n if (index !== -1) {\n this._disposeCallbacks.splice(index, 1);\n }\n };\n }\n\n /**\n * Runs all dispose callbacks.\n * Callbacks are run in the reverse order they were added.\n * This function never throws.\n * It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.\n * Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.\n */\n async dispose(): Promise<void> {\n if (this._disposePromise) {\n return this._disposePromise;\n }\n this._isDisposed = true;\n\n // Set the promise before running the callbacks.\n let resolveDispose!: () => void;\n this._disposePromise = new Promise<void>((resolve) => {\n resolveDispose = resolve;\n });\n\n // Clone the array so that any mutations to the original array don't affect the dispose process.\n const callbacks = Array.from(this._disposeCallbacks).reverse();\n this._disposeCallbacks.length = 0;\n\n for (const callback of callbacks) {\n try {\n await callback();\n } catch (error: any) {\n log.catch(error);\n }\n }\n resolveDispose();\n }\n\n /**\n * Raise the error inside the context.\n * The error will be propagated to the error handler.\n * IF the error handler is not set, the error will dispose the context and cause an unhandled rejection.\n */\n raise(error: Error): void {\n if (this._isDisposed) {\n // TODO(dmaretskyi): Don't log those.\n // log.warn('Error in disposed context', error);\n return;\n }\n\n try {\n this._onError(error);\n } catch (err) {\n // Generate an unhandled rejection and stop the error propagation.\n void Promise.reject(err);\n }\n }\n\n derive({ onError, attributes }: CreateContextParams = {}): Context {\n const newCtx = new Context({\n // TODO(dmaretskyi): Optimize to not require allocating a new closure for every context.\n onError: async (error) => {\n if (!onError) {\n this.raise(error);\n } else {\n try {\n await onError(error);\n } catch {\n this.raise(error);\n }\n }\n },\n attributes,\n });\n const clearDispose = this.onDispose(() => newCtx.dispose());\n newCtx.onDispose(clearDispose);\n return newCtx;\n }\n\n getAttribute(key: string): any {\n if (key in this._attributes) {\n return this._attributes[key];\n }\n if (this._parent !== null) {\n return this._parent.getAttribute(key);\n }\n return undefined;\n }\n\n [Symbol.toStringTag] = 'Context';\n\n [inspect.custom] = () => this.toString();\n\n toString() {\n return `Context(${this._isDisposed ? 'disposed' : 'active'})`;\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nexport class ContextDisposedError extends Error {\n constructor() {\n super('Context disposed.');\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { type Context } from './context';\nimport { ContextDisposedError } from './context-disposed-error';\n\n/**\n * @returns A promise that rejects when the context is disposed.\n */\n// TODO(dmaretskyi): Memory leak.\nexport const rejectOnDispose = (ctx: Context, error = new ContextDisposedError()): Promise<never> =>\n new Promise((resolve, reject) => {\n ctx.onDispose(() => reject(error));\n });\n\n/**\n * Rejects the promise if the context is disposed.\n */\nexport const cancelWithContext = <T>(ctx: Context, promise: Promise<T>): Promise<T> => {\n let clearDispose: () => void;\n return Promise.race([\n promise,\n new Promise<never>((resolve, reject) => {\n // Will be called before .finally() handlers.\n clearDispose = ctx.onDispose(() => reject(new ContextDisposedError()));\n }),\n ]).finally(() => clearDispose?.());\n};\n", "//\n// Copyright 2024 DXOS.org\n//\n\nimport '@dxos/util';\n\nimport { Context } from './context';\n\nexport enum LifecycleState {\n CLOSED = 'CLOSED',\n OPEN = 'OPEN',\n ERROR = 'ERROR',\n}\n\nexport interface Lifecycle {\n open?(ctx?: Context): Promise<any> | any;\n close?(): Promise<any> | any;\n}\n\n/**\n * Base class for resources that need to be opened and closed.\n */\nexport abstract class Resource implements Lifecycle {\n #lifecycleState = LifecycleState.CLOSED;\n #openPromise: Promise<void> | null = null;\n #closePromise: Promise<void> | null = null;\n\n /**\n * Managed internally by the resource.\n * Recreated on close.\n * Errors are propagated to the `_catch` method and the parent context.\n */\n #internalCtx: Context = this.#createContext();\n\n /**\n * Context that is used to bubble up errors that are not handled by the resource.\n * Provided in the open method.\n */\n #parentCtx: Context = new Context();\n\n protected get _lifecycleState() {\n return this.#lifecycleState;\n }\n\n protected get _ctx() {\n return this.#internalCtx;\n }\n\n /**\n * To be overridden by subclasses.\n */\n protected async _open(ctx: Context): Promise<void> {}\n\n /**\n * To be overridden by subclasses.\n */\n protected async _close(ctx: Context): Promise<void> {}\n\n /**\n * Error handler for errors that are caught by the context.\n * By default, errors are bubbled up to the parent context which is passed to the open method.\n */\n protected async _catch(err: Error): Promise<void> {\n throw err;\n }\n\n /**\n * Opens the resource.\n * If the resource is already open, it does nothing.\n * If the resource is in an error state, it throws an error.\n * If the resource is closed, it waits for it to close and then opens it.\n * @param ctx - Context to use for opening the resource. This context will receive errors that are not handled in `_catch`.\n */\n async open(ctx?: Context): Promise<this> {\n switch (this.#lifecycleState) {\n case LifecycleState.OPEN:\n return this;\n case LifecycleState.ERROR:\n throw new Error(`Invalid state: ${this.#lifecycleState}`);\n default:\n }\n\n await this.#closePromise;\n await (this.#openPromise ??= this.#open(ctx));\n\n return this;\n }\n\n /**\n * Closes the resource.\n * If the resource is already closed, it does nothing.\n */\n async close(ctx?: Context): Promise<this> {\n if (this.#lifecycleState === LifecycleState.CLOSED) {\n return this;\n }\n await this.#openPromise;\n await (this.#closePromise ??= this.#close(ctx));\n\n return this;\n }\n\n async [Symbol.asyncDispose]() {\n await this.close();\n }\n\n async #open(ctx?: Context) {\n this.#closePromise = null;\n if (ctx) {\n this.#parentCtx = ctx;\n }\n await this._open(this.#parentCtx);\n this.#lifecycleState = LifecycleState.OPEN;\n }\n\n async #close(ctx = new Context()) {\n this.#openPromise = null;\n await this.#internalCtx.dispose();\n await this._close(ctx);\n this.#internalCtx = this.#createContext();\n this.#lifecycleState = LifecycleState.CLOSED;\n }\n\n #createContext() {\n return new Context({\n onError: (error) =>\n queueMicrotask(async () => {\n try {\n await this._catch(error);\n } catch (err: any) {\n this.#lifecycleState = LifecycleState.ERROR;\n this.#parentCtx.raise(err);\n }\n }),\n });\n }\n}\n\nexport const openInContext = async <T extends Lifecycle>(ctx: Context, resource: T): Promise<T> => {\n await resource.open?.(ctx);\n ctx.onDispose(() => resource.close?.());\n\n return resource;\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["import_util", "ContextDisposedError", "Error", "constructor", "MAX_SAFE_DISPOSE_CALLBACKS", "Context", "onError", "error", "dispose", "attributes", "parent", "_disposeCallbacks", "_isDisposed", "_disposePromise", "undefined", "_parent", "maxSafeDisposeCallbacks", "Symbol", "inspect", "toString", "_onError", "_attributes", "toStringTag", "custom", "default", "disposed", "disposeCallbacksLength", "length", "onDispose", "callback", "log", "catch", "push", "warn", "count", "safeThreshold", "index", "indexOf", "splice", "resolveDispose", "Promise", "resolve", "callbacks", "Array", "from", "reverse", "raise", "err", "reject", "derive", "newCtx", "clearDispose", "getAttribute", "key", "safeInstanceof", "rejectOnDispose", "ctx", "cancelWithContext", "promise", "race", "finally", "LifecycleState", "Resource", "_lifecycleState", "_ctx", "_open", "_close", "_catch", "open", "close", "asyncDispose", "queueMicrotask", "openInContext", "resource"]
|
|
7
7
|
}
|
package/dist/lib/node/meta.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"inputs":{"packages/common/context/src/context-disposed-error.ts":{"bytes":817,"imports":[],"format":"esm"},"packages/common/context/src/context.ts":{"bytes":
|
|
1
|
+
{"inputs":{"packages/common/context/src/context-disposed-error.ts":{"bytes":817,"imports":[],"format":"esm"},"packages/common/context/src/context.ts":{"bytes":18959,"imports":[{"path":"node:util","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"packages/common/context/src/context-disposed-error.ts","kind":"import-statement","original":"./context-disposed-error"}],"format":"esm"},"packages/common/context/src/promise-utils.ts":{"bytes":3040,"imports":[{"path":"packages/common/context/src/context-disposed-error.ts","kind":"import-statement","original":"./context-disposed-error"}],"format":"esm"},"packages/common/context/src/resource.ts":{"bytes":11744,"imports":[{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"packages/common/context/src/context.ts","kind":"import-statement","original":"./context"}],"format":"esm"},"packages/common/context/src/index.ts":{"bytes":777,"imports":[{"path":"packages/common/context/src/context.ts","kind":"import-statement","original":"./context"},{"path":"packages/common/context/src/promise-utils.ts","kind":"import-statement","original":"./promise-utils"},{"path":"packages/common/context/src/context-disposed-error.ts","kind":"import-statement","original":"./context-disposed-error"},{"path":"packages/common/context/src/resource.ts","kind":"import-statement","original":"./resource"}],"format":"esm"}},"outputs":{"packages/common/context/dist/lib/node/index.cjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":16013},"packages/common/context/dist/lib/node/index.cjs":{"imports":[{"path":"node:util","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"exports":["Context","ContextDisposedError","LifecycleState","Resource","cancelWithContext","openInContext","rejectOnDispose"],"entryPoint":"packages/common/context/src/index.ts","inputs":{"packages/common/context/src/context.ts":{"bytesInOutput":5313},"packages/common/context/src/context-disposed-error.ts":{"bytesInOutput":106},"packages/common/context/src/index.ts":{"bytesInOutput":0},"packages/common/context/src/promise-utils.ts":{"bytesInOutput":410},"packages/common/context/src/resource.ts":{"bytesInOutput":2979}},"bytes":9212}}}
|
|
@@ -3,23 +3,20 @@ import { inspect } from 'node:util';
|
|
|
3
3
|
export type ContextErrorHandler = (error: Error) => void;
|
|
4
4
|
export type DisposeCallback = () => any | Promise<any>;
|
|
5
5
|
export type CreateContextParams = {
|
|
6
|
-
name?: string;
|
|
7
|
-
parent?: Context;
|
|
8
|
-
attributes?: Record<string, any>;
|
|
9
6
|
onError?: ContextErrorHandler;
|
|
7
|
+
attributes?: Record<string, any>;
|
|
8
|
+
parent?: Context;
|
|
10
9
|
};
|
|
11
10
|
export declare class Context {
|
|
12
11
|
static default(): Context;
|
|
13
|
-
private readonly _disposeCallbacks;
|
|
14
|
-
private readonly _name?;
|
|
15
|
-
private readonly _parent?;
|
|
16
|
-
private readonly _attributes;
|
|
17
12
|
private readonly _onError;
|
|
13
|
+
private readonly _disposeCallbacks;
|
|
18
14
|
private _isDisposed;
|
|
19
15
|
private _disposePromise?;
|
|
16
|
+
private _parent;
|
|
17
|
+
private _attributes;
|
|
20
18
|
maxSafeDisposeCallbacks: number;
|
|
21
|
-
constructor({
|
|
22
|
-
parent, attributes, onError, }?: CreateContextParams);
|
|
19
|
+
constructor({ onError, attributes, parent, }?: CreateContextParams);
|
|
23
20
|
get disposed(): boolean;
|
|
24
21
|
get disposeCallbacksLength(): number;
|
|
25
22
|
/**
|
|
@@ -39,7 +36,7 @@ export declare class Context {
|
|
|
39
36
|
* It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.
|
|
40
37
|
* Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.
|
|
41
38
|
*/
|
|
42
|
-
dispose(
|
|
39
|
+
dispose(): Promise<void>;
|
|
43
40
|
/**
|
|
44
41
|
* Raise the error inside the context.
|
|
45
42
|
* The error will be propagated to the error handler.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/context.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;AAEzD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;AAEvD,MAAM,MAAM,mBAAmB,GAAG;IAChC,
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/context.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;AAEzD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;AAEvD,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAOF,qBACa,OAAO;IAClB,MAAM,CAAC,OAAO;IAId,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsB;IAC/C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAyB;IAC3D,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAC,CAA4B;IACpD,OAAO,CAAC,OAAO,CAAwB;IAEvC,OAAO,CAAC,WAAW,CAAsB;IAElC,uBAAuB,SAA8B;gBAEhD,EACV,OASC,EACD,UAAe,EACf,MAAM,GACP,GAAE,mBAAwB;IAQ3B,IAAI,QAAQ,YAEX;IAED,IAAI,sBAAsB,WAEzB;IAED;;;;;;;;OAQG;IACH,SAAS,CAAC,QAAQ,EAAE,eAAe;IA4BnC;;;;;;OAMG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA0B9B;;;;OAIG;IACH,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAezB,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,GAAE,mBAAwB,GAAG,OAAO;IAqBlE,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG;IAU9B,CAAC,MAAM,CAAC,WAAW,CAAC,SAAa;IAEjC,CAAC,OAAO,CAAC,MAAM,CAAC,eAAyB;IAEzC,QAAQ;CAGT"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/context",
|
|
3
|
-
"version": "0.5.3-main.
|
|
3
|
+
"version": "0.5.3-main.e76d664",
|
|
4
4
|
"description": "Async utils.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"src"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@dxos/log": "0.5.3-main.
|
|
20
|
-
"@dxos/util": "0.5.3-main.
|
|
21
|
-
"@dxos/node-std": "0.5.3-main.
|
|
19
|
+
"@dxos/log": "0.5.3-main.e76d664",
|
|
20
|
+
"@dxos/util": "0.5.3-main.e76d664",
|
|
21
|
+
"@dxos/node-std": "0.5.3-main.e76d664"
|
|
22
22
|
},
|
|
23
23
|
"publishConfig": {
|
|
24
24
|
"access": "public"
|
package/src/context.ts
CHANGED
|
@@ -14,10 +14,9 @@ export type ContextErrorHandler = (error: Error) => void;
|
|
|
14
14
|
export type DisposeCallback = () => any | Promise<any>;
|
|
15
15
|
|
|
16
16
|
export type CreateContextParams = {
|
|
17
|
-
name?: string;
|
|
18
|
-
parent?: Context;
|
|
19
|
-
attributes?: Record<string, any>;
|
|
20
17
|
onError?: ContextErrorHandler;
|
|
18
|
+
attributes?: Record<string, any>;
|
|
19
|
+
parent?: Context;
|
|
21
20
|
};
|
|
22
21
|
|
|
23
22
|
/**
|
|
@@ -31,22 +30,17 @@ export class Context {
|
|
|
31
30
|
return new Context();
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
private readonly _disposeCallbacks: DisposeCallback[] = [];
|
|
35
|
-
|
|
36
|
-
private readonly _name?: string;
|
|
37
|
-
private readonly _parent?: Context;
|
|
38
|
-
private readonly _attributes: Record<string, any>;
|
|
39
33
|
private readonly _onError: ContextErrorHandler;
|
|
40
|
-
|
|
34
|
+
private readonly _disposeCallbacks: DisposeCallback[] = [];
|
|
41
35
|
private _isDisposed = false;
|
|
42
|
-
private _disposePromise?: Promise<
|
|
36
|
+
private _disposePromise?: Promise<void> = undefined;
|
|
37
|
+
private _parent: Context | null = null;
|
|
38
|
+
|
|
39
|
+
private _attributes: Record<string, any>;
|
|
43
40
|
|
|
44
41
|
public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
|
|
45
42
|
|
|
46
43
|
constructor({
|
|
47
|
-
name, // TODO(burdon): Automate?
|
|
48
|
-
parent,
|
|
49
|
-
attributes = {},
|
|
50
44
|
onError = (error) => {
|
|
51
45
|
if (error instanceof ContextDisposedError) {
|
|
52
46
|
return;
|
|
@@ -57,11 +51,14 @@ export class Context {
|
|
|
57
51
|
// Will generate an unhandled rejection.
|
|
58
52
|
throw error;
|
|
59
53
|
},
|
|
54
|
+
attributes = {},
|
|
55
|
+
parent,
|
|
60
56
|
}: CreateContextParams = {}) {
|
|
61
|
-
this._name = name;
|
|
62
|
-
this._parent = parent;
|
|
63
|
-
this._attributes = attributes;
|
|
64
57
|
this._onError = onError;
|
|
58
|
+
this._attributes = attributes;
|
|
59
|
+
if (parent !== undefined) {
|
|
60
|
+
this._parent = parent;
|
|
61
|
+
}
|
|
65
62
|
}
|
|
66
63
|
|
|
67
64
|
get disposed() {
|
|
@@ -81,7 +78,7 @@ export class Context {
|
|
|
81
78
|
*
|
|
82
79
|
* @returns A function that can be used to remove the callback from the dispose list.
|
|
83
80
|
*/
|
|
84
|
-
onDispose(callback: DisposeCallback)
|
|
81
|
+
onDispose(callback: DisposeCallback) {
|
|
85
82
|
if (this._isDisposed) {
|
|
86
83
|
// Call the callback immediately if the context is already disposed.
|
|
87
84
|
void (async () => {
|
|
@@ -95,12 +92,12 @@ export class Context {
|
|
|
95
92
|
|
|
96
93
|
this._disposeCallbacks.push(callback);
|
|
97
94
|
if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {
|
|
98
|
-
log.warn('Context has a large number of dispose callbacks
|
|
95
|
+
log.warn('Context has a large number of dispose callbacks. This might be a memory leak.', {
|
|
99
96
|
count: this._disposeCallbacks.length,
|
|
97
|
+
safeThreshold: this.maxSafeDisposeCallbacks,
|
|
100
98
|
});
|
|
101
99
|
}
|
|
102
100
|
|
|
103
|
-
// Remove handler.
|
|
104
101
|
return () => {
|
|
105
102
|
const index = this._disposeCallbacks.indexOf(callback);
|
|
106
103
|
if (index !== -1) {
|
|
@@ -116,50 +113,30 @@ export class Context {
|
|
|
116
113
|
* It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.
|
|
117
114
|
* Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.
|
|
118
115
|
*/
|
|
119
|
-
async dispose(
|
|
116
|
+
async dispose(): Promise<void> {
|
|
120
117
|
if (this._disposePromise) {
|
|
121
118
|
return this._disposePromise;
|
|
122
119
|
}
|
|
123
|
-
|
|
124
|
-
// TODO(burdon): Probably should not be set until the dispose is complete, but causes tests to fail if moved.
|
|
125
120
|
this._isDisposed = true;
|
|
126
121
|
|
|
127
122
|
// Set the promise before running the callbacks.
|
|
128
|
-
let resolveDispose!: (
|
|
129
|
-
this._disposePromise = new Promise<
|
|
123
|
+
let resolveDispose!: () => void;
|
|
124
|
+
this._disposePromise = new Promise<void>((resolve) => {
|
|
130
125
|
resolveDispose = resolve;
|
|
131
126
|
});
|
|
132
127
|
|
|
133
|
-
// Process last first.
|
|
134
128
|
// Clone the array so that any mutations to the original array don't affect the dispose process.
|
|
135
129
|
const callbacks = Array.from(this._disposeCallbacks).reverse();
|
|
136
130
|
this._disposeCallbacks.length = 0;
|
|
137
131
|
|
|
138
|
-
if (this._name) {
|
|
139
|
-
log('disposing', { context: this._name, count: callbacks.length });
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
let i = 0;
|
|
143
|
-
let clean = true;
|
|
144
132
|
for (const callback of callbacks) {
|
|
145
133
|
try {
|
|
146
134
|
await callback();
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
log.catch(err, { context: this._name, callback: i, count: callbacks.length });
|
|
150
|
-
clean = false;
|
|
151
|
-
if (throwOnError) {
|
|
152
|
-
throw err;
|
|
153
|
-
}
|
|
135
|
+
} catch (error: any) {
|
|
136
|
+
log.catch(error);
|
|
154
137
|
}
|
|
155
138
|
}
|
|
156
|
-
|
|
157
|
-
resolveDispose(clean);
|
|
158
|
-
if (this._name) {
|
|
159
|
-
log('disposed', { context: this._name });
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return clean;
|
|
139
|
+
resolveDispose();
|
|
163
140
|
}
|
|
164
141
|
|
|
165
142
|
/**
|
|
@@ -198,7 +175,6 @@ export class Context {
|
|
|
198
175
|
},
|
|
199
176
|
attributes,
|
|
200
177
|
});
|
|
201
|
-
|
|
202
178
|
const clearDispose = this.onDispose(() => newCtx.dispose());
|
|
203
179
|
newCtx.onDispose(clearDispose);
|
|
204
180
|
return newCtx;
|
|
@@ -208,14 +184,14 @@ export class Context {
|
|
|
208
184
|
if (key in this._attributes) {
|
|
209
185
|
return this._attributes[key];
|
|
210
186
|
}
|
|
211
|
-
if (this._parent) {
|
|
187
|
+
if (this._parent !== null) {
|
|
212
188
|
return this._parent.getAttribute(key);
|
|
213
189
|
}
|
|
214
|
-
|
|
215
190
|
return undefined;
|
|
216
191
|
}
|
|
217
192
|
|
|
218
193
|
[Symbol.toStringTag] = 'Context';
|
|
194
|
+
|
|
219
195
|
[inspect.custom] = () => this.toString();
|
|
220
196
|
|
|
221
197
|
toString() {
|