@dxos/context 0.6.4 → 0.6.5-staging.42fccfe

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.
@@ -1,5 +1,6 @@
1
1
  // packages/common/context/src/context.ts
2
2
  import { inspect } from "@dxos/node-std/util";
3
+ import { StackTrace } from "@dxos/debug";
3
4
  import { log } from "@dxos/log";
4
5
  import { safeInstanceof } from "@dxos/util";
5
6
 
@@ -31,13 +32,15 @@ var DEFAULT_ERROR_HANDLER = (error, ctx) => {
31
32
  void ctx.dispose();
32
33
  throw error;
33
34
  };
35
+ var CONTEXT_FLAG_IS_DISPOSED = 1 << 0;
36
+ var CONTEXT_FLAG_LEAK_DETECTED = 1 << 1;
34
37
  var _a, _b;
35
38
  var Context = class _Context {
36
39
  constructor(params = {}, callMeta) {
37
40
  this.#disposeCallbacks = [];
38
41
  this.#name = void 0;
39
42
  this.#parent = void 0;
40
- this.#isDisposed = false;
43
+ this.#flags = 0;
41
44
  this.#disposePromise = void 0;
42
45
  this.maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
43
46
  this[_a] = "Context";
@@ -55,8 +58,20 @@ var Context = class _Context {
55
58
  #parent;
56
59
  #attributes;
57
60
  #onError;
58
- #isDisposed;
61
+ #flags;
59
62
  #disposePromise;
63
+ get #isDisposed() {
64
+ return !!(this.#flags & CONTEXT_FLAG_IS_DISPOSED);
65
+ }
66
+ set #isDisposed(value) {
67
+ this.#flags = value ? this.#flags | CONTEXT_FLAG_IS_DISPOSED : this.#flags & ~CONTEXT_FLAG_IS_DISPOSED;
68
+ }
69
+ get #leakDetected() {
70
+ return !!(this.#flags & CONTEXT_FLAG_LEAK_DETECTED);
71
+ }
72
+ set #leakDetected(value) {
73
+ this.#flags = value ? this.#flags | CONTEXT_FLAG_LEAK_DETECTED : this.#flags & ~CONTEXT_FLAG_LEAK_DETECTED;
74
+ }
60
75
  get disposed() {
61
76
  return this.#isDisposed;
62
77
  }
@@ -82,7 +97,7 @@ var Context = class _Context {
82
97
  context: this.#name
83
98
  }, {
84
99
  F: __dxlog_file,
85
- L: 90,
100
+ L: 116,
86
101
  S: this,
87
102
  C: (f, a) => f(...a)
88
103
  });
@@ -90,13 +105,16 @@ var Context = class _Context {
90
105
  })();
91
106
  }
92
107
  this.#disposeCallbacks.push(callback);
93
- if (this.#disposeCallbacks.length > this.maxSafeDisposeCallbacks) {
108
+ if (this.#disposeCallbacks.length > this.maxSafeDisposeCallbacks && !this.#leakDetected) {
109
+ this.#leakDetected = true;
110
+ const callSite = new StackTrace().getStackArray(1)[0].trim();
94
111
  log.warn("Context has a large number of dispose callbacks (this might be a memory leak).", {
95
112
  context: this.#name,
113
+ callSite,
96
114
  count: this.#disposeCallbacks.length
97
115
  }, {
98
116
  F: __dxlog_file,
99
- L: 97,
117
+ L: 125,
100
118
  S: this,
101
119
  C: (f, a) => f(...a)
102
120
  });
@@ -134,7 +152,7 @@ var Context = class _Context {
134
152
  count: callbacks.length
135
153
  }, {
136
154
  F: __dxlog_file,
137
- L: 141,
155
+ L: 170,
138
156
  S: this,
139
157
  C: (f, a) => f(...a)
140
158
  });
@@ -157,7 +175,7 @@ var Context = class _Context {
157
175
  count: callbacks.length
158
176
  }, {
159
177
  F: __dxlog_file,
160
- L: 156,
178
+ L: 185,
161
179
  S: this,
162
180
  C: (f, a) => f(...a)
163
181
  });
@@ -173,7 +191,7 @@ var Context = class _Context {
173
191
  context: this.#name
174
192
  }, {
175
193
  F: __dxlog_file,
176
- L: 167,
194
+ L: 196,
177
195
  S: this,
178
196
  C: (f, a) => f(...a)
179
197
  });
