Skip to content

bug: memory leaks related to go-ethereum DialContext #4857

Description

@johnsaigle

While you're here, you may want to look into testing rpc.DialContext as well. GPT is telling me the following. Take it or leave it -- also feel free to capture in a GitHub Issue and resolve separately. I just wanted to note it here in case the memory leaks are interfering with your work. If so, you could look into DialContext as well to help get unstuck.

Why Context Cancel Is Not Enough
In go-ethereum v1.10.21, rpc.DialContext says the context only controls initial connection establishment. It does not close the resulting client.
For non-HTTP clients, initClient starts dispatch/read goroutines:
if !isHTTP {
    go c.dispatch(conn)
}
dispatch starts the read loop:
go c.read(codec)
The intended shutdown path is Client.Close():
func (c *Client) Close() {
    if c.isHTTP {
        return
    }
    select {
    case c.close <- struct{}{}:
        <-c.didClose
    case <-c.didClose:
    }
}
So defer cancel() only cancels the timeout context. It does not signal c.close, does not wait on c.didClose, and does not terminate those goroutines.
Proper Fix
Close immediately after a successful dial:
c, err := rpc.DialContext(timeout, url)
if err != nil {
    return fmt.Errorf("failed to connect to endpoint: %w", err)
}
defer c.Close()
Same pattern in getFinality:
c, err := rpc.DialContext(timeout, w.url)
if err != nil {
    return false, false, fmt.Errorf("failed to connect to endpoint: %w", err)
}
defer c.Close()
That ensures every return path after successful dial closes the temporary client, including error returns from the RPC call or response parsing.

Originally posted by @johnsaigle in #4852 (review)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions