Skip to content

Commit 0a655cb

Browse files
committed
async_hooks: use correct resource for AsyncLocalStorage
Fixes: #40693 Signed-off-by: Darshan Sen <[email protected]>
1 parent dd52c05 commit 0a655cb

File tree

4 files changed

+106
-4
lines changed

4 files changed

+106
-4
lines changed

lib/async_hooks.js

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -252,10 +252,29 @@ class AsyncResource {
252252
}
253253
}
254254

255+
function lookupResourceWithStorage() {
256+
// When a TCP/UDP object is created, its owner_symbol is uninitialized, so
257+
// calling executionAsyncResource() would return the TCP/UDP object itself.
258+
// So the store is initially saved on the TCP/UDP object. Later on, when the
259+
// Socket/TLSSocket object is created, the TCP/UDP object is wrapped by the
260+
// Socket/TLSSocket object in the _handle property and the owner_symbol of the
261+
// TCP/UDP object is set to the corresponding Socket/TLSSocket object. So
262+
// calling executionAsyncResource() now would return the Socket/TLSSocket
263+
// object, which does not contain the storage that was saved initially. Hence,
264+
// in the case of a Socket/TLSSocket object, we must use the underlying
265+
// TCP/UDP object, if available, for storage.
266+
let resource = executionAsyncResource();
267+
if ((resource.constructor.name === 'Socket' ||
268+
resource.constructor.name === 'TLSSocket') &&
269+
resource._handle != null)
270+
resource = resource._handle;
271+
return resource;
272+
}
273+
255274
const storageList = [];
256275
const storageHook = createHook({
257276
init(asyncId, type, triggerAsyncId, resource) {
258-
const currentResource = executionAsyncResource();
277+
const currentResource = lookupResourceWithStorage();
259278
// Value of currentResource is always a non null object
260279
for (let i = 0; i < storageList.length; ++i) {
261280
storageList[i]._propagate(resource, currentResource);
@@ -299,7 +318,7 @@ class AsyncLocalStorage {
299318

300319
enterWith(store) {
301320
this._enable();
302-
const resource = executionAsyncResource();
321+
const resource = lookupResourceWithStorage();
303322
resource[this.kResourceStore] = store;
304323
}
305324

@@ -311,7 +330,7 @@ class AsyncLocalStorage {
311330

312331
this._enable();
313332

314-
const resource = executionAsyncResource();
333+
const resource = lookupResourceWithStorage();
315334
const oldStore = resource[this.kResourceStore];
316335

317336
resource[this.kResourceStore] = store;
@@ -337,7 +356,7 @@ class AsyncLocalStorage {
337356

338357
getStore() {
339358
if (this.enabled) {
340-
const resource = executionAsyncResource();
359+
const resource = lookupResourceWithStorage();
341360
return resource[this.kResourceStore];
342361
}
343362
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict';
2+
3+
require('../common');
4+
5+
const assert = require('assert');
6+
const dgram = require('dgram');
7+
const { AsyncLocalStorage } = require('async_hooks');
8+
9+
dgram.createSocket('udp4')
10+
.on('message', function(msg, rinfo) { this.send(msg, rinfo.port); })
11+
.on('listening', function() {
12+
const asyncLocalStorage = new AsyncLocalStorage();
13+
const store = { val: 'abcd' };
14+
asyncLocalStorage.run(store, () => {
15+
const client = dgram.createSocket('udp4');
16+
client.on('message', (msg, rinfo) => {
17+
assert.deepStrictEqual(asyncLocalStorage.getStore(), store);
18+
client.close();
19+
this.close();
20+
});
21+
client.send('Hello, world!', this.address().port);
22+
});
23+
})
24+
.bind(0);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict';
2+
3+
require('../common');
4+
5+
const assert = require('assert');
6+
const net = require('net');
7+
const { AsyncLocalStorage } = require('async_hooks');
8+
9+
net
10+
.createServer((socket) => {
11+
socket.write('Hello, world!');
12+
socket.pipe(socket);
13+
})
14+
.listen(0, function() {
15+
const asyncLocalStorage = new AsyncLocalStorage();
16+
const store = { val: 'abcd' };
17+
asyncLocalStorage.run({ val: 'abcd' }, () => {
18+
const client = net.connect({ port: this.address().port });
19+
client.on('data', () => {
20+
assert.deepStrictEqual(asyncLocalStorage.getStore(), store);
21+
client.end();
22+
this.close();
23+
});
24+
});
25+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
if (!common.hasCrypto)
5+
common.skip('missing crypto');
6+
7+
const assert = require('assert');
8+
const fixtures = require('../common/fixtures');
9+
const tls = require('tls');
10+
const { AsyncLocalStorage } = require('async_hooks');
11+
12+
const options = {
13+
cert: fixtures.readKey('rsa_cert.crt'),
14+
key: fixtures.readKey('rsa_private.pem'),
15+
rejectUnauthorized: false
16+
};
17+
18+
tls
19+
.createServer(options, (socket) => {
20+
socket.write('Hello, world!');
21+
socket.pipe(socket);
22+
})
23+
.listen(0, function() {
24+
const asyncLocalStorage = new AsyncLocalStorage();
25+
const store = { val: 'abcd' };
26+
asyncLocalStorage.run({ val: 'abcd' }, () => {
27+
const client = tls.connect({ port: this.address().port, ...options });
28+
client.on('data', () => {
29+
assert.deepStrictEqual(asyncLocalStorage.getStore(), store);
30+
client.end();
31+
this.close();
32+
});
33+
});
34+
});

0 commit comments

Comments
 (0)