Skip to content

Commit 1918001

Browse files
mertcanaltinaduh95
authored andcommitted
fs: cancel in-flight stat on abort
Signed-off-by: Mert Can Altin <mertgold60@gmail.com> PR-URL: #63142 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
1 parent 4b7abe8 commit 1918001

3 files changed

Lines changed: 48 additions & 0 deletions

File tree

lib/fs.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ function bindSignalToReq(req, signal, callback) {
395395
let aborted = false;
396396
const onAbort = () => {
397397
aborted = true;
398+
req.cancel();
398399
callback(new AbortError(undefined, { cause: signal.reason }));
399400
};
400401
kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation;

src/node_file.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,12 @@ void NewFSReqCallback(const FunctionCallbackInfo<Value>& args) {
758758
new FSReqCallback(binding_data, args.This(), args[0]->IsTrue());
759759
}
760760

761+
void CancelFSReq(const FunctionCallbackInfo<Value>& args) {
762+
FSReqBase* req_wrap;
763+
ASSIGN_OR_RETURN_UNWRAP(&req_wrap, args.This());
764+
req_wrap->Cancel();
765+
}
766+
761767
FSReqAfterScope::FSReqAfterScope(FSReqBase* wrap, uv_fs_t* req)
762768
: wrap_(wrap),
763769
req_(req),
@@ -4618,6 +4624,7 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
46184624
fst->InstanceTemplate()->SetInternalFieldCount(
46194625
FSReqBase::kInternalFieldCount);
46204626
fst->Inherit(AsyncWrap::GetConstructorTemplate(isolate_data));
4627+
SetProtoMethod(isolate, fst, "cancel", CancelFSReq);
46214628
SetConstructorFunction(isolate, target, "FSReqCallback", fst);
46224629

46234630
// Create FunctionTemplate for FileHandleReadWrap. There’s no need
@@ -4734,6 +4741,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
47344741
registry->Register(HandleToFd);
47354742
#endif
47364743
registry->Register(NewFSReqCallback);
4744+
registry->Register(CancelFSReq);
47374745

47384746
registry->Register(FileHandle::New);
47394747
registry->Register(FileHandle::Close);
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
'use strict';
2+
3+
// Aborting a queued stat must cancel the libuv request instead of running it.
4+
// The threadpool is saturated so the request is still queued when it is
5+
// cancelled; the queue is FIFO, so the stat cannot start before the blocker.
6+
7+
const common = require('../common');
8+
9+
if (!common.hasCrypto) common.skip('missing crypto');
10+
11+
const assert = require('assert');
12+
const crypto = require('crypto');
13+
const { spawnSyncAndExitWithoutError } = require('../common/child_process');
14+
15+
if (process.argv[2] !== 'child') {
16+
spawnSyncAndExitWithoutError(
17+
process.execPath,
18+
['--expose-internals', __filename, 'child'],
19+
{ env: { ...process.env, UV_THREADPOOL_SIZE: '1' } },
20+
);
21+
return;
22+
}
23+
24+
// A single worker is what keeps the stat queued behind the blocker below.
25+
assert.strictEqual(process.env.UV_THREADPOOL_SIZE, '1');
26+
27+
const { internalBinding } = require('internal/test/binding');
28+
const binding = internalBinding('fs');
29+
30+
// Occupy the only worker thread so the stat below cannot start.
31+
crypto.pbkdf2('secret', 'salt', 500_000, 32, 'sha512', common.mustCall());
32+
33+
const req = new binding.FSReqCallback(false);
34+
req.oncomplete = common.mustCall((err) => {
35+
// Without cancellation the stat would run and report success.
36+
assert.strictEqual(err?.code, 'ECANCELED');
37+
});
38+
binding.stat(__filename, false, req, true);
39+
req.cancel();

0 commit comments

Comments
 (0)