@@ -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 { type CallMetadata, log } from '@dxos/log';\nimport { safeInstanceof } from '@dxos/util';\n\nimport { ContextDisposedError } from './context-disposed-error';\n\nexport type ContextErrorHandler = (error: Error, ctx: Context) => 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\nconst DEBUG_LOG_DISPOSE = false;\n\n/**\n * Maximum number of dispose callbacks before we start logging warnings.\n */\nconst MAX_SAFE_DISPOSE_CALLBACKS = 300;\n\nconst DEFAULT_ERROR_HANDLER: ContextErrorHandler = (error, ctx) => {\n if (error instanceof ContextDisposedError) {\n return;\n }\n\n void ctx.dispose();\n\n // Will generate an unhandled rejection.\n throw error;\n};\n\n@safeInstanceof('Context')\nexport class Context {\n static default() {\n return new Context();\n }\n\n readonly #disposeCallbacks: DisposeCallback[] = [];\n\n readonly #name?: string = undefined;\n readonly #parent?: Context = undefined;\n readonly #attributes: Record<string, any>;\n readonly #onError: ContextErrorHandler;\n\n #isDisposed = false;\n #disposePromise?: Promise<boolean> = undefined;\n\n public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;\n\n constructor(params: CreateContextParams = {}, callMeta?: Partial<CallMetadata>) {\n this.#name = getContextName(params, callMeta);\n this.#parent = params.parent;\n this.#attributes = params.attributes ?? {};\n this.#onError = params.onError ?? DEFAULT_ERROR_HANDLER;\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, { context: this.#name });\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 context: this.#name,\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 * @returns true if there were no errors during the dispose process.\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 const promise = new Promise<boolean>((resolve) => {\n resolveDispose = resolve;\n });\n this.#disposePromise = promise;\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 (DEBUG_LOG_DISPOSE) {\n log('disposing', { context: this.#name, count: callbacks.length });\n }\n\n let i = 0;\n let clean = true;\n const errors: Error[] = [];\n for (const callback of callbacks) {\n try {\n await callback();\n i++;\n } catch (err: any) {\n clean = false;\n if (throwOnError) {\n errors.push(err);\n } else {\n log.catch(err, { context: this.#name, callback: i, count: callbacks.length });\n }\n }\n }\n\n if (errors.length > 0) {\n throw new AggregateError(errors);\n }\n\n resolveDispose(clean);\n if (DEBUG_LOG_DISPOSE) {\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, this);\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, this);\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 async [Symbol.asyncDispose](): Promise<void> {\n await this.dispose();\n }\n}\n\nconst getContextName = (params: CreateContextParams, callMeta?: Partial<CallMetadata>): string | undefined => {\n if (params.name) {\n return params.name;\n }\n if (callMeta?.F?.length) {\n const pathSegments = callMeta?.F.split('/');\n return `${pathSegments[pathSegments.length - 1]}#${callMeta?.L ?? 0}`;\n }\n return undefined;\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 { throwUnhandledError } from '@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// Feature flag to be enabled later.\nconst CLOSE_RESOURCE_ON_UNHANDLED_ERROR = false;\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 = this.#createParentContext();\n\n get #name() {\n return Object.getPrototypeOf(this).constructor.name;\n }\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 if (CLOSE_RESOURCE_ON_UNHANDLED_ERROR) {\n try {\n await this.close();\n } catch (doubleErr: any) {\n throwUnhandledError(doubleErr);\n }\n }\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 this.#parentCtx = ctx?.derive({ name: this.#name }) ?? this.#createParentContext();\n await this._open(this.#parentCtx);\n this.#lifecycleState = LifecycleState.OPEN;\n }\n\n async #close(ctx = Context.default()) {\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 name: this.#name,\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 #createParentContext() {\n return new Context({ name: this.#name });\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", "DEBUG_LOG_DISPOSE", "MAX_SAFE_DISPOSE_CALLBACKS", "DEFAULT_ERROR_HANDLER", "error", "ctx", "ContextDisposedError", "dispose", "Context", "constructor", "params", "callMeta", "undefined", "maxSafeDisposeCallbacks", "Symbol", "inspect", "toString", "getContextName", "parent", "attributes", "onError", "default", "disposed", "disposeCallbacksLength", "length", "onDispose", "callback", "log", "catch", "context", "push", "warn", "count", "index", "indexOf", "splice", "throwOnError", "resolveDispose", "promise", "Promise", "resolve", "callbacks", "Array", "from", "reverse", "i", "clean", "errors", "err", "AggregateError", "raise", "reject", "derive", "newCtx", "clearDispose", "getAttribute", "key", "toStringTag", "custom", "asyncDispose", "safeInstanceof", "name", "F", "pathSegments", "split", "L", "rejectOnDispose", "ctx", "error", "ContextDisposedError", "Promise", "resolve", "reject", "onDispose", "cancelWithContext", "promise", "clearDispose", "race", "finally", "throwUnhandledError", "LifecycleState", "CLOSE_RESOURCE_ON_UNHANDLED_ERROR", "Resource", "Object", "getPrototypeOf", "constructor", "name", "_lifecycleState", "_ctx", "_open", "ctx", "_close", "_catch", "err", "close", "doubleErr", "throwUnhandledError", "open", "Error", "Symbol", "asyncDispose", "derive", "Context", "default", "dispose", "onError", "error", "queueMicrotask", "raise", "openInContext", "resource", "onDispose"]
4
+ "sourcesContent": ["//\n// Copyright 2022 DXOS.org\n//\n\nimport { inspect } from 'node:util';\n\nimport { StackTrace } from '@dxos/debug';\nimport { type CallMetadata, log } from '@dxos/log';\nimport { safeInstanceof } from '@dxos/util';\n\nimport { ContextDisposedError } from './context-disposed-error';\n\nexport type ContextErrorHandler = (error: Error, ctx: Context) => 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\nconst DEBUG_LOG_DISPOSE = false;\n\n/**\n * Maximum number of dispose callbacks before we start logging warnings.\n */\nconst MAX_SAFE_DISPOSE_CALLBACKS = 300;\n\nconst DEFAULT_ERROR_HANDLER: ContextErrorHandler = (error, ctx) => {\n if (error instanceof ContextDisposedError) {\n return;\n }\n\n void ctx.dispose();\n\n // Will generate an unhandled rejection.\n throw error;\n};\n\ntype ContextFlags = number;\n\nconst CONTEXT_FLAG_IS_DISPOSED: ContextFlags = 1 << 0;\n\n/**\n * Whether the dispose callback leak was detected.\n */\nconst CONTEXT_FLAG_LEAK_DETECTED: ContextFlags = 1 << 1;\n\n@safeInstanceof('Context')\nexport class Context {\n static default() {\n return new Context();\n }\n\n readonly #disposeCallbacks: DisposeCallback[] = [];\n\n readonly #name?: string = undefined;\n readonly #parent?: Context = undefined;\n readonly #attributes: Record<string, any>;\n readonly #onError: ContextErrorHandler;\n\n #flags: ContextFlags = 0;\n #disposePromise?: Promise<boolean> = undefined;\n\n public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;\n\n constructor(params: CreateContextParams = {}, callMeta?: Partial<CallMetadata>) {\n this.#name = getContextName(params, callMeta);\n this.#parent = params.parent;\n this.#attributes = params.attributes ?? {};\n this.#onError = params.onError ?? DEFAULT_ERROR_HANDLER;\n }\n\n get #isDisposed() {\n return !!(this.#flags & CONTEXT_FLAG_IS_DISPOSED);\n }\n\n set #isDisposed(value: boolean) {\n this.#flags = value ? this.#flags | CONTEXT_FLAG_IS_DISPOSED : this.#flags & ~CONTEXT_FLAG_IS_DISPOSED;\n }\n\n get #leakDetected() {\n return !!(this.#flags & CONTEXT_FLAG_LEAK_DETECTED);\n }\n\n set #leakDetected(value: boolean) {\n this.#flags = value ? this.#flags | CONTEXT_FLAG_LEAK_DETECTED : this.#flags & ~CONTEXT_FLAG_LEAK_DETECTED;\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, { context: this.#name });\n }\n })();\n }\n\n this.#disposeCallbacks.push(callback);\n if (this.#disposeCallbacks.length > this.maxSafeDisposeCallbacks && !this.#leakDetected) {\n this.#leakDetected = true;\n const callSite = new StackTrace().getStackArray(1)[0].trim();\n log.warn('Context has a large number of dispose callbacks (this might be a memory leak).', {\n context: this.#name,\n callSite,\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 * @returns true if there were no errors during the dispose process.\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 const promise = new Promise<boolean>((resolve) => {\n resolveDispose = resolve;\n });\n this.#disposePromise = promise;\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 (DEBUG_LOG_DISPOSE) {\n log('disposing', { context: this.#name, count: callbacks.length });\n }\n\n let i = 0;\n let clean = true;\n const errors: Error[] = [];\n for (const callback of callbacks) {\n try {\n await callback();\n i++;\n } catch (err: any) {\n clean = false;\n if (throwOnError) {\n errors.push(err);\n } else {\n log.catch(err, { context: this.#name, callback: i, count: callbacks.length });\n }\n }\n }\n\n if (errors.length > 0) {\n throw new AggregateError(errors);\n }\n\n resolveDispose(clean);\n if (DEBUG_LOG_DISPOSE) {\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, this);\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, this);\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 async [Symbol.asyncDispose](): Promise<void> {\n await this.dispose();\n }\n}\n\nconst getContextName = (params: CreateContextParams, callMeta?: Partial<CallMetadata>): string | undefined => {\n if (params.name) {\n return params.name;\n }\n if (callMeta?.F?.length) {\n const pathSegments = callMeta?.F.split('/');\n return `${pathSegments[pathSegments.length - 1]}#${callMeta?.L ?? 0}`;\n }\n return undefined;\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 { throwUnhandledError } from '@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// Feature flag to be enabled later.\nconst CLOSE_RESOURCE_ON_UNHANDLED_ERROR = false;\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 = this.#createParentContext();\n\n get #name() {\n return Object.getPrototypeOf(this).constructor.name;\n }\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 if (CLOSE_RESOURCE_ON_UNHANDLED_ERROR) {\n try {\n await this.close();\n } catch (doubleErr: any) {\n throwUnhandledError(doubleErr);\n }\n }\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 this.#parentCtx = ctx?.derive({ name: this.#name }) ?? this.#createParentContext();\n await this._open(this.#parentCtx);\n this.#lifecycleState = LifecycleState.OPEN;\n }\n\n async #close(ctx = Context.default()) {\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 name: this.#name,\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 #createParentContext() {\n return new Context({ name: this.#name });\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", "StackTrace", "log", "safeInstanceof", "ContextDisposedError", "Error", "constructor", "DEBUG_LOG_DISPOSE", "MAX_SAFE_DISPOSE_CALLBACKS", "DEFAULT_ERROR_HANDLER", "error", "ctx", "ContextDisposedError", "dispose", "CONTEXT_FLAG_IS_DISPOSED", "CONTEXT_FLAG_LEAK_DETECTED", "Context", "constructor", "params", "callMeta", "undefined", "maxSafeDisposeCallbacks", "Symbol", "inspect", "toString", "getContextName", "parent", "attributes", "onError", "default", "value", "disposed", "disposeCallbacksLength", "length", "onDispose", "callback", "log", "catch", "context", "push", "callSite", "StackTrace", "getStackArray", "trim", "warn", "count", "index", "indexOf", "splice", "throwOnError", "resolveDispose", "promise", "Promise", "resolve", "callbacks", "Array", "from", "reverse", "i", "clean", "errors", "err", "AggregateError", "raise", "reject", "derive", "newCtx", "clearDispose", "getAttribute", "key", "toStringTag", "custom", "asyncDispose", "safeInstanceof", "name", "F", "pathSegments", "split", "L", "rejectOnDispose", "ctx", "error", "ContextDisposedError", "Promise", "resolve", "reject", "onDispose", "cancelWithContext", "promise", "clearDispose", "race", "finally", "throwUnhandledError", "LifecycleState", "CLOSE_RESOURCE_ON_UNHANDLED_ERROR", "Resource", "Object", "getPrototypeOf", "constructor", "name", "_lifecycleState", "_ctx", "_open", "ctx", "_close", "_catch", "err", "close", "doubleErr", "throwUnhandledError", "open", "Error", "Symbol", "asyncDispose", "derive", "Context", "default", "dispose", "onError", "error", "queueMicrotask", "raise", "openInContext", "resource", "onDispose"]
7
7
  }
