Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

fix: after provider.disconnect() is called, Ganache should stop serving requests #3433

Merged
merged 14 commits into from
Aug 19, 2022
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion src/chains/ethereum/ethereum/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,9 +420,9 @@ export class EthereumProvider
};

public disconnect = async () => {
this.#executor.disconnect();
await this.#blockchain.stop();
this.emit("disconnect");
return;
};

//#region legacy
Expand Down
10 changes: 10 additions & 0 deletions src/chains/ethereum/ethereum/tests/provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,16 @@ describe("provider", () => {
}
);
});

it("stops responding to RPC methods once disconnected", async () => {
const provider = await getProvider();
await provider.disconnect();

await assert.rejects(
provider.send("eth_getBlockByNumber", ["latest"]),
new Error("Cannot process request, Ganache is disconnected.")
);
});
});

describe("web3 compatibility", () => {
Expand Down
4 changes: 4 additions & 0 deletions src/packages/utils/src/utils/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export class Executor {
this.#requestCoordinator = requestCoordinator;
}

public disconnect() {
this.#requestCoordinator.disconnect();
}

/**
* Executes the method with the given methodName on the API
* @param methodName - The name of the JSON-RPC method to execute.
Expand Down
30 changes: 29 additions & 1 deletion src/packages/utils/src/utils/request-coordinator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { OverloadedParameters } from "../types";

const noop = () => {};
type RejectableTask = ((...args: any) => Promise<any>) & {
reject: (reason?: any) => void;
};

/**
* Responsible for managing global concurrent requests.
Expand All @@ -14,7 +17,7 @@ export class RequestCoordinator {
/**
* The pending requests. You can't do anything with this array.
*/
public readonly pending: ((...args: any) => Promise<any>)[] = [];
public readonly pending: RejectableTask[] = [];

/**
* The number of tasks currently being processed.
Expand Down Expand Up @@ -74,6 +77,30 @@ export class RequestCoordinator {
}
};

public disconnect() {
this.pause();
// ensure nothing can be requeued (although tasks can be added directly to this.pending
// but they will never be processed). We make this async to force a Promise return type.
this.queue = async () => {
throw new Error("Cannot process request, Ganache is disconnected.");
};

// ensure that processing cannot be resumed.
this.resume = () => {
throw new Error(
"Cannot resume processing requests, Ganache is disconnected."
);
};

// purge any pending tasks, respecting FIFO nature of the queue
while (this.pending.length > 0) {
const current = this.pending.shift();
jeffsmale90 marked this conversation as resolved.
Show resolved Hide resolved
current.reject(
new Error("Cannot process request, Ganache is disconnected.")
);
}
}

/**
* Insert a new function into the queue.
*/
Expand All @@ -97,6 +124,7 @@ export class RequestCoordinator {
reject(e);
}
};
executor.reject = reject;
jeffsmale90 marked this conversation as resolved.
Show resolved Hide resolved
this.pending.push(executor);
this.#process();
});
Expand Down
45 changes: 45 additions & 0 deletions src/packages/utils/tests/request-coordinator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import assert from "assert";
import { RequestCoordinator } from "../src/utils/request-coordinator";

describe("request-coordinator", () => {
let coordinator: RequestCoordinator;

beforeEach("instantiate RequestCoordinator", () => {
coordinator = new RequestCoordinator(0);
});

describe("disconnect", () => {
it("should pause processing", () => {
davidmurdoch marked this conversation as resolved.
Show resolved Hide resolved
coordinator.disconnect();

assert(coordinator.paused);
});

it("should not allow processing to be resumed", () => {
coordinator.disconnect();

assert.throws(
() => coordinator.resume(),
new Error("Cannot resume processing requests, Ganache is disconnected.")
);
});

it("should not allow new requests to be queued", async () => {
coordinator.disconnect();

await assert.rejects(
coordinator.queue(() => null, this, []),
new Error("Cannot process request, Ganache is disconnected.")
);
});

it("should reject all queued requests", async () => {
const taskPromise = coordinator.queue(() => null, this, []);
coordinator.disconnect();
await assert.rejects(
taskPromise,
new Error("Cannot process request, Ganache is disconnected.")
);
});
});
});