Skip to content

Commit 3b6c00a

Browse files
DamienOReillyMylesBorins
authored andcommitted
dgram: support for setting socket buffer size
* 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 <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent 85c4d75 commit 3b6c00a

File tree

7 files changed

+221
-0
lines changed

7 files changed

+221
-0
lines changed

doc/api/dgram.md

+39
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,20 @@ never have reason to call this.
227227
If `multicastInterface` is not specified, the operating system will attempt to
228228
drop membership on all valid interfaces.
229229

230+
### socket.getRecvBufferSize(size)
231+
<!-- YAML
232+
added: REPLACEME
233+
-->
234+
235+
* Returns {number} the `SO_RCVBUF` socket receive buffer size in bytes.
236+
237+
### socket.getSendBufferSize(size)
238+
<!-- YAML
239+
added: REPLACEME
240+
-->
241+
242+
* Returns {number} the `SO_SNDBUF` socket send buffer size in bytes.
243+
230244
### socket.ref()
231245
<!-- YAML
232246
added: v0.9.1
@@ -476,6 +490,26 @@ decremented to 0 by a router, it will not be forwarded.
476490
The argument passed to to `socket.setMulticastTTL()` is a number of hops
477491
between 0 and 255. The default on most systems is `1` but can vary.
478492

493+
### socket.setRecvBufferSize(size)
494+
<!-- YAML
495+
added: REPLACEME
496+
-->
497+
498+
* `size` {number} Integer
499+
500+
Sets the `SO_RCVBUF` socket option. Sets the maximum socket receive buffer
501+
in bytes.
502+
503+
### socket.setSendBufferSize(size)
504+
<!-- YAML
505+
added: REPLACEME
506+
-->
507+
508+
* `size` {number} Integer
509+
510+
Sets the `SO_SNDBUF` socket option. Sets the maximum socket send buffer
511+
in bytes.
512+
479513
### socket.setTTL(ttl)
480514
<!-- YAML
481515
added: v0.1.101
@@ -539,6 +573,9 @@ changes:
539573
- version: v8.6.0
540574
pr-url: https://github.com/nodejs/node/pull/14560
541575
description: The `lookup` option is supported.
576+
- version: REPLACEME
577+
pr-url: https://github.com/nodejs/node/pull/13623
578+
description: `recvBufferSize` and `sendBufferSize` options are supported now.
542579
-->
543580

544581
* `options` {Object} Available options are:
@@ -547,6 +584,8 @@ changes:
547584
* `reuseAddr` {boolean} When `true` [`socket.bind()`][] will reuse the
548585
address, even if another process has already bound a socket on it. Optional.
549586
Defaults to `false`.
587+
* `recvBufferSize` {number} - Optional. Sets the `SO_RCVBUF` socket value.
588+
* `sendBufferSize` {number} - Optional. Sets the `SO_SNDBUF` socket value.
550589
* `lookup` {Function} Custom lookup function. Defaults to [`dns.lookup()`][].
551590
Optional.
552591
* `callback` {Function} Attached as a listener for `'message'` events. Optional.

lib/dgram.js

+45
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,19 @@ function _createSocketHandle(address, port, addressType, fd, flags) {
9898
return handle;
9999
}
100100

101+
const kOptionSymbol = Symbol('options symbol');
101102

