Skip to content

Commit c1cc10d

Browse files
RafaelGSStheanarkh
authored andcommitted
lib: reuse invalid state errors on webstreams
Signed-off-by: RafaelGSS <[email protected]> PR-URL: nodejs#46086 Reviewed-By: Robert Nagy <[email protected]> Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent 31ea7be commit c1cc10d

File tree

8 files changed

+119
-10
lines changed

8 files changed

+119
-10
lines changed

doc/api/process.md

+15
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,21 @@ and [Cluster][] documentation), the `process.connected` property will return
11031103
Once `process.connected` is `false`, it is no longer possible to send messages
11041104
over the IPC channel using `process.send()`.
11051105

1106+
## `process.constrainedMemory()`
1107+
1108+
<!-- YAML
1109+
added: REPLACEME
1110+
-->
1111+
1112+
* {number}
1113+
1114+
Gets the amount of memory available to the process (in bytes) based on
1115+
limits imposed by the OS. If there is no such constraint, or the constraint
1116+
is unknown, `0` is returned. It is not unusual for this value to
1117+
be less than or greater than `os.totalmem()`. This function currently only
1118+
returns a non-zero value on Linux, based on cgroups if it is present, and
1119+
on z/OS based on `RLIMIT_MEMLIMIT`.
1120+
11061121
## `process.cpuUsage([previousValue])`
11071122

11081123
<!-- YAML

lib/internal/bootstrap/node.js

+1
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ const rawMethods = internalBinding('process_methods');
184184

185185
process.hrtime = perThreadSetup.hrtime;
186186
process.hrtime.bigint = perThreadSetup.hrtimeBigInt;
187+
process.constrainedMemory = perThreadSetup.constrainedMemory;
187188

