question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Solve memory leak with circular dependencies of streams

See original GitHub issue

This is an issue I keep getting stuck at in different context. When the parent needs to listen of events of the child, how do you break or avoid a cycle?

e.g.


function List(sources) {
  const actions = intent({ ...sources, click$: ??? })
  const state = model(actions).shareReplay(1)

  const items$ = state
    .flatMapLatest(({ items, selected }) => 
      items.map(item => Item({ 
        ...sources, 
        props$: Observable.of({ id: item.id, selected: item.id === 'selected' })
      }))

  const click$ = items$.map(items => Observable.merge(...items.map(item => item.click$)))

  return {
    ...
  }
}

Basically the following flow:

click => List => selected => Item => click

Which gives me a cycle. How do I get around this? I could use a proxy as I’ve seen in some examples:

  const clickProxy = new Subject()
  const actions = intent({ ...sources, click$: clickProxy })

  const subscription = click$.subscribe(clickProxy)

  // subscription leaks...

However, that gives me a memory leak…

Issue Analytics

  • State:closed
  • Created 8 years ago
  • Comments:34 (17 by maintainers)

github_iconTop GitHub Comments

3reactions
wclrcommented, Apr 5, 2016

I believe leak with proxy subject can be avoided using finally that catches any termination of the stream:

var proxySubject = new Rx.Subject()

let source2 = proxySubject  
  .finally(() =>  proxySubscription.dispose())
  .map(x => x*2)

var source = Rx.Observable.timer(0, 1000)
.do(x => console.log('timer', x))

let proxySubscription = source.subscribe(proxySubject)

let sub = source.subscribe(x => {
    console.log('source', x)
})
let sub2 = source2.subscribe(x => {
    console.log(' source2', x)
})
setTimeout(() => {
  console.log('DISPOSE')
  sub.dispose()
  sub2.dispose()
}, 2500)

https://jsfiddle.net/jm4ke9h0/

babel plugin (https://github.com/cyclejs/core/issues/170) definitly will be needed to get rid of this dirt)

using construction is quite clumsy, especially if multiple proxy will be needed in function.

2reactions
ronagcommented, Feb 29, 2016

@staltz: This doesn’t feel quite solved.

Take the following more advanced example:

function Grid(sources) {
  const click$ = new Subject()

  const actions = intent({ ...sources, click$ })
  const state$ = model(actions).shareReplay(1)

  const items$ = state$
    .pluck('items')
    .flatMapLatest(items => items.map(id => Asset({
      ...sources,
      props$: state$
        .pluck('selected')
        .map(selected => selected.includes(id))
        .distinctUntilChanged()
        .map(selected => ({ id, selected })),
    })))
    .shareReplay(1)

  // Leaks
  items$
    .map(items => items.map(item => item.click$))
    .flatMapLatest(items => Observable.merge(...items))
    .subscribe(click$)

  return {
    DOM: view({
      state$,
      items$: items$
        .map(items => items.map(item => item.DOM))
        .flatMapLatest(items => items.length
          ? Observable.combineLatest(...items, (...items) => items)
          : Observable.of([])
        ),
    }),
    HTTP: 
      items$
        .map(items => items.map(item => item.HTTP))
        .flatMapLatest(items => Observable.merge(...items))
  })
}

What stream do you hook up with Observable.using. With my suggestion it’s kind of undefined and I am confused on how to apply your suggestion on this, since your example only returns a single sink.

i.e. the fundamental conceptual questions is, who owns the subscription? Is it the DOM?

const vtree$ = Observable.using(
  () => items$
    .map(items => items.map(item => item.click$))
    .flatMapLatest(items => Observable.merge(...items))
    .subscribe(click$),
  () => view({
      state$,
      items$: items$
        .map(items => items.map(item => item.DOM))
        .flatMapLatest(items => items.length
          ? Observable.combineLatest(...items, (...items) => items)
          : Observable.of([])
        ),
    })
  )

return {
    DOM: vtree$,
    HTTP: items$
      .map(items => items.map(item => item.HTTP))
      .flatMapLatest(items => Observable.merge(...items))
}

In that case we can create a more generic helper:

function using(sinks, ...disposables) {
  return {
    ...sinks,
    DOM: Observable.using(() => new CompositeDisposable(disposables), () => sinks.DOM),
  }
}

Which would give us:

const clickSubscription = items$
    .map(items => items.map(item => item.click$))
    .flatMapLatest(items => Observable.merge(...items))
    .subscribe(click$)

return using({
    DOM: view({
      state$,
      items$: items$
        .map(items => items.map(item => item.DOM))
        .flatMapLatest(items => items.length
          ? Observable.combineLatest(...items, (...items) => items)
          : Observable.of([])
        ),
    }),
    HTTP: 
      items$
        .map(items => items.map(item => item.HTTP))
        .flatMapLatest(items => Observable.merge(...items))
  }, clickSubscription)

I wonder whether it is enough to just dispose click$ or whether one has to dispose the actual subscription.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Circular References Cause Memory Leak? - Stack Overflow
Great question! No, Both forms will be (can be) GC'd because the GC does not directly look for references in other references.
Read more >
Avoiding Memory Leaks in Node.js: Best Practices for ...
Automatic memory management like garbage collection in V8 aims to avoid such memory leaks, for example, circular references are no longer a ...
Read more >
(1162701) Consistent Memory Leak on .NET 4.x Runtime Only ...
This accounts for both direct and indirect circular references and unfortunately it isn't possible for us to guarantee there are no circular ...
Read more >
Resolving Circular Reference Related Memory Leaks ... - DZone
This breaks the circular link and keeps the handler from referencing the DOM any longer.
Read more >
Few words about the Memory leak and SSR - Medium
Another way to solve the problem of memory leaks in Nodejs applications, ... I install a webpack plugin to detect circular dependencies.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found