- Sponsor
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Introduce the generateMask
option
#1990
Conversation
c15291a
to
95f269d
Compare
generateMask
option
lib/sender.js
Outdated
|
||
target[1] |= 0x80; | ||
target[offset - 4] = mask[0]; | ||
target[offset - 3] = mask[1]; | ||
target[offset - 2] = mask[2]; | ||
target[offset - 1] = mask[3]; | ||
|
||
if ((mask[0] | mask[1] | mask[2] | mask[3]) === 0) { |
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.
I think this optimization does not make sense. It is actually counterproductive in the common case. If a random mask is used, then the probability of this event is 1 / 2**32. The same applies for the same optimization on lib/receiver.js
.
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.
I don’t think it hurts and allows for hot path optimization for clients that know the hot path “trick”.
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.
I initially called the option fillMaskBuffer
, and made it take the mask buffer like crypto.randomFillSync()
. Then I changed my mind and went for generateMask
. The reason is that it is simpler to use. The user does not have to remember to fill the buffer. They are not forced to do that but we should then document that the last one is used even if the WebSocket
instance is different (this might be confusing). With generateMask
, not returning a buffer results in a thrown error. Finally, generateMask
does not force the user to return a new Buffer
instance every time. The user can return the same instance:
const mask = Buffer.alloc(4);
const ws = new Websocket(url, {
generateMask() {
mask.fill(0);
return mask;
}
});
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.
Isn't this sufficient? I don't think ws
will mutate the buffer, right?
const mask = Buffer.alloc(4);
const ws = new Websocket(url, {
generateMask() {
return mask;
}
});
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.
Correct, ws
does not mutate it.
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.
I benchmarked this line
(mask[0] | mask[1] | mask[2] | mask[3]) === 0
and it came out to less than 1 nanosecond
How can you get that precision? Two sequential process.hrtime.bigint()
with nothing in-between take ~0.1 milliseconds / ~100000 nanoseconds on my machine.
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 don't you make an uint32array from the memory and just check if (a[0])
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.
Ok sorry I see what you are saying, pass around Uint32Array's instead of Buffers for the mask:
uint32 = new Uint32Array(1) // new mask default value = 0
{ generateMask: uint32 => require('crypto').randomFillSync(uint32) }
{ generateMask: uint32 => uint32[0] = Math.random()*2**32 }
{ generateMask: uint32 => uint32[0]++ }
const skipMasking = uint32[0] === 0;
They are easier to work with than Buffer because the mask is 32 bit unsigned int. Maybe only reason to use Buffer is more people are familiar with Buffer? You could do either one, Uint32Array makes more sense, you just have to know how it works. Buffer is easier to say than Uint32Array. Ideally Uint32Array but Buffer is ok. Uint32Array has a performance advantage setting 1 value vs 4
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.
Maybe only reason to use Buffer is more people are familiar with Buffer?
Yes, and because bufferUtil.mask()
was written to take a buffer at the time https://github.com/websockets/bufferutil/blob/cd48704aa8e87c19d7497812d466fd6fb6a28c09/src/bufferutil.cc#L89-L90, when typed arrays were not supported, and that did not change over time. We could make it take a Uint32Array
but I do not think it is worth the effort.
lib/sender.js
Outdated
let mask = _mask; | ||
|
||
if (options.generateMask) { | ||
mask = options.generateMask(); |
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.
This generates a new Buffer object on every send, instead it should modify existing buffer
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.
Not necessarily, see #1990 (comment).
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.
It makes the config setting awkward to require maintaining the buffer outside the setting, it would be more convenient and succinct to pass the buffer to the function:
new WebSocket(url, { generateMask: buf => require('crypto').randomFillSync(buf) } )
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.
it would be more convenient and succinct to pass the buffer to the function.
The only reason why I did not do that is that the buffer is shared between WebSocket
instances and this might generate confusion even if it is documented because people do not read.
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.
Does it need to be shared?
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.
It requires more changes because I don't want to add a mask buffer also for server clients and currently the Sender
is server/client agnostic.
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.
There is also the static Sender.frame()
method. It gets kinda messy.
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.
You only need to create an extra Buffer if the Sender has the config option generateMask
set, otherwise you use the shared buffer
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.
See 8ab0db4
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.
Looks like exactly what is needed for that, this would be a great feature nice work
LGTM! Thanks! |
6564661
to
18bef28
Compare
18bef28
to
8fb2fc1
Compare
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.
I think allocating target is not necessary on the === 0 fast path?
8fb2fc1
to
4da9b64
Compare
It is, but |
I had to come back and fix that on my 1st PR 9a22689 added PR suggestion: https://github.com/websockets/ws/pull/1991/files |
Will fix it later, when I get a chance. |
4da9b64
to
d0e5b34
Compare
d0e5b34
to
6d83e71
Compare
Thank you for the reviews. |
The
generateMask
option specifies a function that can be used togenerate custom masking keys.
Refs: #1986
Refs: #1988
Refs: #1989