Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/wild-pets-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"hardhat": patch
---

Optimize the network connections to prevent memory leaks.
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ export class EdrProvider extends BaseProvider {

let edrProvider: EdrProvider;

// We use a WeakRef to the provider to prevent the subscriptionCallback
// below from creating a cycle and leaking the provider.
let edrProviderWeakRef: WeakRef<EdrProvider> | undefined;

// We need to catch errors here, as the provider creation can panic unexpectedly,
// and we want to make sure such a crash is propagated as a ProviderError.
try {
Expand All @@ -191,13 +195,17 @@ export class EdrProvider extends BaseProvider {
},
{
subscriptionCallback: (event: SubscriptionEvent) => {
edrProvider.onSubscriptionEvent(event);
const deferredProvider = edrProviderWeakRef?.deref();
if (deferredProvider !== undefined) {
deferredProvider.onSubscriptionEvent(event);
}
Comment on lines 197 to +201
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

edrProviderWeakRef is assigned only after createProvider(...) resolves, but it’s dereferenced unconditionally in subscriptionCallback. If the callback is invoked before edrProviderWeakRef is assigned (e.g., if the underlying provider emits an event during initialization), this will throw when calling .deref(). Consider making edrProviderWeakRef optional (WeakRef<EdrProvider> | undefined) and guarding before dereferencing.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in 166f6e6

},
},
contractDecoder,
);

edrProvider = new EdrProvider(provider, jsonRpcRequestWrapper);
edrProviderWeakRef = new WeakRef(edrProvider);
} catch (error) {
ensureError(error);

Expand Down Expand Up @@ -282,6 +290,7 @@ export class EdrProvider extends BaseProvider {
}

public async close(): Promise<void> {
this.removeAllListeners();
// Clear the provider reference to help with garbage collection
this.#provider = undefined;
Comment thread
alcuadrado marked this conversation as resolved.
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export class HttpProvider extends BaseProvider {
}

public async close(): Promise<void> {
this.removeAllListeners();
if (this.#dispatcher !== undefined) {
Comment thread
alcuadrado marked this conversation as resolved.
// See https://github.com/nodejs/undici/discussions/3522#discussioncomment-10498734
await this.#dispatcher.close();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,17 @@ describe("edr-provider", () => {
{},
);
});

it("should remove all listeners after closing", async () => {
const connection = await hre.network.connect();

connection.provider.on("notification", () => {});
assert.equal(connection.provider.listenerCount("notification"), 1);

await connection.provider.close();

assert.equal(connection.provider.listenerCount("notification"), 0);
});
});

describe("isDefaultEdrNetworkHDAccountsConfig", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -579,5 +579,20 @@ describe("http-provider", () => {
{},
);
});

it("should remove all listeners after closing", async () => {
const provider = await HttpProvider.create({
url: "http://localhost",
networkName: "exampleNetwork",
timeout: 20_000,
});

provider.on("notification", () => {});
assert.equal(provider.listenerCount("notification"), 1);

await provider.close();

assert.equal(provider.listenerCount("notification"), 0);
});
});
});
Loading