Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
dgram: add support to get buffer sizes
  • Loading branch information
DamienOReilly committed Sep 10, 2017
commit d5789c0a46da8412774e56517a69ddf919a10f3a
15 changes: 15 additions & 0 deletions doc/api/dgram.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,20 @@ never have reason to call this.
If `multicastInterface` is not specified, the operating system will attempt to
drop membership on all valid interfaces.

### socket.getRecvBufferSize(size)
<!-- YAML
added: REPLACEME
-->

* Returns {number} the `SO_RCVBUF` socket receive buffer size in bytes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a thought, but we might not need to mention SO_RCVBUF and SO_SNDBUF at all. They aren't really needed to work with the API.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are not needed, but I though it was no harm having them mentioned in the docs. I'd like to get a few people's opinion on this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that googling SO_RCVBUF gives very good results (linux man and MSDN) so I'm +0.5

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can always add something like (see socket(7)) if you like, the doctool will expand that into a link.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for the record, I'm not opposed to including them. I just thought we might be able to get by without them. I'm open to whatever everyone thinks is best here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I know, the only reason I included them was that I was familiar with setting the buffer sizes with the JDK, and the javadocs mentioned these property names. When I started working on a small app to get more familiar with javascript, I was goggle'in around "node.js SO_RCVBUF" to see if anyone had already added or was in the process of adding buffer size setting support in node.js.


### socket.getSendBufferSize(size)
<!-- YAML
added: REPLACEME
-->

* Returns {number} the `SO_SNDBUF` socket send buffer size in bytes.