@@ -1 +1 @@
1
- {"inputs":{"packages/common/context/src/context-disposed-error.ts":{"bytes":853,"imports":[],"format":"esm"},"packages/common/context/src/context.ts":{"bytes":25119,"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":3096,"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":14074,"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":809,"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":19523},"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":6814},"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":3476}},"bytes":11210}}}
1
+ {"inputs":{"packages/common/context/src/context-disposed-error.ts":{"bytes":853,"imports":[],"format":"esm"},"packages/common/context/src/context.ts":{"bytes":28261,"imports":[{"path":"@dxos/node-std/util","kind":"import-statement","external":true},{"path":"@dxos/debug","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":3096,"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":14074,"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":809,"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":20999},"packages/common/context/dist/lib/browser/index.mjs":{"imports":[{"path":"@dxos/node-std/util","kind":"import-statement","external":true},{"path":"@dxos/debug","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":7516},"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":3476}},"bytes":11912}}}
@@ -28,6 +28,7 @@ __export(node_exports, {
28
28
  });
29
29
  module.exports = __toCommonJS(node_exports);
30
30
  var import_node_util = require("node:util");
31
+ var import_debug = require("@dxos/debug");
31
32
  var import_log = require("@dxos/log");
32
33
  var import_util = require("@dxos/util");
33
34
  var import_util2 = require("@dxos/util");
@@ -56,13 +57,15 @@ var DEFAULT_ERROR_HANDLER = (error, ctx) => {
56
57
  void ctx.dispose();
57
58
  throw error;
58
59
  };
60
+ var CONTEXT_FLAG_IS_DISPOSED = 1 << 0;
61
+ var CONTEXT_FLAG_LEAK_DETECTED = 1 << 1;
59
62
  var _a, _b;
60
63
  var Context = class _Context {
61
64
  constructor(params = {}, callMeta) {
62
65
  this.#disposeCallbacks = [];
63
66
  this.#name = void 0;
64
67
  this.#parent = void 0;
65
- this.#isDisposed = false;
68
+ this.#flags = 0;
66
69
  this.#disposePromise = void 0;
67
70
  this.maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
68
71
  this[_a] = "Context";
@@ -80,8 +83,20 @@ var Context = class _Context {
80
83
  #parent;
81
84
  #attributes;
82
85
  #onError;
83
- #isDisposed;
86
+ #flags;
84
87
  #disposePromise;
88
+ get #isDisposed() {
89
+ return !!(this.#flags & CONTEXT_FLAG_IS_DISPOSED);
90
+ }
91
+ set #isDisposed(value) {
92
+ this.#flags = value ? this.#flags | CONTEXT_FLAG_IS_DISPOSED : this.#flags & ~CONTEXT_FLAG_IS_DISPOSED;
93
+ }
94
+ get #leakDetected() {
95
+ return !!(this.#flags & CONTEXT_FLAG_LEAK_DETECTED);
96
+ }
97
+ set #leakDetected(value) {
98
+ this.#flags = value ? this.#flags | CONTEXT_FLAG_LEAK_DETECTED : this.#flags & ~CONTEXT_FLAG_LEAK_DETECTED;
99
+ }
85
100
  get disposed() {
86
101
  return this.#isDisposed;
87
102
  }
@@ -107,7 +122,7 @@ var Context = class _Context {
107
122
  context: this.#name
108
123
  }, {
109
124
  F: __dxlog_file,
110
- L: 90,
125
+ L: 116,
111
126
  S: this,
112
127
  C: (f, a) => f(...a)
113
128
  });
@@ -115,13 +130,16 @@ var Context = class _Context {
115
130
  })();
