Skip to content

Commit

Permalink
dgram: support for setting socket buffer size
Browse files Browse the repository at this point in the history
* setRecvBufferSize(int) and setSendBufferSize(int)
* added docs for send/receive buffer sizes
* Added options support to set buffer sizes in
  dgram.createSocket().

PR-URL: #13623
Reviewed-By: Refael Ackermann <refack@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
  • Loading branch information
DamienOReilly authored and refack committed Sep 13, 2017
1 parent fca7e49 commit 2ac7b43
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 0 deletions.
39 changes: 39 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.

### 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 @@ -398,6 +412,26 @@ decremented to 0 by a router, it will not be forwarded.
The argument passed to to `socket.setMulticastTTL()` is a number of hops
between 0 and 255. The default on most systems is `1` but can vary.

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

* `size` {number} Integer

Sets the `SO_RCVBUF` socket option. Sets the maximum socket receive buffer
in bytes.

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

* `size` {number} Integer

Sets the `SO_SNDBUF` socket option. Sets the maximum socket send buffer
in bytes.

### socket.setTTL(ttl)
<!-- YAML
added: v0.1.101
Expand Down Expand Up @@ -461,6 +495,9 @@ changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/14560
description: The `lookup` option is supported.
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/13623
description: `recvBufferSize` and `sendBufferSize` options are supported now.
-->

