Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

libp2p.dialProtocol promises are retained in memory #2181

Closed
twoeths opened this issue Mar 17, 2021 · 5 comments
Closed

libp2p.dialProtocol promises are retained in memory #2181

twoeths opened this issue Mar 17, 2021 · 5 comments
Assignees

Comments

@twoeths
Copy link
Contributor

twoeths commented Mar 17, 2021

Describe the bug

This is from a heap snapshot I use to sync Pyrmont

retained_eth2_Stream

Expected behavior

  • The stream and Promise are not retained in memory
  • finalizeRoot should not carry the Tree object

Steps to Reproduce

  • Sync Pyrmont
@twoeths twoeths changed the title Stream is retained in Promise eth2 Stream is retained in Promise Mar 17, 2021
@twoeths
Copy link
Contributor Author

twoeths commented Mar 17, 2021

this is another view where the retained tree stay in finalizedRoot of Status message
retained_Tree_finalizedRoot

All retained trees in Status message takes ~200MB

@twoeths
Copy link
Contributor Author

twoeths commented Mar 18, 2021

so the libp2p.dialProtocol promises were retained in memory, and it keeps the stream with it while the stream.sink keeps the requestEncode generator along with finalizeRoot (and a tree)
retained_dialProtocol_promise

and the retained libp2p.dialProtocol promises happen with other protocols as well (beacon_blocks_by_range and ipfs etc.), not just status.

@twoeths twoeths changed the title eth2 Stream is retained in Promise libp2p.dialProtocol promises are retained in memory Mar 18, 2021
@dapplion
Copy link
Contributor

In regards to the finalizedRoot:

When accessing a property of the state, a new SubTree is created which holds a reference to the parent tree (the entire state). For example here:

this.fcStore.finalizedCheckpoint = state.finalizedCheckpoint;

ssz getter code: https://github.com/ChainSafe/ssz/blob/d6efe104a3bdc2d2beebc47af8234a3e40d414f4/src/backings/tree/container.ts#L142

getProperty<V extends keyof T>(target: Tree, property: V): PropOfTreeBacked<T, V> {
   ...
      return fieldType.tree.asTreeBacked(this.getSubtreeAtChunk(target, chunkIndex)) as PropOfTreeBacked<T, V>;
  }

persistent-merkle-tree code:
https://github.com/ChainSafe/persistent-merkle-tree/blob/44ec217a1531137ef9d9ebdd2fdf653559e3d4d6/src/tree.ts#L83

getSubtree(index: Gindex): Tree {
    return new Tree(this.getNode(index), (v: Tree): void => this.setNode(index, v.rootNode));
  }

So if someone grabs finalizedCheckpoint from the ForkChoice and keeps that for long the entire state won't ever be garbage collected.

  • Is this expected? This behavior can cause un-expected memory leaks very hard to trace since random variables could be holding to big state without being immediately obvious.
  • Could finalizedCheckpoint be cloned to break that reference?
  • Could the automatic binding of subTrees be configurable somehow? How often we need a binded subtree vs how often don't?

@dapplion
Copy link
Contributor

Proposed solution ChainSafe/ssz#83 (comment)

@dapplion
Copy link
Contributor

This seems to be fixed completely with ChainSafe/persistent-merkle-tree#26

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants