Skip to content

The BehaviorSubject of useObservable could cause resource leaks #123

@darkyzhou

Description

@darkyzhou

Root cause:

export function useObservableInternal(...): Observable<TOutput> {
  if (!inputs) {
    return useState(init as () => Observable<TOutput>)[0]
  }

  const [inputs$] = useState(() => new BehaviorSubject(inputs))
  const [source$] = useState(() => init(inputs$))

  const firstEffectRef = useRef(true)
  useCustomEffect(() => {
    if (firstEffectRef.current) {
      firstEffectRef.current = false
      return
    }
    inputs$.next(inputs)
  }, inputs)

  // No `inputs$.complete()` presents :/

  return source$
}

When using operators like shareReplay(), they typically rely on the source observable(i.e. the BehaviorSubject inside useObservable()) to send a complete signal in order to unsubscribe the internal ReplaySubject from it. If no complete signal is received, resource leaks could occur.

Stackblitz URL: https://stackblitz.com/edit/stackblitz-starters-ghtjf1?devToolsHeight=33&file=src%2FApp.tsx

The example above is using RxJS 6. Haven't tested on RxJS 7 yet.

  • In the example, we use a toggle button to control the existence of FantasyGauge.
  • In FantasyGauge we use a modified version of useObservable with myShareReplay (which just logs some actions on top of original shareReplay logics from RxJS 6.2.1).
  • When FantasyGauge is destroyed, the BehaviorSubject of myUseObservable still holds some observers. See BEFORE.mp4 below.
  • When added inputs$.complete(), the BehaviorSubject no longer holds any observers. See AFTER.mp4 below.

I also find that useObservableRef and useObservableCallback both use BehaviorSubject or Subject under the hood, and both of them don't seem to call complete() either. So I suspect these hooks might also be vulnerable to resource leaks.

BEFORE.mp4
AFTER.mp4

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions