Skip to content
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

dgram: support AbortSignal in createSocket #37026

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 18 additions & 0 deletions doc/api/dgram.md
Expand Up @@ -735,6 +735,9 @@ chained.
<!-- YAML
added: v0.11.13
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/37026
description: AbortSignal support was added.
- version: v11.4.0
pr-url: https://github.com/nodejs/node/pull/23798
description: The `ipv6Only` option is supported.
Expand All @@ -759,6 +762,7 @@ changes:
* `recvBufferSize` {number} Sets the `SO_RCVBUF` socket value.
* `sendBufferSize` {number} Sets the `SO_SNDBUF` socket value.
* `lookup` {Function} Custom lookup function. **Default:** [`dns.lookup()`][].
* `signal` {AbortSignal} An AbortSignal that may be used to close a socket.
* `callback` {Function} Attached as a listener for `'message'` events. Optional.
* Returns: {dgram.Socket}

Expand All @@ -770,6 +774,20 @@ method will bind the socket to the "all interfaces" address on a random port
and port can be retrieved using [`socket.address().address`][] and
[`socket.address().port`][].

If the `signal` option is enabled, calling `.abort()` on the corresponding
`AbortController` is similar to calling `.close()` on the socket:

```js
const controller = new AbortController();
const { signal } = controller;
const server = dgram.createSocket({ type: 'udp4', signal });
server.on('message', (msg, rinfo) => {
console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`);
});
// Later, when you want to close the server.
controller.abort();
```

### `dgram.createSocket(type[, callback])`
<!-- YAML
added: v0.1.99
Expand Down
15 changes: 15 additions & 0 deletions lib/dgram.js
Expand Up @@ -52,6 +52,7 @@ const {
} = errors.codes;
const {
isInt32,
validateAbortSignal,
validateString,
validateNumber,
validatePort,
Expand Down Expand Up @@ -125,6 +126,20 @@ function Socket(type, listener) {
recvBufferSize,
sendBufferSize
};

if (options?.signal !== undefined) {
const { signal } = options;
validateAbortSignal(signal, 'options.signal');
const onAborted = () => {
this.close();
};
if (signal.aborted) {
onAborted();
} else {
signal.addEventListener('abort', onAborted);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
signal.addEventListener('abort', onAborted);
signal.addEventListener('abort', onAborted, { once: true });

Copy link
Member

Choose a reason for hiding this comment

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

I think this is fine, once just creates the impression that it removes the abort listener on 'abort' but you'd need the removeEventListener anyway for the "server closed for another reason" case.

this.once('close', () => signal.removeEventListener('abort', onAborted));
}
}
}
ObjectSetPrototypeOf(Socket.prototype, EventEmitter.prototype);
ObjectSetPrototypeOf(Socket, EventEmitter);
Expand Down
33 changes: 33 additions & 0 deletions test/parallel/test-dgram-close-signal.js
@@ -0,0 +1,33 @@
'use strict';

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

{
// Test bad signal.
assert.throws(
() => dgram.createSocket({ type: 'udp4', signal: {} }),
{
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError'
});
}

{
// Test close.
const controller = new AbortController();
const { signal } = controller;
const server = dgram.createSocket({ type: 'udp4', signal });
server.on('close', common.mustCall());
controller.abort();
}

{
// Test close with pre-aborted signal.
const controller = new AbortController();
controller.abort();
const { signal } = controller;
const server = dgram.createSocket({ type: 'udp4', signal });
server.on('close', common.mustCall());
}