mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-12 16:10:30 +02:00
fix(core): Avoid Agent.close() deadlock in instance-ai web-research fetch (no-changelog) (#30105)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3123f2551b
commit
5bf5f03453
|
|
@ -302,4 +302,35 @@ describe('fetchAndExtract', () => {
|
||||||
).rejects.toThrow(/restricted IP/i);
|
).rejects.toThrow(/restricted IP/i);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not deadlock when the response body streams in chunks after fetch resolves', async () => {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const slowBody = new ReadableStream({
|
||||||
|
async start(controller) {
|
||||||
|
for (const part of ['<html>', '<body>', 'hello world', '</body>', '</html>']) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 5));
|
||||||
|
controller.enqueue(encoder.encode(part));
|
||||||
|
}
|
||||||
|
controller.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
globalThis.fetch = jest.fn().mockResolvedValue({
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
statusText: 'OK',
|
||||||
|
url: 'https://example.com/slow',
|
||||||
|
headers: new Headers({ 'content-type': 'text/html' }),
|
||||||
|
body: slowBody,
|
||||||
|
} as unknown as Response);
|
||||||
|
|
||||||
|
const result = await Promise.race([
|
||||||
|
fetchAndExtract('https://example.com/slow', { ssrf }),
|
||||||
|
new Promise<never>((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error('fetchAndExtract deadlocked')), 2000),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(result.content).toContain('hello world');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,8 @@ export async function fetchAndExtract(
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
await dispatcher.close();
|
// Fire-and-forget — awaiting Agent.close() deadlocks against an unread body.
|
||||||
|
void dispatcher.close().catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Follow redirects manually so each hop is SSRF-checked
|
// Follow redirects manually so each hop is SSRF-checked
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user