-
Notifications
You must be signed in to change notification settings - Fork 107
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
AbortController break fetch after first timeout because aborted signal can't be re-used #861
Comments
Hello @lbeschastny, I would to work on this with you but it seem like it is sait that "The intent is that when a circuit times out, an assumption is made that all other ongoing requests to the same endpoint will time out as well". That is why the subsequent request failed. I think what we need is a reset timeout for the abort controller. |
@cedrick-ah the problem here is not about other ongoing requests since this limitation is clearly stated in the readme. The problem is that all new requests are immediately aborted by |
Okay, maybe you can try this approach to change abort for each request. const { describe, it, before, after } = require('node:test');
const assert = require('node:assert/strict');
const http = require('node:http');
const CircuitBreaker = require('opossum');
describe('Circuit breaker with abort controller', () => {
let abortController;
const get = timeout => {
const { port } = server.address();
abortController = new AbortController();
const { signal } = abortController;
const headers = { 'x-timeout': timeout };
return fetch(`http://localhost:${port}`, { signal, headers });
};
const breaker = new CircuitBreaker(get, {
abortController,
timeout: 1000,
volumeThreshold: 3
});
const server = http.createServer((req, res) => {
const timeout = Number(req.headers['x-timeout']);
setTimeout(() => res.end(`delayed for ${timeout}ms`), timeout);
});
before(() => server.listen());
after(() => server.close());
it('Should work', async () => {
const response = await breaker.fire(100);
const text = await response.text();
assert.equal(text, 'delayed for 100ms');
});
it('Should timeout', async () => {
await assert.rejects(() => breaker.fire(2000), {
message: 'Timed out after 1000ms'
});
});
it('Should work again', async () => {
try {
await breaker.fire(100);
} catch ({ name, message }) {
throw Object.assign(new Error(message), { name });
}
});
});
|
@cedrick-ah you create new So, requests just newer get aborted. |
It will be defined after breaker.fire() is called. Also, abort controller and signal only come in pairs as you said earlier. I tested and the new requests was not aborted. |
@cedrick-ah I updated my Minimal reproducible example with an additional assertion to verify that the operation was actually aborted. |
Okay, I understand. |
This issue is stale because it has been open 30 days with no activity. |
We are in the same situation, we use "static" But the design choice here according to the documentation is to create an
I have an idea for a nice and easy to understand API:
Boom 😄 ...and consider getting rid of |
This issue is stale because it has been open 30 days with no activity. |
I'm suggesting a solution where we reuse the same I propose adding a new option to the Example: const options = {
timeout: 300,
resetTimeout: 100,
autoRenewSignal: true, // new flag
};
const breaker = new CircuitBreaker(asyncFunctionThatCouldFail, options);
const signal = breaker.getSignal(); // get the current signal
if the circuit is 'open', return an aborted signal.
If the state is 'halfOpen' or 'close', return a fresh, non-aborted signal. |
That could an interesting addition. would you like to send a PR for it? |
…pecific states When state changes to 'halfOpen' or 'close', the AbortController will be recreated to handle reuse. nodeshift#861
…pecific states When state changes to 'halfOpen' or 'close', the AbortController will be recreated to handle reuse. nodeshift#861
PR: #892 |
…lose' or 'close' state (#892) * feat: add option to auto-recreate AbortController if aborted during specific states When state changes to 'halfOpen' or 'close', the AbortController will be recreated to handle reuse. #861 * feat(circuit-breaker): extract abort controller renewal logic into separate function #861 * docs: update README to include autoRenewAbortController configuration options #861
Node.js Version:
Operating System:
The problem:
AbortSignal
both stores it's state and provides anabort
event to signal when abort is happening.And
AbortController
newer updates it'ssignal
, leaving it inaborted
state forever:It looks like
undici
fetch uses bothaborted
property andabort
event to skip sending the request if it is already aborted.So, once the action times out for the first time, it switches
AbortController
intoaborted
state and all subsequent fetch calls just abort immediately.Minimal reproducible example:
Possible ways to fix this:
It seems we cannot re-use the same
AbortController
andAbortSignal
pair for all requests sinceAbortSignal
preserves itsaborted
state.I can only see one way to fix this problem - use new
AbortSignal
for every requests (see #780).For example,
opossum
could pass thesignal
object as the first argument to the action handler.The text was updated successfully, but these errors were encountered: