From ec25b6144d128116d949c142fe5dcd58671ce0b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 21:57:25 +0000 Subject: [PATCH 1/3] Initial plan From b6dee099df97e136a8ab05b45cf86cf5578796bb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 22:01:43 +0000 Subject: [PATCH 2/3] docs(utils): document buffered mode implementation with guarantees and limitations Co-authored-by: BioPhoton <10064416+BioPhoton@users.noreply.github.com> --- .../utils/src/lib/performance-observer.ts | 50 +++++++++++++++---- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/packages/utils/src/lib/performance-observer.ts b/packages/utils/src/lib/performance-observer.ts index 0c55f0ff2..b3a4db2b3 100644 --- a/packages/utils/src/lib/performance-observer.ts +++ b/packages/utils/src/lib/performance-observer.ts @@ -95,8 +95,16 @@ export type PerformanceObserverOptions = { /** * Whether to enable buffered observation mode. - * When true, captures all performance entries that occurred before observation started. - * When false, only captures entries after subscription begins. + * + * When true, captures all performance marks and measures that exist in the Node.js + * performance buffer at the time `subscribe()` is called. This allows you to capture + * performance entries that were created before the observer was created. + * + * When false, only captures entries created after `subscribe()` is called. + * + * **Important:** The implementation uses a manual approach via `performance.getEntriesByType()` + * rather than the native PerformanceObserver `buffered` option. See the `subscribe()` method + * documentation for details on guarantees and limitations. * * @default true */ @@ -312,6 +320,34 @@ export class PerformanceObserverSink { * When buffered mode is enabled, any existing buffered entries are immediately flushed. * If the sink is closed, items stay in the queue until reopened. * + * ## Buffered Mode Implementation + * + * When `captureBufferedEntries` is true, this method captures all performance entries + * that exist in the Node.js performance buffer at the time of subscription. + * + * **Why Manual Approach:** + * The standard `buffered: true` option in PerformanceObserver.observe() is not used + * because it has proven unreliable in Node.js environments. Instead, we use + * `performance.getEntriesByType()` to manually retrieve buffered entries. + * + * **Guarantees:** + * - All marks and measures in the performance buffer at subscription time will be captured + * - Entries are processed synchronously before the observer begins watching for new entries + * - No entries created after subscription will be missed (observer handles them) + * + * **Limitations:** + * - Potential for duplicate processing if an entry exists both in the buffer and is + * delivered by the observer callback (though Node.js typically avoids this) + * - Performance buffer has a limited size; very old entries may have been evicted by Node.js + * - The manual approach captures a snapshot at subscription time; there's a small race + * condition window where entries created during getEntriesByType() execution might + * be captured by both the manual call and the observer + * + * **Memory Management:** + * Applications should call `performance.clearMarks()` and `performance.clearMeasures()` + * periodically to prevent the Node.js performance buffer from growing unbounded. + * This is especially important when using buffered mode, as the entire buffer is + * processed on subscription. */ subscribe(): void { if (this.#observer) { @@ -322,11 +358,8 @@ export class PerformanceObserverSink { this.processPerformanceEntries(list.getEntries()); }); - // When buffered mode is enabled, Node.js PerformanceObserver invokes - // the callback synchronously with all buffered entries before observe() returns. - // However, entries created before any observer existed may not be buffered by Node.js. - // We manually retrieve entries from the performance buffer using getEntriesByType() - // to capture entries that were created before the observer was created. + // Manually capture buffered entries instead of using the native buffered option. + // See method documentation above for rationale and guarantees. if (this.#buffered) { const existingMarks = performance.getEntriesByType('mark'); const existingMeasures = performance.getEntriesByType('measure'); @@ -336,8 +369,7 @@ export class PerformanceObserverSink { this.#observer.observe({ entryTypes: OBSERVED_TYPES, - // @NOTE: This is for unknown reasons not working, and we manually do it above - // buffered: this.#buffered, + // Note: buffered option intentionally omitted. See method documentation above. }); } From 37cc84ef75d7c21d74cbed144a34c71700342085 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 23:38:58 +0000 Subject: [PATCH 3/3] docs(utils): reduce buffered mode documentation to one sentence Co-authored-by: BioPhoton <10064416+BioPhoton@users.noreply.github.com> --- .../utils/src/lib/performance-observer.ts | 46 ++----------------- 1 file changed, 5 insertions(+), 41 deletions(-) diff --git a/packages/utils/src/lib/performance-observer.ts b/packages/utils/src/lib/performance-observer.ts index b3a4db2b3..6c75f09e9 100644 --- a/packages/utils/src/lib/performance-observer.ts +++ b/packages/utils/src/lib/performance-observer.ts @@ -97,14 +97,8 @@ export type PerformanceObserverOptions = { * Whether to enable buffered observation mode. * * When true, captures all performance marks and measures that exist in the Node.js - * performance buffer at the time `subscribe()` is called. This allows you to capture - * performance entries that were created before the observer was created. - * - * When false, only captures entries created after `subscribe()` is called. - * - * **Important:** The implementation uses a manual approach via `performance.getEntriesByType()` - * rather than the native PerformanceObserver `buffered` option. See the `subscribe()` method - * documentation for details on guarantees and limitations. + * performance buffer at the time `subscribe()` is called using `performance.getEntriesByType()` + * (the native `buffered` option is unreliable in Node.js). * * @default true */ @@ -317,37 +311,8 @@ export class PerformanceObserverSink { * * Creates a Node.js PerformanceObserver that monitors 'mark' and 'measure' entries. * The observer uses a bounded queue with proactive flushing to manage memory usage. - * When buffered mode is enabled, any existing buffered entries are immediately flushed. + * When buffered mode is enabled, existing entries are captured via `performance.getEntriesByType()` instead of the unreliable native `buffered` option. * If the sink is closed, items stay in the queue until reopened. - * - * ## Buffered Mode Implementation - * - * When `captureBufferedEntries` is true, this method captures all performance entries - * that exist in the Node.js performance buffer at the time of subscription. - * - * **Why Manual Approach:** - * The standard `buffered: true` option in PerformanceObserver.observe() is not used - * because it has proven unreliable in Node.js environments. Instead, we use - * `performance.getEntriesByType()` to manually retrieve buffered entries. - * - * **Guarantees:** - * - All marks and measures in the performance buffer at subscription time will be captured - * - Entries are processed synchronously before the observer begins watching for new entries - * - No entries created after subscription will be missed (observer handles them) - * - * **Limitations:** - * - Potential for duplicate processing if an entry exists both in the buffer and is - * delivered by the observer callback (though Node.js typically avoids this) - * - Performance buffer has a limited size; very old entries may have been evicted by Node.js - * - The manual approach captures a snapshot at subscription time; there's a small race - * condition window where entries created during getEntriesByType() execution might - * be captured by both the manual call and the observer - * - * **Memory Management:** - * Applications should call `performance.clearMarks()` and `performance.clearMeasures()` - * periodically to prevent the Node.js performance buffer from growing unbounded. - * This is especially important when using buffered mode, as the entire buffer is - * processed on subscription. */ subscribe(): void { if (this.#observer) { @@ -358,8 +323,7 @@ export class PerformanceObserverSink { this.processPerformanceEntries(list.getEntries()); }); - // Manually capture buffered entries instead of using the native buffered option. - // See method documentation above for rationale and guarantees. + // Manually capture buffered entries instead of the unreliable native buffered option. if (this.#buffered) { const existingMarks = performance.getEntriesByType('mark'); const existingMeasures = performance.getEntriesByType('measure'); @@ -369,7 +333,7 @@ export class PerformanceObserverSink { this.#observer.observe({ entryTypes: OBSERVED_TYPES, - // Note: buffered option intentionally omitted. See method documentation above. + // Note: buffered option intentionally omitted due to unreliability in Node.js. }); }