### socket.ref()
<!-- YAML
added: v0.9.1
Expand Down Expand Up @@ -478,6 +492,7 @@ s.bind(1234, () => {
<!-- YAML
added: v0.11.13
changes:
<<<<<<< f6bf7f248e558ec49cddef9f440498c558a6574a
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/14560
description: The `lookup` option is supported.
Expand Down
63 changes: 33 additions & 30 deletions lib/dgram.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ const EventEmitter = require('events');
const setInitTriggerId = require('async_hooks').setInitTriggerId;
const UV_UDP_REUSEADDR = process.binding('constants').os.UV_UDP_REUSEADDR;
const async_id_symbol = process.binding('async_wrap').async_id_symbol;
const uv = process.binding('uv');
const nextTick = require('internal/process/next_tick').nextTick;

const UDP = process.binding('udp_wrap').UDP;
Expand Down Expand Up @@ -99,15 +98,19 @@ function _createSocketHandle(address, port, addressType, fd, flags) {
return handle;
}

const kOptionSymbol = Symbol('options symbol');

function Socket(type, listener) {
EventEmitter.call(this);
var lookup;

this[kOptionSymbol] = {};
if (type !== null && typeof type === 'object') {
var options = type;
type = options.type;
lookup = options.lookup;
this[kOptionSymbol].recvBufferSize = options.recvBufferSize;
this[kOptionSymbol].sendBufferSize = options.sendBufferSize;
}

var handle = newHandle(type, lookup);
Expand All @@ -122,12 +125,6 @@ function Socket(type, listener) {

// If true - UV_UDP_REUSEADDR flag will be set
this._reuseAddr = options && options.reuseAddr;

if (options && options.recvBufferSize)
this._recvBufferSize = options.recvBufferSize;

if (options && options.sendBufferSize)
this._sendBufferSize = options.sendBufferSize;

if (typeof listener === 'function')
this.on('message', listener);
Expand All @@ -147,12 +144,12 @@ function startListening(socket) {
socket._receiving = true;
socket._bindState = BIND_STATE_BOUND;
socket.fd = -42; // compatibility hack

if (socket._recvBufferSize)
socket.setRecvBufferSize(socket._recvBufferSize);

if (socket._sendBufferSize)
socket.setSendBufferSize(socket._sendBufferSize);
if (socket[kOptionSymbol].recvBufferSize)
bufferSize(socket, socket[kOptionSymbol].recvBufferSize, 'recv');

if (socket[kOptionSymbol].sendBufferSize)
bufferSize(socket, socket[kOptionSymbol].sendBufferSize, 'send');

socket.emit('listening');
}
Expand All @@ -170,6 +167,20 @@ function replaceHandle(self, newHandle) {
self._handle = newHandle;
}

function bufferSize(self, size, buffer) {
if (!isValidSize(size))
throw new errors.TypeError('ERR_SOCKET_BAD_BUFFER_SIZE');

try {
if (buffer === 'recv')
return self._handle.bufferSize(size, 0);
else
return self._handle.bufferSize(size, 1);
} catch (e) {
throw new errors.Error('ERR_SOCKET_BUFFER_SIZE', e);
}
}

Socket.prototype.bind = function(port_, address_ /*, callback*/) {
let port = port_;

Expand Down Expand Up @@ -341,10 +352,7 @@ function enqueue(self, toEnqueue) {


function isValidSize(size) {
return Number.isFinite(size) &&
size <= Number.MAX_SAFE_INTEGER &&
size >= 0 &&
size >>> 0 === size;
return size >>> 0 === size;
}


Expand Down Expand Up @@ -659,27 +667,22 @@ Socket.prototype.unref = function() {


Socket.prototype.setRecvBufferSize = function(size) {
if (!isValidSize(size))
throw new errors.TypeError('ERR_SOCKET_BAD_BUFFER_SIZE');

var err = this._handle.setRecvBufferSize(size);
bufferSize(this, size, 'recv');
};

if (err)
throw new errors.TypeError('ERR_SOCKET_SET_BUFFER_SIZE',
uv.errname(err));

Socket.prototype.setSendBufferSize = function(size) {
bufferSize(this, size, 'send');
};


Socket.prototype.setSendBufferSize = function(size) {
if (!isValidSize(size))
throw new errors.TypeError('ERR_SOCKET_BAD_BUFFER_SIZE');
Socket.prototype.getRecvBufferSize = function() {
return bufferSize(this, 0, 'recv');
};

var err = this._handle.setSendBufferSize(size);

if (err)
throw new errors.TypeError('ERR_SOCKET_SET_BUFFER_SIZE',
uv.errname(err));
Socket.prototype.getSendBufferSize = function() {
return bufferSize(this, 0, 'send');
};


Expand Down
2 changes: 1 addition & 1 deletion lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ E('ERR_SOCKET_BAD_TYPE',
E('ERR_SOCKET_CANNOT_SEND', 'Unable to send data');
E('ERR_SOCKET_CLOSED', 'Socket is closed');
E('ERR_SOCKET_DGRAM_NOT_RUNNING', 'Not running');
E('ERR_SOCKET_SET_BUFFER_SIZE', (reason) => `Coud not set buffer size: ${reason}`);
E('ERR_SOCKET_BUFFER_SIZE', (reason) => `Could not get or set buffer size: ${reason}`);
E('ERR_STDERR_CLOSE', 'process.stderr cannot be closed');
E('ERR_STDOUT_CLOSE', 'process.stdout cannot be closed');
E('ERR_STREAM_WRAP', 'Stream has StringDecoder set or is in objectMode');
Expand Down
41 changes: 20 additions & 21 deletions src/udp_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,7 @@ void UDPWrap::Initialize(Local<Object> target,
env->SetProtoMethod(t, "setMulticastLoopback", SetMulticastLoopback);
env->SetProtoMethod(t, "setBroadcast", SetBroadcast);
env->SetProtoMethod(t, "setTTL", SetTTL);
env->SetProtoMethod(t, "setRecvBufferSize", SetRecvBufferSize);
env->SetProtoMethod(t, "setSendBufferSize", SetSendBufferSize);
env->SetProtoMethod(t, "bufferSize", BufferSize);

env->SetProtoMethod(t, "ref", HandleWrap::Ref);
env->SetProtoMethod(t, "unref", HandleWrap::Unref);
Expand Down Expand Up @@ -225,35 +224,35 @@ void UDPWrap::Bind6(const FunctionCallbackInfo<Value>& args) {
}


void UDPWrap::SetRecvBufferSize(const FunctionCallbackInfo<Value>& args) {
void UDPWrap::BufferSize(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
UDPWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap,
args.Holder(),
args.GetReturnValue().Set(UV_EBADF));

CHECK(args[0]->IsUint32());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably don't want to crash when the user does socket.setRecvBufferSize(Infinity). Throwing a TypeError should be a better choice.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TimothyGu you mean on the JS side, right?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted, do you recommend:

  if (!args[0]->IsUint32()) {
    return env->ThrowTypeError("size must be an uint32");
  }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend doing the type check in JS. Just something simple like:

const errors = require('internal/errors');

// then within the the functions
if (typeof size !== 'number')
  throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'size', 'number');

Copy link
Copy Markdown
Contributor Author

@DamienOReilly DamienOReilly Jun 12, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your input. Do you think I need to do any safety checking in udp_wrap.cc SetRecvBufferSize() or we assume only dgram.js will interact with udp_wrap ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, once the type checking is added to the js side, I would keep the CHECK in place on the native side. That should only be hit if we do something wrong in core or if the user is doing something they really shouldn't.

int size = (int)args[0].As<Uint32>()->Value();
int err = uv_recv_buffer_size(reinterpret_cast<uv_handle_t*>(&wrap->handle_),
CHECK(args[1]->IsUint32());
int size = (int)args[0].As<Uint32>()->Value();

int err = 0;
if (args[1].As<Uint32>()->Value() == 0)
err = uv_recv_buffer_size(reinterpret_cast<uv_handle_t*>(&wrap->handle_),
&size);

args.GetReturnValue().Set(err);
}


void UDPWrap::SetSendBufferSize(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
UDPWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap,
args.Holder(),
args.GetReturnValue().Set(UV_EBADF));

CHECK(args[0]->IsUint32());
int size = (int)args[0].As<Uint32>()->Value();
int err = uv_send_buffer_size(reinterpret_cast<uv_handle_t*>(&wrap->handle_),
else
err = uv_send_buffer_size(reinterpret_cast<uv_handle_t*>(&wrap->handle_),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here about alignment.

&size);

args.GetReturnValue().Set(err);
if (err != 0) {
std::string syscall;
if (args[1].As<Uint32>()->Value() == 0)
syscall = "uv_recv_buffer_size";
else
syscall = "uv_send_buffer_size";

return env->ThrowUVException(err, syscall.c_str());
}
args.GetReturnValue().Set(size);
}


Expand Down
3 changes: 1 addition & 2 deletions src/udp_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ class UDPWrap: public HandleWrap {
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetBroadcast(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetTTL(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetRecvBufferSize(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetSendBufferSize(const v8::FunctionCallbackInfo<v8::Value>& args);
static void BufferSize(const v8::FunctionCallbackInfo<v8::Value>& args);

static v8::Local<v8::Object> Instantiate(Environment* env, AsyncWrap* parent);
uv_udp_t* UVHandle();
Expand Down
19 changes: 19 additions & 0 deletions test/parallel/test-dgram-createSocket-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,22 @@ validTypes.forEach((validType) => {
socket.close();
});
});

// Ensure buffer sizes can be set
{
const socket = dgram.createSocket({ type: 'udp4',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little surprised the linter doesn't complain about this. Can you move type to the next line with two spaces indentation.

recvBufferSize: 1300,
sendBufferSize: 1800,
});

socket.bind(common.mustCall(() => {
// note: linux will double the buffer size
assert.ok(socket.getRecvBufferSize() === 1300 ||
socket.getRecvBufferSize() === 2600,
"SO_RCVBUF not 1300 or 2600");
assert.ok(socket.getSendBufferSize() === 1800 ||
socket.getSendBufferSize() === 3600,
"SO_SNDVBUF not 1800 or 3600");
socket.close();
}));
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,41 @@ const dgram = require('dgram');
assert.throws(() => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The duplication here could probably be reduced.

socket.setRecvBufferSize(8192);
}, common.expectsError({
code: 'ERR_SOCKET_SET_BUFFER_SIZE',
code: 'ERR_SOCKET_BUFFER_SIZE',
type: Error,
message: /^Coud not set buffer size: E[A-Z]+$/
message: /^Could not get or set buffer size:.*$/
}));

assert.throws(() => {
socket.setSendBufferSize(8192);
}, common.expectsError({
code: 'ERR_SOCKET_SET_BUFFER_SIZE',
code: 'ERR_SOCKET_BUFFER_SIZE',
type: Error,
message: /^Coud not set buffer size: E[A-Z]+$/
message: /^Could not get or set buffer size:.*$/
}));

assert.throws(() => {
socket.getRecvBufferSize();
}, common.expectsError({
code: 'ERR_SOCKET_BUFFER_SIZE',
type: Error,
message: /^Could not get or set buffer size:.*$/
}));

assert.throws(() => {
socket.getSendBufferSize();
}, common.expectsError({
code: 'ERR_SOCKET_BUFFER_SIZE',
type: Error,
message: /^Could not get or set buffer size:.*$/
}));


}

{
// Should throw error if invalid buffer size is specified
const socket = dgram.createSocket('udp4');

socket.bind(0, common.mustCall(() => {

socket.bind(common.mustCall(() => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test doesn't exercise socket.setSendBufferSize().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good spot., thanks.

assert.throws(() => {
socket.setRecvBufferSize(-1);
}, common.expectsError({
Expand Down Expand Up @@ -61,13 +74,21 @@ const dgram = require('dgram');
}));
}

// Can set and get buffer sizes after binding the socket.
{
// Can call setRecvBufferSize() and setRecvBufferSize() after binding the socket.
const socket = dgram.createSocket('udp4');

socket.bind(0, common.mustCall(() => {
socket.setRecvBufferSize(8192);
socket.setSendBufferSize(8192);
socket.bind(common.mustCall(() => {
socket.setRecvBufferSize(1200);
socket.setSendBufferSize(1500);

// note: linux will double the buffer size
assert.ok(socket.getRecvBufferSize() === 1200 ||
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be better to add an if statement here and verify that the buffer size is only doubled on Linux.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ternary condition nicer, maybe..
assert.ok(socket.getRecvBufferSize() === (os.platform === 'linux' ? 2400 : 1200), 'SO_RCVBUF not size expected.');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not my cup of tea.
I'd go:

const expectedBufferSize = common.isLinux ? 2400 : 1200;
assert.strictEqual(socket.getRecvBufferSize(), expectedBufferSize);

socket.getRecvBufferSize() === 2400,
"SO_RCVBUF not 1200 or 2400");
assert.ok(socket.getSendBufferSize() === 1500 ||
socket.getSendBufferSize() === 3000,
"SO_SNDVBUF not 1500 or 3000");
socket.close();
}));
}