116
131
  }
117
132
  this.#disposeCallbacks.push(callback);
118
- if (this.#disposeCallbacks.length > this.maxSafeDisposeCallbacks) {
133
+ if (this.#disposeCallbacks.length > this.maxSafeDisposeCallbacks && !this.#leakDetected) {
134
+ this.#leakDetected = true;
135
+ const callSite = new import_debug.StackTrace().getStackArray(1)[0].trim();
119
136
  import_log.log.warn("Context has a large number of dispose callbacks (this might be a memory leak).", {
120
137
  context: this.#name,
138
+ callSite,
121
139
  count: this.#disposeCallbacks.length
122
140
  }, {
123
141
  F: __dxlog_file,
124
- L: 97,
142
+ L: 125,
125
143
  S: this,
126
144
  C: (f, a) => f(...a)
127
145
  });
@@ -159,7 +177,7 @@ var Context = class _Context {
159
177
  count: callbacks.length
160
178
  }, {
161
179
  F: __dxlog_file,
162
- L: 141,
180
+ L: 170,
163
181
  S: this,
164
182
  C: (f, a) => f(...a)
165
183
  });
@@ -182,7 +200,7 @@ var Context = class _Context {
182
200
  count: callbacks.length
183
201
  }, {
184
202
  F: __dxlog_file,
185
- L: 156,
203
+ L: 185,
186
204
  S: this,
187
205
  C: (f, a) => f(...a)
188
206
  });
