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);
|
||||
});
|
||||
});
|
||||
|
||||
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 {
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user