102103
function Socket(type, listener) {
103104
EventEmitter.call(this);
104105
var lookup;
105106

107+
this[kOptionSymbol] = {};
106108
if (type !== null && typeof type === 'object') {
107109
var options = type;
108110
type = options.type;
109111
lookup = options.lookup;
112+
this[kOptionSymbol].recvBufferSize = options.recvBufferSize;
113+
this[kOptionSymbol].sendBufferSize = options.sendBufferSize;
110114
}
111115

112116
var handle = newHandle(type, lookup);
@@ -141,6 +145,12 @@ function startListening(socket) {
141145
socket._bindState = BIND_STATE_BOUND;
142146
socket.fd = -42; // compatibility hack
143147

148+
if (socket[kOptionSymbol].recvBufferSize)
149+
bufferSize(socket, socket[kOptionSymbol].recvBufferSize, 'recv');
150+
151+
if (socket[kOptionSymbol].sendBufferSize)
152+
bufferSize(socket, socket[kOptionSymbol].sendBufferSize, 'send');
153+
144154
socket.emit('listening');
145155
}
146156

@@ -157,6 +167,20 @@ function replaceHandle(self, newHandle) {
157167
self._handle = newHandle;
158168
}
159169

170+
function bufferSize(self, size, buffer) {
171+
if (size >>> 0 !== size)
172+
throw new errors.TypeError('ERR_SOCKET_BAD_BUFFER_SIZE');
173+
174+
try {
175+
if (buffer === 'recv')
176+
return self._handle.bufferSize(size, 0);
177+
else
178+
return self._handle.bufferSize(size, 1);
179+
} catch (e) {
180+
throw new errors.Error('ERR_SOCKET_BUFFER_SIZE', e);
181+
}
182+
}
183+
160184
Socket.prototype.bind = function(port_, address_ /*, callback*/) {
161185
let port = port_;
162186

@@ -651,6 +675,27 @@ Socket.prototype.unref = function() {
651675
return this;
652676
};
653677

678+
679+
Socket.prototype.setRecvBufferSize = function(size) {
680+
bufferSize(this, size, 'recv');
681+
};
682+
683+
684+
Socket.prototype.setSendBufferSize = function(size) {
685+
bufferSize(this, size, 'send');
686+
};
687+
688+
689+
Socket.prototype.getRecvBufferSize = function() {
690+
return bufferSize(this, 0, 'recv');
691+
};
692+
693+
694+
Socket.prototype.getSendBufferSize = function() {
695+
return bufferSize(this, 0, 'send');
696+
};
697+
698+
654699
module.exports = {
655700
_createSocketHandle,
656701
createSocket,

lib/internal/errors.js

+3
Original file line numberDiff line numberDiff line change
@@ -241,12 +241,15 @@ E('ERR_REQUIRE_ESM', 'Must use import to load ES Module: %s');
241241
E('ERR_SOCKET_ALREADY_BOUND', 'Socket is already bound');
242242
E('ERR_SOCKET_BAD_BUFFER_SIZE', 'Buffer size must be a positive integer');
243243
E('ERR_SOCKET_BAD_PORT', 'Port should be > 0 and < 65536');
244+
E('ERR_SOCKET_BAD_BUFFER_SIZE', 'Buffer size must be a positive integer');
244245
E('ERR_SOCKET_BAD_TYPE',
245246
'Bad socket type specified. Valid types are: udp4, udp6');
246247
E('ERR_SOCKET_BUFFER_SIZE',
247248
(reason) => `Could not get or set buffer size: ${reason}`);
248249
E('ERR_SOCKET_CANNOT_SEND', 'Unable to send data');
249250
E('ERR_SOCKET_DGRAM_NOT_RUNNING', 'Not running');
251+
E('ERR_SOCKET_BUFFER_SIZE',
252+
(reason) => `Could not get or set buffer size: ${reason}`);
250253
E('ERR_STDERR_CLOSE', 'process.stderr cannot be closed');
251254
E('ERR_STDOUT_CLOSE', 'process.stdout cannot be closed');
252255
E('ERR_UNKNOWN_BUILTIN_MODULE', (id) => `No such built-in module: ${id}`);

src/udp_wrap.cc

+39
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ using v8::Object;
4646
using v8::PropertyAttribute;
4747
using v8::PropertyCallbackInfo;
4848
using v8::String;
49+
using v8::Uint32;
4950
using v8::Undefined;
5051
using v8::Value;
5152

@@ -135,6 +136,7 @@ void UDPWrap::Initialize(Local<Object> target,
135136
env->SetProtoMethod(t, "setMulticastLoopback", SetMulticastLoopback);
136137
env->SetProtoMethod(t, "setBroadcast", SetBroadcast);
137138
env->SetProtoMethod(t, "setTTL", SetTTL);
139+
env->SetProtoMethod(t, "bufferSize", BufferSize);
138140

139141
env->SetProtoMethod(t, "ref", HandleWrap::Ref);
140142
env->SetProtoMethod(t, "unref", HandleWrap::Unref);
@@ -223,6 +225,43 @@ void UDPWrap::Bind6(const FunctionCallbackInfo<Value>& args) {
223225
}
224226

225227

228+
void UDPWrap::BufferSize(const FunctionCallbackInfo<Value>& args) {
229+
Environment* env = Environment::GetCurrent(args);
230+
UDPWrap* wrap;
231+
ASSIGN_OR_RETURN_UNWRAP(&wrap,
232+
args.Holder(),
233+
args.GetReturnValue().Set(UV_EBADF));
234+
235+
CHECK(args[0]->IsUint32());
236+
CHECK(args[1]->IsUint32());
237+
int size = static_cast<int>(args[0].As<Uint32>()->Value());
238+
239+
if (size != args[0].As<Uint32>()->Value()) {
240+
if (args[1].As<Uint32>()->Value() == 0)
241+
return env->ThrowUVException(EINVAL, "uv_recv_buffer_size");
242+
else
243+
return env->ThrowUVException(EINVAL, "uv_send_buffer_size");
244+
}
245+
246+
int err;
247+
if (args[1].As<Uint32>()->Value() == 0) {
248+
err = uv_recv_buffer_size(reinterpret_cast<uv_handle_t*>(&wrap->handle_),
249+
&size);
250+
} else {
251+
err = uv_send_buffer_size(reinterpret_cast<uv_handle_t*>(&wrap->handle_),
252+
&size);
253+
}
254+
255+
if (err != 0) {
256+
if (args[1].As<Uint32>()->Value() == 0)
257+
return env->ThrowUVException(err, "uv_recv_buffer_size");
258+
else
259+
return env->ThrowUVException(err, "uv_send_buffer_size");
260+
}
261+
args.GetReturnValue().Set(size);
262+
}
263+
264+
226265
#define X(name, fn) \
227266
void UDPWrap::name(const FunctionCallbackInfo<Value>& args) { \
228267
UDPWrap* wrap = Unwrap<UDPWrap>(args.Holder()); \

src/udp_wrap.h

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class UDPWrap: public HandleWrap {
5757
const v8::FunctionCallbackInfo<v8::Value>& args);
5858
static void SetBroadcast(const v8::FunctionCallbackInfo<v8::Value>& args);
5959
static void SetTTL(const v8::FunctionCallbackInfo<v8::Value>& args);
60+
static void BufferSize(const v8::FunctionCallbackInfo<v8::Value>& args);
6061

6162
static v8::Local<v8::Object> Instantiate(Environment* env, AsyncWrap* parent);
6263
uv_udp_t* UVHandle();

test/parallel/test-dgram-createSocket-type.js

+20
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,23 @@ validTypes.forEach((validType) => {
3939
socket.close();
4040
});
4141
});
42+
43+
// Ensure buffer sizes can be set
44+
{
45+
const socket = dgram.createSocket({
46+
type: 'udp4',
47+
recvBufferSize: 10000,
48+
sendBufferSize: 15000
49+
});
50+
51+
socket.bind(common.mustCall(() => {
52+
// note: linux will double the buffer size
53+
assert.ok(socket.getRecvBufferSize() === 10000 ||
54+
socket.getRecvBufferSize() === 20000,
55+
'SO_RCVBUF not 1300 or 2600');
56+
assert.ok(socket.getSendBufferSize() === 15000 ||
57+
socket.getSendBufferSize() === 30000,
58+
'SO_SNDBUF not 1800 or 3600');
59+
socket.close();
60+
}));
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const dgram = require('dgram');
6+
7+
{
8+
// Should throw error if the socket is never bound.
9+
const errorObj = {
10+
code: 'ERR_SOCKET_BUFFER_SIZE',
11+
type: Error,
12+
message: /^Could not get or set buffer size:.*$/
13+
};
14+
15+
const socket = dgram.createSocket('udp4');
16+
17+
assert.throws(() => {
18+
socket.setRecvBufferSize(8192);
19+
}, common.expectsError(errorObj));
20+
21+
assert.throws(() => {
22+
socket.setSendBufferSize(8192);
23+
}, common.expectsError(errorObj));
24+
25+
assert.throws(() => {
26+
socket.getRecvBufferSize();
27+
}, common.expectsError(errorObj));
28+
29+
assert.throws(() => {
30+
socket.getSendBufferSize();
31+
}, common.expectsError(errorObj));
32+
}
33+
34+
{
35+
// Should throw error if invalid buffer size is specified
36+
const errorObj = {
37+
code: 'ERR_SOCKET_BAD_BUFFER_SIZE',
38+
type: TypeError,
39+
message: /^Buffer size must be a positive integer$/
40+
};
41+
42+
const badBufferSizes = [-1, Infinity, 'Doh!'];
43+
44+
const socket = dgram.createSocket('udp4');
45+
46+
socket.bind(common.mustCall(() => {
47+
badBufferSizes.forEach((badBufferSize) => {
48+
assert.throws(() => {
49+
socket.setRecvBufferSize(badBufferSize);
50+
}, common.expectsError(errorObj));
51+
52+
assert.throws(() => {
53+
socket.setSendBufferSize(badBufferSize);
54+
}, common.expectsError(errorObj));
55+
});
56+
socket.close();
57+
}));
58+
}
59+
60+
{
61+
// Can set and get buffer sizes after binding the socket.
62+
const socket = dgram.createSocket('udp4');
63+
64+
socket.bind(common.mustCall(() => {
65+
socket.setRecvBufferSize(10000);
66+
socket.setSendBufferSize(10000);
67+
68+
// note: linux will double the buffer size
69+
const expectedBufferSize = common.isLinux ? 20000 : 10000;
70+
assert.strictEqual(socket.getRecvBufferSize(), expectedBufferSize);
71+
assert.strictEqual(socket.getSendBufferSize(), expectedBufferSize);
72+
socket.close();
73+
}));
74+
}

0 commit comments

Comments
 (0)