@@ -198,7 +216,7 @@ var Context = class _Context {
198
216
  context: this.#name
199
217
  }, {
200
218
  F: __dxlog_file,
201
- L: 167,
219
+ L: 196,
202
220
  S: this,
203
221
  C: (f, a) => f(...a)
204
222
  });
@@ -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 { type CallMetadata, log } from '@dxos/log';\nimport { safeInstanceof } from '@dxos/util';\n\nimport { ContextDisposedError } from './context-disposed-error';\n\nexport type ContextErrorHandler = (error: Error, ctx: Context) => 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\nconst DEBUG_LOG_DISPOSE = false;\n\n/**\n * Maximum number of dispose callbacks before we start logging warnings.\n */\nconst MAX_SAFE_DISPOSE_CALLBACKS = 300;\n\nconst DEFAULT_ERROR_HANDLER: ContextErrorHandler = (error, ctx) => {\n if (error instanceof ContextDisposedError) {\n return;\n }\n\n void ctx.dispose();\n\n // Will generate an unhandled rejection.\n throw error;\n};\n\n@safeInstanceof('Context')\nexport class Context {\n static default() {\n return new Context();\n }\n\n readonly #disposeCallbacks: DisposeCallback[] = [];\n\n readonly #name?: string = undefined;\n readonly #parent?: Context = undefined;\n readonly #attributes: Record<string, any>;\n readonly #onError: ContextErrorHandler;\n\n #isDisposed = false;\n #disposePromise?: Promise<boolean> = undefined;\n\n public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;\n\n constructor(params: CreateContextParams = {}, callMeta?: Partial<CallMetadata>) {\n this.#name = getContextName(params, callMeta);\n this.#parent = params.parent;\n this.#attributes = params.attributes ?? {};\n this.#onError = params.onError ?? DEFAULT_ERROR_HANDLER;\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, { context: this.#name });\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 context: this.#name,\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 * @returns true if there were no errors during the dispose process.\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 const promise = new Promise<boolean>((resolve) => {\n resolveDispose = resolve;\n });\n this.#disposePromise = promise;\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 (DEBUG_LOG_DISPOSE) {\n log('disposing', { context: this.#name, count: callbacks.length });\n }\n\n let i = 0;\n let clean = true;\n const errors: Error[] = [];\n for (const callback of callbacks) {\n try {\n await callback();\n i++;\n } catch (err: any) {\n clean = false;\n if (throwOnError) {\n errors.push(err);\n } else {\n log.catch(err, { context: this.#name, callback: i, count: callbacks.length });\n }\n }\n }\n\n if (errors.length > 0) {\n throw new AggregateError(errors);\n }\n\n resolveDispose(clean);\n if (DEBUG_LOG_DISPOSE) {\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, this);\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, this);\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 async [Symbol.asyncDispose](): Promise<void> {\n await this.dispose();\n }\n}\n\nconst getContextName = (params: CreateContextParams, callMeta?: Partial<CallMetadata>): string | undefined => {\n if (params.name) {\n return params.name;\n }\n if (callMeta?.F?.length) {\n const pathSegments = callMeta?.F.split('/');\n return `${pathSegments[pathSegments.length - 1]}#${callMeta?.L ?? 0}`;\n }\n return undefined;\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 { throwUnhandledError } from '@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// Feature flag to be enabled later.\nconst CLOSE_RESOURCE_ON_UNHANDLED_ERROR = false;\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 = this.#createParentContext();\n\n get #name() {\n return Object.getPrototypeOf(this).constructor.name;\n }\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 if (CLOSE_RESOURCE_ON_UNHANDLED_ERROR) {\n try {\n await this.close();\n } catch (doubleErr: any) {\n throwUnhandledError(doubleErr);\n }\n }\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 this.#parentCtx = ctx?.derive({ name: this.#name }) ?? this.#createParentContext();\n await this._open(this.#parentCtx);\n this.#lifecycleState = LifecycleState.OPEN;\n }\n\n async #close(ctx = Context.default()) {\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 name: this.#name,\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 #createParentContext() {\n return new Context({ name: this.#name });\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", "DEBUG_LOG_DISPOSE", "MAX_SAFE_DISPOSE_CALLBACKS", "DEFAULT_ERROR_HANDLER", "error", "ctx", "dispose", "Context", "params", "callMeta", "undefined", "maxSafeDisposeCallbacks", "Symbol", "inspect", "toString", "getContextName", "parent", "attributes", "onError", "default", "disposed", "disposeCallbacksLength", "length", "onDispose", "callback", "log", "catch", "context", "push", "warn", "count", "index", "indexOf", "splice", "throwOnError", "resolveDispose", "promise", "Promise", "resolve", "callbacks", "Array", "from", "reverse", "i", "clean", "errors", "err", "AggregateError", "raise", "reject", "derive", "newCtx", "clearDispose", "getAttribute", "key", "toStringTag", "custom", "asyncDispose", "safeInstanceof", "name", "F", "pathSegments", "split", "L", "rejectOnDispose", "cancelWithContext", "race", "finally", "LifecycleState", "CLOSE_RESOURCE_ON_UNHANDLED_ERROR", "Resource", "Object", "getPrototypeOf", "_lifecycleState", "_ctx", "_open", "_close", "_catch", "close", "doubleErr", "throwUnhandledError", "open", "queueMicrotask", "openInContext", "resource"]
4
+ "sourcesContent": ["//\n// Copyright 2022 DXOS.org\n//\n\nimport { inspect } from 'node:util';\n\nimport { StackTrace } from '@dxos/debug';\nimport { type CallMetadata, log } from '@dxos/log';\nimport { safeInstanceof } from '@dxos/util';\n\nimport { ContextDisposedError } from './context-disposed-error';\n\nexport type ContextErrorHandler = (error: Error, ctx: Context) => 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\nconst DEBUG_LOG_DISPOSE = false;\n\n/**\n * Maximum number of dispose callbacks before we start logging warnings.\n */\nconst MAX_SAFE_DISPOSE_CALLBACKS = 300;\n\nconst DEFAULT_ERROR_HANDLER: ContextErrorHandler = (error, ctx) => {\n if (error instanceof ContextDisposedError) {\n return;\n }\n\n void ctx.dispose();\n\n // Will generate an unhandled rejection.\n throw error;\n};\n\ntype ContextFlags = number;\n\nconst CONTEXT_FLAG_IS_DISPOSED: ContextFlags = 1 << 0;\n\n/**\n * Whether the dispose callback leak was detected.\n */\nconst CONTEXT_FLAG_LEAK_DETECTED: ContextFlags = 1 << 1;\n\n@safeInstanceof('Context')\nexport class Context {\n static default() {\n return new Context();\n }\n\n readonly #disposeCallbacks: DisposeCallback[] = [];\n\n readonly #name?: string = undefined;\n readonly #parent?: Context = undefined;\n readonly #attributes: Record<string, any>;\n readonly #onError: ContextErrorHandler;\n\n #flags: ContextFlags = 0;\n #disposePromise?: Promise<boolean> = undefined;\n\n public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;\n\n constructor(params: CreateContextParams = {}, callMeta?: Partial<CallMetadata>) {\n this.#name = getContextName(params, callMeta);\n this.#parent = params.parent;\n this.#attributes = params.attributes ?? {};\n this.#onError = params.onError ?? DEFAULT_ERROR_HANDLER;\n }\n\n get #isDisposed() {\n return !!(this.#flags & CONTEXT_FLAG_IS_DISPOSED);\n }\n\n set #isDisposed(value: boolean) {\n this.#flags = value ? this.#flags | CONTEXT_FLAG_IS_DISPOSED : this.#flags & ~CONTEXT_FLAG_IS_DISPOSED;\n }\n\n get #leakDetected() {\n return !!(this.#flags & CONTEXT_FLAG_LEAK_DETECTED);\n }\n\n set #leakDetected(value: boolean) {\n this.#flags = value ? this.#flags | CONTEXT_FLAG_LEAK_DETECTED : this.#flags & ~CONTEXT_FLAG_LEAK_DETECTED;\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, { context: this.#name });\n }\n })();\n }\n\n this.#disposeCallbacks.push(callback);\n if (this.#disposeCallbacks.length > this.maxSafeDisposeCallbacks && !this.#leakDetected) {\n this.#leakDetected = true;\n const callSite = new StackTrace().getStackArray(1)[0].trim();\n log.warn('Context has a large number of dispose callbacks (this might be a memory leak).', {\n context: this.#name,\n callSite,\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 * @returns true if there were no errors during the dispose process.\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 const promise = new Promise<boolean>((resolve) => {\n resolveDispose = resolve;\n });\n this.#disposePromise = promise;\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 (DEBUG_LOG_DISPOSE) {\n log('disposing', { context: this.#name, count: callbacks.length });\n }\n\n let i = 0;\n let clean = true;\n const errors: Error[] = [];\n for (const callback of callbacks) {\n try {\n await callback();\n i++;\n } catch (err: any) {\n clean = false;\n if (throwOnError) {\n errors.push(err);\n } else {\n log.catch(err, { context: this.#name, callback: i, count: callbacks.length });\n }\n }\n }\n\n if (errors.length > 0) {\n throw new AggregateError(errors);\n }\n\n resolveDispose(clean);\n if (DEBUG_LOG_DISPOSE) {\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, this);\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, this);\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 async [Symbol.asyncDispose](): Promise<void> {\n await this.dispose();\n }\n}\n\nconst getContextName = (params: CreateContextParams, callMeta?: Partial<CallMetadata>): string | undefined => {\n if (params.name) {\n return params.name;\n }\n if (callMeta?.F?.length) {\n const pathSegments = callMeta?.F.split('/');\n return `${pathSegments[pathSegments.length - 1]}#${callMeta?.L ?? 0}`;\n }\n return undefined;\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 { throwUnhandledError } from '@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// Feature flag to be enabled later.\nconst CLOSE_RESOURCE_ON_UNHANDLED_ERROR = false;\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 = this.#createParentContext();\n\n get #name() {\n return Object.getPrototypeOf(this).constructor.name;\n }\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 if (CLOSE_RESOURCE_ON_UNHANDLED_ERROR) {\n try {\n await this.close();\n } catch (doubleErr: any) {\n throwUnhandledError(doubleErr);\n }\n }\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 this.#parentCtx = ctx?.derive({ name: this.#name }) ?? this.#createParentContext();\n await this._open(this.#parentCtx);\n this.#lifecycleState = LifecycleState.OPEN;\n }\n\n async #close(ctx = Context.default()) {\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 name: this.#name,\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 #createParentContext() {\n return new Context({ name: this.#name });\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", "DEBUG_LOG_DISPOSE", "MAX_SAFE_DISPOSE_CALLBACKS", "DEFAULT_ERROR_HANDLER", "error", "ctx", "dispose", "CONTEXT_FLAG_IS_DISPOSED", "CONTEXT_FLAG_LEAK_DETECTED", "Context", "params", "callMeta", "undefined", "maxSafeDisposeCallbacks", "Symbol", "inspect", "toString", "getContextName", "parent", "attributes", "onError", "default", "value", "disposed", "disposeCallbacksLength", "length", "onDispose", "callback", "log", "catch", "context", "push", "callSite", "StackTrace", "getStackArray", "trim", "warn", "count", "index", "indexOf", "splice", "throwOnError", "resolveDispose", "promise", "Promise", "resolve", "callbacks", "Array", "from", "reverse", "i", "clean", "errors", "err", "AggregateError", "raise", "reject", "derive", "newCtx", "clearDispose", "getAttribute", "key", "toStringTag", "custom", "asyncDispose", "safeInstanceof", "name", "F", "pathSegments", "split", "L", "rejectOnDispose", "cancelWithContext", "race", "finally", "LifecycleState", "CLOSE_RESOURCE_ON_UNHANDLED_ERROR", "Resource", "Object", "getPrototypeOf", "_lifecycleState", "_ctx", "_open", "_close", "_catch", "close", "doubleErr", "throwUnhandledError", "open", "queueMicrotask", "openInContext", "resource"]
7
7
  }
