@@ -11,6 +11,7 @@ import { createSingleCallFunction } from './functional.js';
1111import { combinedDisposable , Disposable , DisposableMap , DisposableStore , IDisposable , toDisposable } from './lifecycle.js' ;
1212import { LinkedList } from './linkedList.js' ;
1313import { IObservable , IObservableWithChange , IObserver } from './observable.js' ;
14+ import { env } from './process.js' ;
1415import { StopWatch } from './stopwatch.js' ;
1516import { MicrotaskDelay } from './symbols.js' ;
1617
@@ -31,6 +32,14 @@ const _enableSnapshotPotentialLeakWarning = false
3132 // || Boolean("TRUE") // causes a linter warning so that it cannot be pushed
3233 ;
3334
35+
36+ const _bufferLeakWarnCountThreshold = 100 ;
37+ const _bufferLeakWarnTimeThreshold = 60_000 ; // 1 minute
38+
39+ function _isBufferLeakWarningEnabled ( ) : boolean {
40+ return ! ! env [ 'VSCODE_DEV' ] ;
41+ }
42+
3443/**
3544 * An event with zero or one parameters that can be subscribed to. The event is a function itself.
3645 */
@@ -490,6 +499,7 @@ export namespace Event {
490499 * returned event causes this utility to leak a listener on the original event.
491500 *
492501 * @param event The event source for the new event.
502+ * @param debugName A name for this buffer, used in leak detection warnings.
493503 * @param flushAfterTimeout Determines whether to flush the buffer after a timeout immediately or after a
494504 * `setTimeout` when the first event listener is added.
495505 * @param _buffer Internal: A source event array used for tests.
@@ -499,15 +509,46 @@ export namespace Event {
499509 * // Start accumulating events, when the first listener is attached, flush
500510 * // the event after a timeout such that multiple listeners attached before
501511 * // the timeout would receive the event
502- * this.onInstallExtension = Event.buffer(service.onInstallExtension, true);
512+ * this.onInstallExtension = Event.buffer(service.onInstallExtension, 'onInstallExtension', true);
503513 * ```
504514 */
505- export function buffer < T > ( event : Event < T > , flushAfterTimeout = false , _buffer : T [ ] = [ ] , disposable ?: DisposableStore ) : Event < T > {
515+ export function buffer < T > ( event : Event < T > , debugName : string , flushAfterTimeout = false , _buffer : T [ ] = [ ] , disposable ?: DisposableStore ) : Event < T > {
506516 let buffer : T [ ] | null = _buffer . slice ( ) ;
507517
518+ // Dev-only leak detection: track when buffer was created and warn
519+ // if events accumulate without ever being consumed.
520+ let bufferLeakWarningData : { stack : Stacktrace ; timerId : ReturnType < typeof setTimeout > ; warned : boolean } | undefined ;
521+ if ( _isBufferLeakWarningEnabled ( ) ) {
522+ bufferLeakWarningData = {
523+ stack : Stacktrace . create ( ) ,
524+ timerId : setTimeout ( ( ) => {
525+ if ( buffer && buffer . length > 0 && bufferLeakWarningData && ! bufferLeakWarningData . warned ) {
526+ bufferLeakWarningData . warned = true ;
527+ console . warn ( `[Event.buffer][${ debugName } ] potential LEAK detected: ${ buffer . length } events buffered for ${ _bufferLeakWarnTimeThreshold / 1000 } s without being consumed. Buffered here:` ) ;
528+ bufferLeakWarningData . stack . print ( ) ;
529+ }
530+ } , _bufferLeakWarnTimeThreshold ) ,
531+ warned : false
532+ } ;
533+ if ( disposable ) {
534+ disposable . add ( toDisposable ( ( ) => clearTimeout ( bufferLeakWarningData ! . timerId ) ) ) ;
535+ }
536+ }
537+
538+ const clearLeakWarningTimer = ( ) => {
539+ if ( bufferLeakWarningData ) {
540+ clearTimeout ( bufferLeakWarningData . timerId ) ;
541+ }
542+ } ;
543+
508544 let listener : IDisposable | null = event ( e => {
509545 if ( buffer ) {
510546 buffer . push ( e ) ;
547+ if ( _isBufferLeakWarningEnabled ( ) && bufferLeakWarningData && ! bufferLeakWarningData . warned && buffer . length >= _bufferLeakWarnCountThreshold ) {
548+ bufferLeakWarningData . warned = true ;
549+ console . warn ( `[Event.buffer][${ debugName } ] potential LEAK detected: ${ buffer . length } events buffered without being consumed. Buffered here:` ) ;
550+ bufferLeakWarningData . stack . print ( ) ;
551+ }
511552 } else {
512553 emitter . fire ( e ) ;
513554 }
@@ -520,6 +561,7 @@ export namespace Event {
520561 const flush = ( ) => {
521562 buffer ?. forEach ( e => emitter . fire ( e ) ) ;
522563 buffer = null ;
564+ clearLeakWarningTimer ( ) ;
523565 } ;
524566
525567 const emitter = new Emitter < T > ( {
@@ -547,6 +589,7 @@ export namespace Event {
547589 listener . dispose ( ) ;
548590 }
549591 listener = null ;
592+ clearLeakWarningTimer ( ) ;
550593 }
551594 } ) ;
552595
0 commit comments