* `options` {Object} Available options are:
Expand All @@ -469,6 +506,8 @@ changes:
* `reuseAddr` {boolean} When `true` [`socket.bind()`][] will reuse the
address, even if another process has already bound a socket on it. Optional.
Defaults to `false`.
* `recvBufferSize` {number} - Optional. Sets the `SO_RCVBUF` socket value.
* `sendBufferSize` {number} - Optional. Sets the `SO_SNDBUF` socket value.
* `lookup` {Function} Custom lookup function. Defaults to [`dns.lookup()`][].
Optional.
* `callback` {Function} Attached as a listener for `'message'` events. Optional.
Expand Down
45 changes: 45 additions & 0 deletions lib/dgram.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,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 Down Expand Up @@ -141,6 +145,12 @@ function startListening(socket) {
socket._bindState = BIND_STATE_BOUND;
socket.fd = -42; // compatibility hack

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 @@ -157,6 +167,20 @@ function replaceHandle(self, newHandle) {
self._handle = newHandle;
}

function bufferSize(self, size, buffer) {
if (size >>> 0 !== 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 @@ -636,6 +660,27 @@ Socket.prototype.unref = function() {
return this;
};


Socket.prototype.setRecvBufferSize = function(size) {
bufferSize(this, size, 'recv');
};


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


Socket.prototype.getRecvBufferSize = function() {
return bufferSize(this, 0, 'recv');
};


Socket.prototype.getSendBufferSize = function() {
return bufferSize(this, 0, 'send');
};


module.exports = {
_createSocketHandle,
createSocket,
Expand Down
3 changes: 3 additions & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,11 +255,14 @@ E('ERR_SERVER_ALREADY_LISTEN',
'Listen method has been called more than once without closing.');
E('ERR_SOCKET_ALREADY_BOUND', 'Socket is already bound');
E('ERR_SOCKET_BAD_PORT', 'Port should be > 0 and < 65536');
E('ERR_SOCKET_BAD_BUFFER_SIZE', 'Buffer size must be a positive integer');
E('ERR_SOCKET_BAD_TYPE',
'Bad socket type specified. Valid types are: udp4, udp6');
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_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
39 changes: 39 additions & 0 deletions src/udp_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ using v8::Object;
using v8::PropertyAttribute;
using v8::PropertyCallbackInfo;
using v8::String;
using v8::Uint32;
using v8::Undefined;
using v8::Value;

Expand Down Expand Up @@ -134,6 +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, "bufferSize", BufferSize);

env->SetProtoMethod(t, "ref", HandleWrap::Ref);
env->SetProtoMethod(t, "unref", HandleWrap::Unref);
Expand Down Expand Up @@ -222,6 +224,43 @@ void UDPWrap::Bind6(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());
CHECK(args[1]->IsUint32());
int size = static_cast<int>(args[0].As<Uint32>()->Value());

if (size != args[0].As<Uint32>()->Value()) {
if (args[1].As<Uint32>()->Value() == 0)
return env->ThrowUVException(EINVAL, "uv_recv_buffer_size");
else
return env->ThrowUVException(EINVAL, "uv_send_buffer_size");
}

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

if (err != 0) {
if (args[1].As<Uint32>()->Value() == 0)
return env->ThrowUVException(err, "uv_recv_buffer_size");
else
return env->ThrowUVException(err, "uv_send_buffer_size");
}
args.GetReturnValue().Set(size);
}


#define X(name, fn) \
void UDPWrap::name(const FunctionCallbackInfo<Value>& args) { \
UDPWrap* wrap = Unwrap<UDPWrap>(args.Holder()); \
Expand Down
1 change: 1 addition & 0 deletions src/udp_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +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 BufferSize(const v8::FunctionCallbackInfo<v8::Value>& args);

static v8::Local<v8::Object> Instantiate(Environment* env, AsyncWrap* parent);
uv_udp_t* UVHandle();
Expand Down
20 changes: 20 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,23 @@ validTypes.forEach((validType) => {
socket.close();
});
});

// Ensure buffer sizes can be set
{
const socket = dgram.createSocket({
type: 'udp4',
recvBufferSize: 10000,
sendBufferSize: 15000
});

socket.bind(common.mustCall(() => {
// note: linux will double the buffer size
assert.ok(socket.getRecvBufferSize() === 10000 ||
socket.getRecvBufferSize() === 20000,
'SO_RCVBUF not 1300 or 2600');
assert.ok(socket.getSendBufferSize() === 15000 ||
socket.getSendBufferSize() === 30000,
'SO_SNDBUF not 1800 or 3600');
socket.close();
}));
}
74 changes: 74 additions & 0 deletions test/parallel/test-dgram-socket-buffer-size.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use strict';

const common = require('../common');
const assert = require('assert');
const dgram = require('dgram');

{
// Should throw error if the socket is never bound.
const errorObj = {
code: 'ERR_SOCKET_BUFFER_SIZE',
type: Error,
message: /^Could not get or set buffer size:.*$/
};

const socket = dgram.createSocket('udp4');

assert.throws(() => {
socket.setRecvBufferSize(8192);
}, common.expectsError(errorObj));

assert.throws(() => {
socket.setSendBufferSize(8192);
}, common.expectsError(errorObj));

assert.throws(() => {
socket.getRecvBufferSize();
}, common.expectsError(errorObj));

assert.throws(() => {
socket.getSendBufferSize();
}, common.expectsError(errorObj));
}

{
// Should throw error if invalid buffer size is specified
const errorObj = {
code: 'ERR_SOCKET_BAD_BUFFER_SIZE',
type: TypeError,
message: /^Buffer size must be a positive integer$/
};

const badBufferSizes = [-1, Infinity, 'Doh!'];

const socket = dgram.createSocket('udp4');

socket.bind(common.mustCall(() => {
badBufferSizes.forEach((badBufferSize) => {
assert.throws(() => {
socket.setRecvBufferSize(badBufferSize);
}, common.expectsError(errorObj));

assert.throws(() => {
socket.setSendBufferSize(badBufferSize);
}, common.expectsError(errorObj));
});
socket.close();
}));
}

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

socket.bind(common.mustCall(() => {
socket.setRecvBufferSize(10000);
socket.setSendBufferSize(10000);

// note: linux will double the buffer size
const expectedBufferSize = common.isLinux ? 20000 : 10000;
assert.strictEqual(socket.getRecvBufferSize(), expectedBufferSize);
assert.strictEqual(socket.getSendBufferSize(), expectedBufferSize);
socket.close();
}));
}

0 comments on commit 2ac7b43

Please sign in to comment.