Skip to content

Commit b6dee09

Browse files
CopilotBioPhoton
andcommitted
docs(utils): document buffered mode implementation with guarantees and limitations
Co-authored-by: BioPhoton <10064416+BioPhoton@users.noreply.github.com>
1 parent ec25b61 commit b6dee09

File tree

1 file changed

+41
-9
lines changed

1 file changed

+41
-9
lines changed

packages/utils/src/lib/performance-observer.ts

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,16 @@ export type PerformanceObserverOptions<T> = {
9595

9696
/**
9797
* Whether to enable buffered observation mode.
98-
* When true, captures all performance entries that occurred before observation started.
99-
* When false, only captures entries after subscription begins.
98+
*
99+
* When true, captures all performance marks and measures that exist in the Node.js
100+
* performance buffer at the time `subscribe()` is called. This allows you to capture
101+
* performance entries that were created before the observer was created.
102+
*
103+
* When false, only captures entries created after `subscribe()` is called.
104+
*
105+
* **Important:** The implementation uses a manual approach via `performance.getEntriesByType()`
106+
* rather than the native PerformanceObserver `buffered` option. See the `subscribe()` method
107+
* documentation for details on guarantees and limitations.
100108
*
101109
* @default true
102110
*/
@@ -312,6 +320,34 @@ export class PerformanceObserverSink<T> {
312320
* When buffered mode is enabled, any existing buffered entries are immediately flushed.
313321
* If the sink is closed, items stay in the queue until reopened.
314322
*
323+
* ## Buffered Mode Implementation
324+
*
325+
* When `captureBufferedEntries` is true, this method captures all performance entries
326+
* that exist in the Node.js performance buffer at the time of subscription.
327+
*
328+
* **Why Manual Approach:**
329+
* The standard `buffered: true` option in PerformanceObserver.observe() is not used
330+
* because it has proven unreliable in Node.js environments. Instead, we use
331+
* `performance.getEntriesByType()` to manually retrieve buffered entries.
332+
*
333+
* **Guarantees:**
334+
* - All marks and measures in the performance buffer at subscription time will be captured
335+
* - Entries are processed synchronously before the observer begins watching for new entries
336+
* - No entries created after subscription will be missed (observer handles them)
337+
*
338+
* **Limitations:**
339+
* - Potential for duplicate processing if an entry exists both in the buffer and is
340+
* delivered by the observer callback (though Node.js typically avoids this)
341+
* - Performance buffer has a limited size; very old entries may have been evicted by Node.js
342+
* - The manual approach captures a snapshot at subscription time; there's a small race
343+
* condition window where entries created during getEntriesByType() execution might
344+
* be captured by both the manual call and the observer
345+
*
346+
* **Memory Management:**
347+
* Applications should call `performance.clearMarks()` and `performance.clearMeasures()`
348+
* periodically to prevent the Node.js performance buffer from growing unbounded.
349+
* This is especially important when using buffered mode, as the entire buffer is
350+
* processed on subscription.
315351
*/
316352
subscribe(): void {
317353
if (this.#observer) {
@@ -322,11 +358,8 @@ export class PerformanceObserverSink<T> {
322358
this.processPerformanceEntries(list.getEntries());
323359
});
324360

325-
// When buffered mode is enabled, Node.js PerformanceObserver invokes
326-
// the callback synchronously with all buffered entries before observe() returns.
327-
// However, entries created before any observer existed may not be buffered by Node.js.
328-
// We manually retrieve entries from the performance buffer using getEntriesByType()
329-
// to capture entries that were created before the observer was created.
361+
// Manually capture buffered entries instead of using the native buffered option.
362+
// See method documentation above for rationale and guarantees.
330363
if (this.#buffered) {
331364
const existingMarks = performance.getEntriesByType('mark');
332365
const existingMeasures = performance.getEntriesByType('measure');
@@ -336,8 +369,7 @@ export class PerformanceObserverSink<T> {
336369

337370
this.#observer.observe({
338371
entryTypes: OBSERVED_TYPES,
339-
// @NOTE: This is for unknown reasons not working, and we manually do it above
340-
// buffered: this.#buffered,
372+
// Note: buffered option intentionally omitted. See method documentation above.
341373
});
342374
}
343375

0 commit comments

Comments
 (0)