188189
process.openStdin = function() {
189190
process.stdin.resume();

lib/internal/process/per_thread.js

+8
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ const binding = internalBinding('process_methods');
6060

6161
let hrValues;
6262
let hrBigintValues;
63+
let constrainedMemoryValus;
6364

6465
function refreshHrtimeBuffer() {
6566
// The 3 entries filled in by the original process.hrtime contains
@@ -69,6 +70,7 @@ function refreshHrtimeBuffer() {
6970
// Use a BigUint64Array in the closure because this is actually a bit
7071
// faster than simply returning a BigInt from C++ in V8 7.1.
7172
hrBigintValues = new BigUint64Array(binding.hrtimeBuffer, 0, 1);
73+
constrainedMemoryValus = new BigUint64Array(binding.hrtimeBuffer, 0, 1);
7274
}
7375

7476
// Create the buffers.
@@ -100,6 +102,11 @@ function hrtimeBigInt() {
100102
return hrBigintValues[0];
101103
}
102104

105+
function constrainedMemory() {
106+
binding.constrainedMemory();
107+
return constrainedMemoryValus[0];
108+
}
109+
103110
function nop() {}
104111

105112
// The execution of this function itself should not cause any side effects.
@@ -427,4 +434,5 @@ module.exports = {
427434
hrtime,
428435
hrtimeBigInt,
429436
refreshHrtimeBuffer,
437+
constrainedMemory,
430438
};

lib/internal/webstreams/readablestream.js

+32-6
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ const {
5454
isArrayBufferDetached,
5555
kEmptyObject,
5656
kEnumerableProperty,
57+
SideEffectFreeRegExpPrototypeSymbolReplace,
5758
} = require('internal/util');
5859

5960
const {
@@ -140,6 +141,32 @@ const kError = Symbol('kError');
140141
const kPull = Symbol('kPull');
141142
const kRelease = Symbol('kRelease');
142143

144+
let releasedError;
145+
let releasingError;
146+
147+
const userModuleRegExp = /^ {4}at (?:[^/\\(]+ \()(?!node:(.+):\d+:\d+\)$).*/gm;
148+
149+
function lazyReadableReleasedError() {
150+
if (releasedError) {
151+
return releasedError;
152+
}
153+
154+
releasedError = new ERR_INVALID_STATE.TypeError('Reader released');
155+
// Avoid V8 leak and remove userland stackstrace
156+
releasedError.stack = SideEffectFreeRegExpPrototypeSymbolReplace(userModuleRegExp, releasedError.stack, '');
157+
return releasedError;
158+
}
159+
160+
function lazyReadableReleasingError() {
161+
if (releasingError) {
162+
return releasingError;
163+
}
164+
releasingError = new ERR_INVALID_STATE.TypeError('Releasing reader');
165+
// Avoid V8 leak and remove userland stackstrace
166+
releasingError.stack = SideEffectFreeRegExpPrototypeSymbolReplace(userModuleRegExp, releasingError.stack, '');
167+
return releasingError;
168+
}
169+
143170
const getNonWritablePropertyDescriptor = (value) => {
144171
return {
145172
__proto__: null,
@@ -2029,7 +2056,7 @@ function readableStreamDefaultReaderRelease(reader) {
20292056
readableStreamReaderGenericRelease(reader);
20302057
readableStreamDefaultReaderErrorReadRequests(
20312058
reader,
2032-
new ERR_INVALID_STATE.TypeError('Releasing reader')
2059+
lazyReadableReleasingError(),
20332060
);
20342061
}
20352062

@@ -2044,7 +2071,7 @@ function readableStreamBYOBReaderRelease(reader) {
20442071
readableStreamReaderGenericRelease(reader);
20452072
readableStreamBYOBReaderErrorReadIntoRequests(
20462073
reader,
2047-
new ERR_INVALID_STATE.TypeError('Releasing reader')
2074+
lazyReadableReleasingError(),
20482075
);
20492076
}
20502077

@@ -2062,13 +2089,12 @@ function readableStreamReaderGenericRelease(reader) {
20622089
assert(stream !== undefined);
20632090
assert(stream[kState].reader === reader);
20642091

2092+
const releasedStateError = lazyReadableReleasedError();
20652093
if (stream[kState].state === 'readable') {
2066-
reader[kState].close.reject?.(
2067-
new ERR_INVALID_STATE.TypeError('Reader released'));
2094+
reader[kState].close.reject?.(releasedStateError);
20682095
} else {
20692096
reader[kState].close = {
2070-
promise: PromiseReject(
2071-
new ERR_INVALID_STATE.TypeError('Reader released')),
2097+
promise: PromiseReject(releasedStateError),
20722098
resolve: undefined,
20732099
reject: undefined,
20742100
};

lib/internal/webstreams/writablestream.js

+18-4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const {
3434
createDeferredPromise,
3535
customInspectSymbol: kInspect,
3636
kEnumerableProperty,
37+
SideEffectFreeRegExpPrototypeSymbolReplace,
3738
} = require('internal/util');
3839

3940
const {
@@ -77,6 +78,20 @@ const kAbort = Symbol('kAbort');
7778
const kCloseSentinel = Symbol('kCloseSentinel');
7879
const kError = Symbol('kError');
7980

81+
let releasedError;
82+
83+
function lazyWritableReleasedError() {
84+
if (releasedError) {
85+
return releasedError;
86+
}
87+
const userModuleRegExp = /^ {4}at (?:[^/\\(]+ \()(?!node:(.+):\d+:\d+\)$).*/gm;
88+
89+
releasedError = new ERR_INVALID_STATE.TypeError('Writer has been released');
90+
// Avoid V8 leak and remove userland stackstrace
91+
releasedError.stack = SideEffectFreeRegExpPrototypeSymbolReplace(userModuleRegExp, releasedError.stack, '');
92+
return releasedError;
93+
}
94+
8095
const getNonWritablePropertyDescriptor = (value) => {
8196
return {
8297
__proto__: null,
@@ -970,10 +985,9 @@ function writableStreamDefaultWriterRelease(writer) {
970985
} = writer[kState];
971986
assert(stream !== undefined);
972987
assert(stream[kState].writer === writer);
973-
const releasedError =
974-
new ERR_INVALID_STATE.TypeError('Writer has been released');
975-
writableStreamDefaultWriterEnsureReadyPromiseRejected(writer, releasedError);
976-
writableStreamDefaultWriterEnsureClosedPromiseRejected(writer, releasedError);
988+
const releasedStateError = lazyWritableReleasedError();
989+
writableStreamDefaultWriterEnsureReadyPromiseRejected(writer, releasedStateError);
990+
writableStreamDefaultWriterEnsureClosedPromiseRejected(writer, releasedStateError);
977991
stream[kState].writer = undefined;
978992
writer[kState].stream = undefined;
979993
}

src/node_process.h

+6
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ class BindingData : public SnapshotableObject {
8181

8282
static void SlowBigInt(const v8::FunctionCallbackInfo<v8::Value>& args);
8383

84+
static void ConstrainedMemoryImpl(BindingData* receiver);
85+
static void SlowGetConstrainedMemory(
86+
const v8::FunctionCallbackInfo<v8::Value>& args);
87+
static void FastGetConstrainedMemory(v8::Local<v8::Value> receiver);
88+
8489
private:
8590
static constexpr size_t kBufferSize =
8691
std::max(sizeof(uint64_t), sizeof(uint32_t) * 3);
@@ -92,6 +97,7 @@ class BindingData : public SnapshotableObject {
9297
// time.
9398
static v8::CFunction fast_number_;
9499
static v8::CFunction fast_bigint_;
100+
static v8::CFunction fast_get_constrained_memory_;
95101
};
96102

97103
} // namespace process

src/node_process_methods.cc

+27
Original file line numberDiff line numberDiff line change
@@ -471,11 +471,18 @@ BindingData::BindingData(Environment* env, v8::Local<v8::Object> object)
471471

472472
v8::CFunction BindingData::fast_number_(v8::CFunction::Make(FastNumber));
473473
v8::CFunction BindingData::fast_bigint_(v8::CFunction::Make(FastBigInt));
474+
v8::CFunction BindingData::fast_get_constrained_memory_ =
475+
v8::CFunction::Make(FastGetConstrainedMemory);
474476

475477
void BindingData::AddMethods() {
476478
Local<Context> ctx = env()->context();
477479
SetFastMethod(ctx, object(), "hrtime", SlowNumber, &fast_number_);
478480
SetFastMethod(ctx, object(), "hrtimeBigInt", SlowBigInt, &fast_bigint_);
481+
SetFastMethod(ctx,
482+
object(),
483+
"constrainedMemory",
484+
SlowGetConstrainedMemory,
485+
&fast_get_constrained_memory_);
479486
}
480487

481488
void BindingData::RegisterExternalReferences(
@@ -486,6 +493,9 @@ void BindingData::RegisterExternalReferences(
486493
registry->Register(FastBigInt);
487494
registry->Register(fast_number_.GetTypeInfo());
488495
registry->Register(fast_bigint_.GetTypeInfo());
496+
registry->Register(SlowGetConstrainedMemory);
497+
registry->Register(FastGetConstrainedMemory);
498+
registry->Register(fast_get_constrained_memory_.GetTypeInfo());
489499
}
490500

491501
BindingData* BindingData::FromV8Value(Local<Value> value) {
@@ -533,6 +543,23 @@ void BindingData::SlowNumber(const v8::FunctionCallbackInfo<v8::Value>& args) {
533543
NumberImpl(FromJSObject<BindingData>(args.Holder()));
534544
}
535545

546+
void BindingData::ConstrainedMemoryImpl(BindingData* receiver) {
547+
// Make sure we don't accidentally access buffers wiped for snapshot.
548+
CHECK(!receiver->array_buffer_.IsEmpty());
549+
uint64_t t = uv_get_constrained_memory();
550+
uint64_t* fields = static_cast<uint64_t*>(receiver->backing_store_->Data());
551+
fields[0] = t;
552+
}
553+
554+
void BindingData::SlowGetConstrainedMemory(
555+
const FunctionCallbackInfo<Value>& args) {
556+
ConstrainedMemoryImpl(FromJSObject<BindingData>(args.Holder()));
557+
}
558+
559+
void BindingData::FastGetConstrainedMemory(v8::Local<v8::Value> receiver) {
560+
ConstrainedMemoryImpl(FromV8Value(receiver));
561+
}
562+
536563
bool BindingData::PrepareForSerialization(Local<Context> context,
537564
v8::SnapshotCreator* creator) {
538565
// It's not worth keeping.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
'use strict';
2+
require('../common');
3+
const assert = require('assert');
4+
const { Worker } = require('worker_threads');
5+
6+
if (!process.env.isWorker) {
7+
process.env.isWorker = true;
8+
new Worker(__filename);
9+
assert(process.constrainedMemory() >= 0);
10+
} else {
11+
assert(process.constrainedMemory() >= 0);
12+
}

0 commit comments

Comments
 (0)