-
Notifications
You must be signed in to change notification settings - Fork 30.1k
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
buffer: auto random fill Buffer(num) and new Buffer(num) #11808
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jasnell Could you clarify what makes this less disruptive than the other one? |
@ChALkeR ... thanks for the pointer on that, I had remembered seeing it before but couldn't remember where. I've incorporated the workaround and the test. PTAL @seishun rightfully mentioned in the other alternate PR (which I've closed in favor of this one) that is is a breaking change for performance critical apps that are still using the @seishun ... it's less disruptive in terms of the code changes for the zero-fill (only touches buffer.js, should only give perf impact to |
Benchmark results so far:
(Command line: IMO unless we can make the results significantly better (and on every supported platform), this warrants a full deprecation cycle. |
|
||
process.on('warning', common.mustNotCall((warning) => {})); | ||
|
||
Buffer.alloc(10).map((i) => {}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: it won't hurt to test other methods here, e.g. Buffer.alloc(10).filter(() => true)
, which also would have been affected if not for the fix above.
@jasnell Could I ask you to split this into two separate PRs for a separate review? Opt-in |
Yes, splitting it into two separate PRs is fine. Just to make sure I'm clear, that's:
Correct? |
@jasnell, No, I meant the other way around:
The opt-in flag for |
Selects a random byte and fills Buffer(num) and new Buffer(num) automatically using the randomly selected value. Why random-fill vs. zero-fill? Prior to this commit, the uninitialized Buffer allocation would be potentially filled with non-zeroed data. By filling with a randomly selected value, we eliminate the possibility of leaking uninitialized data but we preserve the need for users to completely over-write the Buffer contents in order for it to be useful. Zero-filling by default would *potentially* put users at risk if module developers assume that the Buffer instance will always be zero-filled (which will not be the case on older versions of Node.js). The cost, however, is that filling with a randomly selected value is a bit less performant than zero-filling on allocation. Note that Buffer.allocUnsafe() and Buffer.alloc() are *not* affected by this change. Buffer.allocUnsafe() will returns a Buffer with uninitialized data and Buffer.alloc() returns *zero-filled* data.
The PRs have been split |
For the record, I will say that I am definitely not yet convinced that random-fill is the right approach to take here. It may be easier and better just to zero-fill from 8.0.0 and on. |
I'm not convinced about this for reasons that I already mentioned. Currently, I'm -1 on this in its default form (just landing this without any additional actions). I'm +0 on this if this would be paired with a strong commitment to runtime-deprecating without a flag in the next version after the random/zero-fill lands. That is for the case if this won't get backported. I'm also -1 to a partial backport, without landing a runtime deprecation at the same time. I'm +1 to landing zero/randomfill at the time when a runtime deprecation is landed, with or without partial or full backport at the time the version that will contain the deprecation will be released. Given the current situation, I propose to just postpone the zero/randomfill decision to 9.0 (because we probably won't land a runtime deprecation by default in 9.0) and observe the ecosystem usage. To explain: I am afraid that this could make things worse if we come to a situaton when most current releases that people would care about (e.g. |
@nodejs/ctc ... Given that we have not been able to reach consensus on this, I'm going to call for an official vote at the next CTC meeting. The vote options are:
Please weigh in here in advance of the CTC meeting if possible. I'd like to have a vote on the next call. |
My first choice is 1. Second choice would be 3. |
Why no runtime deprecation vote option? |
@jasnell I'm generally onboard with that approach but in this case @ChALkeR (and maybe others?) on CTC have made it explicit that they can only support zero-filling if there is a commitment to deprecation. Maybe a good approach is to resolve the "are we going to commit to a deprecation timeline or not" issue first, and then based on that outcome, people can make an informed vote on this? |
My humble proposal: start the vote from the question whether a ~50% performance hit to an API should be preceded by a full deprecation cycle. (I think it should be because that way practically everyone is warned about the impending change and has time to migrate, rather than having their application suddenly get slower after upgrading Node.js) |
const { compare: compare_, compareOffset } = binding; | ||
const { isArrayBuffer, isSharedArrayBuffer, isUint8Array } = | ||
process.binding('util'); | ||
const bindingObj = {}; | ||
const internalUtil = require('internal/util'); | ||
const zeroFillBuffers = !!config.zeroFillBuffers; | ||
const randomFill = zeroFillBuffers ? 0 : Math.floor(Math.random() * 256); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not gather crypto.randomBytes()
if crypto
is available?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we only need a single byte.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(and there's no need for it to be cryptographically safe)
@@ -102,7 +105,7 @@ function Buffer(arg, encodingOrOffset, length) { | |||
'If encoding is specified then the first argument must be a string' | |||
); | |||
} | |||
return Buffer.allocUnsafe(arg); | |||
return Buffer.allocUnsafe(arg).fill(randomFill); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doing this seems... slow?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, it is. I went with this approach here because it requires the least amount of code. It is more performant to do a memset
in the Allocator inside node.cc
, but doing so is a bit more intrusive and introduces a branch that impacts all Allocations (albeit, an extremely minor one).
I maintain that the best thing to do would be to just use calloc
and zero-fill instead.
I don't have an opinion on whether doing this at all is a good idea, but I've had nightmarish experiences debugging systems where someone had the clever idea of randomizing uninitialized memory "so no one would rely on its value". It made bugs manifest randomly. When something is buggy, it should ideally bug out all the time, every run, same place. Not randomly. Please don't make it random. |
The @nodejs/ctc has voted against the random fill approach in nodejs/CTC#90, |
@addaleax @nodejs/ctc
This is an alternate approach for #11806 that uses
fill()
to fill thenew Buffer(num)
/Buffer(num)
after allocation. The changes are less disruptive but occur in two steps. The performance profile is a bit better.This also includes the sameThe--pending-deprecation
addition that is in #11806 and the conditionally-emitted deprecationwarning that is off by default.--pending-deprecation
stuff has been separated out into a separate PR: #11968Again, this is a work in progress
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
buffer