wagmi: Memory leak when using useBlockNumber({watch: true}) -- creates a new websocket subscription on every block until it runs out of memory
Describe the bug
Consider a simple hook like this which consumes the block number for invalidation:
export const useAccountData = () => {
const queryClient = useQueryClient()
const { address } = useAccount()
const { data: blockNumber } = useBlockNumber({ chainId: activeChains.l2.id, watch: true })
const {
data: accountData,
isLoading,
queryKey,
} = useReadContract({
address: env.NEXT_PUBLIC_CONTRACT as `0x${string}`,
abi: fooAbi,
functionName: "fooFunction",
})
useEffect(() => {
void queryClient.invalidateQueries({ queryKey })
}, [blockNumber])
return { accountData, isLoading }
}
When this is used just one time in the app and you look at the websocket traffic, on every single block event it’s creating a new subscription and never closes the old ones. If you have 2 components consuming the hook, it will start 2 subscriptions with every block, etc for each consumer.
In the following screenshot notice it first gets 3 subscription responses, then starts a new one, then gets 4, etc. I ensured that hook above was only called/active one time, and setting watch:false resulted in no websocket activity.
The result is you will run out of memory and the RPC will rate limit the client because eventually it has too many subscriptions.
I tested this with QuickNode and the following:
"@web3modal/wagmi": "^4.0.11",
I tried the same pattern but in a context and it’s the same issue. The problem seems to be wagmi just keeps making new subscriptions.
If I instead use viem to get the block number, it works fine and only starts one subscription, even if I have several parts of the app consuming this hook for the block number:
export const useOnL2Block = () => {
const [blockNumber, setBlockNumber] = useState(BigInt(0))
const subscribe = useCallback(() => {
return watchBlockNumber(config.getClient(), {
onBlockNumber: (blockNumber) => {
setBlockNumber(blockNumber)
},
})
}, [])
useEffect(() => {
const unsubscribe = subscribe()
return () => unsubscribe()
}, [subscribe])
return { blockNumber }
}
export const useAccountData = () => {
const queryClient = useQueryClient()
const { address } = useAccount()
const { blockNumber } = useOnL2Block()
// const { data: blockNumber } = useBlockNumber({ chainId: activeChains.l2.id, watch: watch })
const {
data: accountData,
isLoading,
queryKey,
} = useReadContract({
address: env.NEXT_PUBLIC_CONTRACT as `0x${string}`,
abi: fooAbi,
functionName: "fooFunction",
})
useEffect(() => {
void queryClient.invalidateQueries({ queryKey })
}, [blockNumber])
return { accountData, isLoading }
}
As another side note, what’s the correct pattern to ensure there’s only one bock subscription in the app so i’m not making unnecessary rpc requests?
Link to Minimal Reproducible Example
No response
Steps To Reproduce
No response
Wagmi Version
2.6.5
Viem Version
2.7.16
TypeScript Version
5.3.3
Check existing issues
- I checked there isn’t already an issue for the bug I encountered.
Anything else?
No response
About this issue
- Original URL
- State: closed
- Created 4 months ago
- Reactions: 1
- Comments: 24 (11 by maintainers)
Whoops, not sure how this console.log got there. WIll check
Let’s move to PR to discuss this, yes there’s one test case to test. I’ll describe there in a sec
I’ll have a look
@acoutts I created a PR with a potential fix, not super happy with amount of code I had to add, as i’m not familiar with codebase just yet, but if you could check out the branch and test that would be great
playgrounds/next/src/wagmi.tsto add wss transport. ie.playgrounds/next/src/app/page.tsxLaunch with
pnpm dev:nextand check the network tabIs your View instance set to use polling by any chance @acoutts ?
Because Viem’s
watchBlockNumberis configured to useobservercorrectly, but the connection version with WebSocket is not. See https://github.com/wevm/viem/blob/main/src/actions/public/watchBlockNumber.tsMy best guess right now is that this is actually a Viem issue