From fddc701d3c0eb4520f2af570876cc987ae6b4ba2 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Wed, 10 Aug 2022 00:41:23 +0800 Subject: [PATCH] src: support diagnostics channel in the snapshot PR-URL: https://github.com/nodejs/node/pull/44193 Refs: https://github.com/nodejs/node/issues/44014 Refs: https://github.com/nodejs/node/issues/37476 Reviewed-By: Matteo Collina Reviewed-By: Chengzhong Wu --- lib/internal/main/mksnapshot.js | 4 +- lib/net.js | 20 ++------ test/fixtures/snapshot/server.js | 60 +++++++++++++++++++++++ test/parallel/test-bootstrap-modules.js | 1 + test/parallel/test-snapshot-net.js | 65 +++++++++++++++++++++++++ 5 files changed, 132 insertions(+), 18 deletions(-) create mode 100644 test/fixtures/snapshot/server.js create mode 100644 test/parallel/test-snapshot-net.js diff --git a/lib/internal/main/mksnapshot.js b/lib/internal/main/mksnapshot.js index b91ef29ef14da4..4add00e13c1fd2 100644 --- a/lib/internal/main/mksnapshot.js +++ b/lib/internal/main/mksnapshot.js @@ -48,7 +48,7 @@ const supportedModules = new SafeSet(new SafeArrayIterator([ 'constants', 'crypto', // 'dgram', - // 'diagnostics_channel', + 'diagnostics_channel', // 'dns', // 'dns/promises', // 'domain', @@ -60,7 +60,7 @@ const supportedModules = new SafeSet(new SafeArrayIterator([ // 'https', // 'inspector', // 'module', - // 'net', + 'net', 'os', 'path', 'path/posix', diff --git a/lib/net.js b/lib/net.js index a6f14cd034b7b2..eaa5e594e5f4ef 100644 --- a/lib/net.js +++ b/lib/net.js @@ -131,20 +131,9 @@ const noop = () => {}; const kPerfHooksNetConnectContext = Symbol('kPerfHooksNetConnectContext'); -let netClientSocketChannel; -let netServerSocketChannel; -function lazyChannels() { - // TODO(joyeecheung): support diagnostics channels in the snapshot. - // For now it is fine to create them lazily when there isn't a snapshot to - // build. If users need the channels they would have to create them first - // before invoking any built-ins that would publish to these channels - // anyway. - if (netClientSocketChannel === undefined) { - const dc = require('diagnostics_channel'); - netClientSocketChannel = dc.channel('net.client.socket'); - netServerSocketChannel = dc.channel('net.server.socket'); - } -} +const dc = require('diagnostics_channel'); +const netClientSocketChannel = dc.channel('net.client.socket'); +const netServerSocketChannel = dc.channel('net.server.socket'); const { hasObserver, @@ -217,7 +206,7 @@ function connect(...args) { const options = normalized[0]; debug('createConnection', normalized); const socket = new Socket(options); - lazyChannels(); + if (netClientSocketChannel.hasSubscribers) { netClientSocketChannel.publish({ socket, @@ -1761,7 +1750,6 @@ function onconnection(err, clientHandle) { socket.server = self; socket._server = self; self.emit('connection', socket); - lazyChannels(); if (netServerSocketChannel.hasSubscribers) { netServerSocketChannel.publish({ socket, diff --git a/test/fixtures/snapshot/server.js b/test/fixtures/snapshot/server.js new file mode 100644 index 00000000000000..c0f8c495972037 --- /dev/null +++ b/test/fixtures/snapshot/server.js @@ -0,0 +1,60 @@ +'use strict'; + +const net = require('net'); +const { + setDeserializeMainFunction +} = require('v8').startupSnapshot; +const dc = require('diagnostics_channel'); + +const echoServer = net.Server(function(connection) { + connection.on('data', function(chunk) { + connection.write(chunk); + }); + connection.on('end', function() { + connection.end(); + }); +}); + +const kNumChars = 256; +const buffer = new Uint8Array(kNumChars); +for (let i = 0; i < kNumChars; ++i) { + buffer[i] = i; +} + +let recv = ''; + +echoServer.on('listening', function() { + const port = this.address().port; + console.log(`server port`, port); + const c = net.createConnection({ host: '127.0.0.1', port }); + + c.on('data', function(chunk) { + recv += chunk.toString('latin1'); + + if (recv.length === buffer.length) { + c.end(); + } + }); + + c.on('connect', function() { + c.write(buffer); + }); + + c.on('close', function() { + console.log(`recv.length: ${recv.length}`); + echoServer.close(); + }); + +}); + +dc.subscribe('net.server.socket', (({ socket }) => { + console.log(`From server diagnostics channel:`, socket.localPort); +})); + +dc.subscribe('net.client.socket', (({ socket }) => { + console.log(`From client diagnostics channel`); +})); + +setDeserializeMainFunction(() => { + echoServer.listen(0); +}); diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index 9f578f17a7faa1..e2a39dd4e9e144 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -46,6 +46,7 @@ const expectedModules = new Set([ 'Internal Binding wasm_web_api', 'Internal Binding worker', 'NativeModule buffer', + 'NativeModule diagnostics_channel', 'NativeModule events', 'NativeModule fs', 'NativeModule internal/abort_controller', diff --git a/test/parallel/test-snapshot-net.js b/test/parallel/test-snapshot-net.js new file mode 100644 index 00000000000000..9406f16609e6b8 --- /dev/null +++ b/test/parallel/test-snapshot-net.js @@ -0,0 +1,65 @@ +'use strict'; + +// This tests that a local TCP server can be snapshotted and the +// diagnostics channels work across serialization. + +require('../common'); +const assert = require('assert'); +const { spawnSync } = require('child_process'); +const tmpdir = require('../common/tmpdir'); +const fixtures = require('../common/fixtures'); +const path = require('path'); +const fs = require('fs'); + +tmpdir.refresh(); +const blobPath = path.join(tmpdir.path, 'snapshot.blob'); +const entry = fixtures.path('snapshot', 'server.js'); +{ + const child = spawnSync(process.execPath, [ + '--snapshot-blob', + blobPath, + '--build-snapshot', + entry, + ], { + cwd: tmpdir.path + }); + if (child.status !== 0) { + console.log(child.stderr.toString()); + console.log(child.stdout.toString()); + assert.strictEqual(child.status, 0); + } + const stats = fs.statSync(path.join(tmpdir.path, 'snapshot.blob')); + assert(stats.isFile()); +} + +{ + const child = spawnSync(process.execPath, [ + '--snapshot-blob', + blobPath, + ], { + cwd: tmpdir.path, + env: { + ...process.env, + } + }); + + const stdout = child.stdout.toString().trim(); + console.log(`[stdout]:\n${stdout}\n`); + const stderr = child.stderr.toString().trim(); + console.log(`[stderr]:\n${stderr}\n`); + assert.strictEqual(child.status, 0); + + const lines = stdout.split('\n'); + assert.strictEqual(lines.length, 4); + + // The log should look like this: + // server port ${port} + // From client diagnostics channel + // From server diagnostics channel: ${port} + // recv.length: 256 + assert.match(lines[0], /server port (\d+)/); + const port = lines[0].match(/server port (\d+)/)[1]; + assert.match(lines[1], /From client diagnostics channel/); + assert.match(lines[2], new RegExp(`From server diagnostics channel: ${port}`)); + assert.match(lines[3], /recv\.length: 256/); +}