@@ -1 +1 @@
1
- {"inputs":{"packages/common/context/src/context-disposed-error.ts":{"bytes":853,"imports":[],"format":"esm"},"packages/common/context/src/context.ts":{"bytes":25119,"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":3096,"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":14074,"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":809,"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":19523},"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":6804},"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":3476}},"bytes":11200}}}
1
+ {"inputs":{"packages/common/context/src/context-disposed-error.ts":{"bytes":853,"imports":[],"format":"esm"},"packages/common/context/src/context.ts":{"bytes":28261,"imports":[{"path":"node:util","kind":"import-statement","external":true},{"path":"@dxos/debug","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":3096,"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":14074,"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":809,"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":20999},"packages/common/context/dist/lib/node/index.cjs":{"imports":[{"path":"node:util","kind":"import-statement","external":true},{"path":"@dxos/debug","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":7506},"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":3476}},"bytes":11902}}}
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/context.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,KAAK,YAAY,EAAO,MAAM,WAAW,CAAC;AAKnD,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;AAEvE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;AAEvD,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,mBAAmB,CAAC;CAC/B,CAAC;AAoBF,qBACa,OAAO;;IAClB,MAAM,CAAC,OAAO;IAcP,uBAAuB,SAA8B;gBAEhD,MAAM,GAAE,mBAAwB,EAAE,QAAQ,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC;IAO9E,IAAI,QAAQ,YAEX;IAED,IAAI,sBAAsB,WAEzB;IAED;;;;;;;;OAQG;IACH,SAAS,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,IAAI;IA6BhD;;;;;;;OAOG;IACG,OAAO,CAAC,YAAY,UAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAqDrD;;;;OAIG;IACH,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAezB,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,GAAE,mBAAwB,GAAG,OAAO;IAsBlE,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG;IAW9B,CAAC,MAAM,CAAC,WAAW,CAAC,SAAa;IACjC,CAAC,OAAO,CAAC,MAAM,CAAC,eAAyB;IAEzC,QAAQ;IAIF,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7C"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/context.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,OAAO,EAAE,KAAK,YAAY,EAAO,MAAM,WAAW,CAAC;AAKnD,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;AAEvE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;AAEvD,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,mBAAmB,CAAC;CAC/B,CAAC;AA6BF,qBACa,OAAO;;IAClB,MAAM,CAAC,OAAO;IAcP,uBAAuB,SAA8B;gBAEhD,MAAM,GAAE,mBAAwB,EAAE,QAAQ,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC;IAuB9E,IAAI,QAAQ,YAEX;IAED,IAAI,sBAAsB,WAEzB;IAED;;;;;;;;OAQG;IACH,SAAS,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,IAAI;IAgChD;;;;;;;OAOG;IACG,OAAO,CAAC,YAAY,UAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAqDrD;;;;OAIG;IACH,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAezB,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,GAAE,mBAAwB,GAAG,OAAO;IAsBlE,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG;IAW9B,CAAC,MAAM,CAAC,WAAW,CAAC,SAAa;IACjC,CAAC,OAAO,CAAC,MAAM,CAAC,eAAyB;IAEzC,QAAQ;IAIF,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7C"}
package/package.json CHANGED
@@ -1,24 +1,33 @@
1
1
  {
2
2
  "name": "@dxos/context",
3
- "version": "0.6.4",
3
+ "version": "0.6.5-staging.42fccfe",
4
4
  "description": "Async utils.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
7
7
  "license": "MIT",
8
8
  "author": "DXOS.org",
9
- "main": "dist/lib/node/index.cjs",
10
- "browser": {
11
- "./dist/lib/node/index.cjs": "./dist/lib/browser/index.mjs"
9
+ "exports": {
10
+ ".": {
11
+ "browser": "./dist/lib/browser/index.mjs",
12
+ "node": {
13
+ "default": "./dist/lib/node/index.cjs"
14
+ },
15
+ "types": "./dist/types/src/index.d.ts"
16
+ }
12
17
  },
13
18
  "types": "dist/types/src/index.d.ts",
19
+ "typesVersions": {
20
+ "*": {}
21
+ },
14
22
  "files": [
15
23
  "dist",
16
24
  "src"
17
25
  ],
18
26
  "dependencies": {
19
- "@dxos/log": "0.6.4",
20
- "@dxos/util": "0.6.4",
21
- "@dxos/node-std": "0.6.4"
27
+ "@dxos/log": "0.6.5-staging.42fccfe",
28
+ "@dxos/util": "0.6.5-staging.42fccfe",
29
+ "@dxos/debug": "0.6.5-staging.42fccfe",
30
+ "@dxos/node-std": "0.6.5-staging.42fccfe"
22
31
  },
23
32
  "publishConfig": {
24
33
  "access": "public"
@@ -93,4 +93,19 @@ describe('Context', () => {
93
93
  await ctx.dispose();
94
94
  expect(order).toEqual([2, 1]);
95
95
  });
96
+
97
+ test('leak test', async () => {
98
+ const ctx = new Context();
99
+
100
+ ctx.maxSafeDisposeCallbacks = 1;
101
+
102
+ ctx.onDispose(() => {});
103
+
104
+ const triggerLeak = () => {
105
+ ctx.onDispose(() => {});
106
+ };
107
+
108
+ triggerLeak();
109
+ triggerLeak();
110
+ });
96
111
  });
package/src/context.ts CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { inspect } from 'node:util';
6
6
 
7
+ import { StackTrace } from '@dxos/debug';
7
8
  import { type CallMetadata, log } from '@dxos/log';
8
9
  import { safeInstanceof } from '@dxos/util';
9
10
 
@@ -38,6 +39,15 @@ const DEFAULT_ERROR_HANDLER: ContextErrorHandler = (error, ctx) => {
38
39
  throw error;
39
40
  };
40
41
 
42
+ type ContextFlags = number;
43
+
44
+ const CONTEXT_FLAG_IS_DISPOSED: ContextFlags = 1 << 0;
45
+
46
+ /**
47
+ * Whether the dispose callback leak was detected.
48
+ */
49
+ const CONTEXT_FLAG_LEAK_DETECTED: ContextFlags = 1 << 1;
50
+
41
51
  @safeInstanceof('Context')
42
52
  export class Context {
43
53
  static default() {
@@ -51,7 +61,7 @@ export class Context {
51
61
  readonly #attributes: Record<string, any>;
52
62
  readonly #onError: ContextErrorHandler;
53
63
 
54
- #isDisposed = false;
64
+ #flags: ContextFlags = 0;
55
65
  #disposePromise?: Promise<boolean> = undefined;
56
66
 
57
67
  public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
@@ -63,6 +73,22 @@ export class Context {
63
73
  this.#onError = params.onError ?? DEFAULT_ERROR_HANDLER;
64
74
  }
65
75
 
76
+ get #isDisposed() {
77
+ return !!(this.#flags & CONTEXT_FLAG_IS_DISPOSED);
78
+ }
79
+
80
+ set #isDisposed(value: boolean) {
81
+ this.#flags = value ? this.#flags | CONTEXT_FLAG_IS_DISPOSED : this.#flags & ~CONTEXT_FLAG_IS_DISPOSED;
82
+ }
83
+
84
+ get #leakDetected() {
85
+ return !!(this.#flags & CONTEXT_FLAG_LEAK_DETECTED);
86
+ }
87
+
88
+ set #leakDetected(value: boolean) {
89
+ this.#flags = value ? this.#flags | CONTEXT_FLAG_LEAK_DETECTED : this.#flags & ~CONTEXT_FLAG_LEAK_DETECTED;
90
+ }
91
+
66
92
  get disposed() {
67
93
  return this.#isDisposed;
68
94
  }
@@ -93,9 +119,12 @@ export class Context {
93
119
  }
94
120
 
95
121
  this.#disposeCallbacks.push(callback);
96
- if (this.#disposeCallbacks.length > this.maxSafeDisposeCallbacks) {
122
+ if (this.#disposeCallbacks.length > this.maxSafeDisposeCallbacks && !this.#leakDetected) {
123
+ this.#leakDetected = true;
124
+ const callSite = new StackTrace().getStackArray(1)[0].trim();
97
125
  log.warn('Context has a large number of dispose callbacks (this might be a memory leak).', {
98
126
  context: this.#name,
127
+ callSite,
99
128
  count: this.#disposeCallbacks.length,
100
129
  });
101
130
  }