From 1bcf2f942383e14181bf3118123ca7af57384c87 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Thu, 16 Jan 2020 18:19:10 +0100 Subject: [PATCH] report: add support for Workers Include a report for each sub-Worker of the current Node.js instance. This adds a feature that is necessary for eventually making the report feature stable, as was discussed during the last collaborator summit. Refs: https://github.com/openjs-foundation/summit/pull/240 PR-URL: https://github.com/nodejs/node/pull/31386 Reviewed-By: Gireesh Punathil Reviewed-By: James M Snell Reviewed-By: Colin Ihrig Reviewed-By: Rich Trott --- doc/api/report.md | 21 +++++++++++++ src/env-inl.h | 5 ++++ src/env.h | 2 ++ src/node_report.cc | 43 +++++++++++++++++++++++++- src/node_report.h | 9 ++++++ src/node_report_utils.cc | 24 +++++++++++++++ test/common/report.js | 11 +++++-- test/report/test-report-worker.js | 50 +++++++++++++++++++++++++++++++ 8 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 test/report/test-report-worker.js diff --git a/doc/api/report.md b/doc/api/report.md index b39a5286398b2c..b5ff3d8a88e3e5 100644 --- a/doc/api/report.md +++ b/doc/api/report.md @@ -296,6 +296,7 @@ is provided below for reference. "address": "0x000055fc7b2cb180" } ], + "workers": [], "environmentVariables": { "REMOTEHOST": "REMOVED", "MANPATH": "/opt/rh/devtoolset-3/root/usr/share/man:", @@ -577,4 +578,24 @@ NODE_OPTIONS="--experimental-report --report-uncaught-exception \ Specific API documentation can be found under [`process API documentation`][] section. +## Interaction with Workers + + +[`Worker`][] threads can create reports in the same way that the main thread +does. + +Reports will include information on any Workers that are children of the current +thread as part of the `workers` section, with each Worker generating a report +in the standard report format. + +The thread which is generating the report will wait for the reports from Worker +threads to finish. However, the latency for this will usually be low, as both +running JavaScript and the event loop are interrupted to generate the report. + [`process API documentation`]: process.html +[`Worker`]: worker_threads.html diff --git a/src/env-inl.h b/src/env-inl.h index 3226dc92d63fa6..62524f09b9e21d 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -906,6 +906,11 @@ inline void Environment::remove_sub_worker_context(worker::Worker* context) { sub_worker_contexts_.erase(context); } +template +inline void Environment::ForEachWorker(Fn&& iterator) { + for (worker::Worker* w : sub_worker_contexts_) iterator(w); +} + inline void Environment::add_refs(int64_t diff) { task_queues_async_refs_ += diff; CHECK_GE(task_queues_async_refs_, 0); diff --git a/src/env.h b/src/env.h index c2d7188a35de07..4e6170f725167b 100644 --- a/src/env.h +++ b/src/env.h @@ -1061,6 +1061,8 @@ class Environment : public MemoryRetainer { inline void add_sub_worker_context(worker::Worker* context); inline void remove_sub_worker_context(worker::Worker* context); void stop_sub_worker_contexts(); + template + inline void ForEachWorker(Fn&& iterator); inline bool is_stopping() const; inline void set_stopping(bool value); inline std::list* extra_linked_bindings(); diff --git a/src/node_report.cc b/src/node_report.cc index ddeb216c82d6bc..9b32352326becf 100644 --- a/src/node_report.cc +++ b/src/node_report.cc @@ -4,6 +4,8 @@ #include "diagnosticfilename-inl.h" #include "node_internals.h" #include "node_metadata.h" +#include "node_mutex.h" +#include "node_worker.h" #include "util.h" #ifdef _WIN32 @@ -19,18 +21,20 @@ #include #include -constexpr int NODE_REPORT_VERSION = 1; +constexpr int NODE_REPORT_VERSION = 2; constexpr int NANOS_PER_SEC = 1000 * 1000 * 1000; constexpr double SEC_PER_MICROS = 1e-6; namespace report { using node::arraysize; +using node::ConditionVariable; using node::DiagnosticFilename; using node::Environment; using node::Mutex; using node::NativeSymbolDebuggingContext; using node::PerIsolateOptions; using node::TIME_TYPE; +using node::worker::Worker; using v8::HeapSpaceStatistics; using v8::HeapStatistics; using v8::Isolate; @@ -210,6 +214,10 @@ static void WriteNodeReport(Isolate* isolate, // Report native process ID writer.json_keyvalue("processId", pid); + if (env != nullptr) + writer.json_keyvalue("threadId", env->thread_id()); + else + writer.json_keyvalue("threadId", JSONWriter::Null{}); { // Report the process cwd. @@ -259,6 +267,39 @@ static void WriteNodeReport(Isolate* isolate, writer.json_arrayend(); + writer.json_arraystart("workers"); + if (env != nullptr) { + Mutex workers_mutex; + ConditionVariable notify; + std::vector worker_infos; + size_t expected_results = 0; + + env->ForEachWorker([&](Worker* w) { + expected_results += w->RequestInterrupt([&](Environment* env) { + std::ostringstream os; + + GetNodeReport(env->isolate(), + env, + "Worker thread subreport", + trigger, + Local(), + os); + + Mutex::ScopedLock lock(workers_mutex); + worker_infos.emplace_back(os.str()); + notify.Signal(lock); + }); + }); + + Mutex::ScopedLock lock(workers_mutex); + worker_infos.reserve(expected_results); + while (worker_infos.size() < expected_results) + notify.Wait(lock); + for (const std::string& worker_info : worker_infos) + writer.json_element(JSONWriter::ForeignJSON { worker_info }); + } + writer.json_arrayend(); + // Report operating system information PrintSystemInformation(&writer); diff --git a/src/node_report.h b/src/node_report.h index 4cb82470f43594..46b69b9681db51 100644 --- a/src/node_report.h +++ b/src/node_report.h @@ -44,6 +44,7 @@ void GetNodeReport(v8::Isolate* isolate, // Function declarations - utility functions in src/node_report_utils.cc void WalkHandle(uv_handle_t* h, void* arg); std::string EscapeJsonChars(const std::string& str); +std::string Reindent(const std::string& str, int indentation); template std::string ValueToHexString(T value) { @@ -146,6 +147,10 @@ class JSONWriter { struct Null {}; // Usable as a JSON value. + struct ForeignJSON { + std::string as_string; + }; + private: template { @@ -253,6 +254,10 @@ function _validateContent(report) { report.sharedObjects.forEach((sharedObject) => { assert.strictEqual(typeof sharedObject, 'string'); }); + + // Verify the format of the workers section. + assert(Array.isArray(report.workers)); + report.workers.forEach(_validateContent); } function checkForUnknownFields(actual, expected) { diff --git a/test/report/test-report-worker.js b/test/report/test-report-worker.js new file mode 100644 index 00000000000000..a34c05f08431de --- /dev/null +++ b/test/report/test-report-worker.js @@ -0,0 +1,50 @@ +// Flags: --experimental-report +'use strict'; +const common = require('../common'); +common.skipIfReportDisabled(); +const assert = require('assert'); +const { Worker } = require('worker_threads'); +const { once } = require('events'); +const helper = require('../common/report'); + +async function basic() { + // Test that the report includes basic information about Worker threads. + + const w = new Worker(` + const { parentPort } = require('worker_threads'); + parentPort.once('message', () => { + /* Wait for message to stop the Worker */ + }); + `, { eval: true }); + + await once(w, 'online'); + + const report = process.report.getReport(); + helper.validateContent(report); + assert.strictEqual(report.workers.length, 1); + helper.validateContent(report.workers[0]); + + w.postMessage({}); + + await once(w, 'exit'); +} + +async function interruptingJS() { + // Test that the report also works when Worker threads are busy in JS land. + + const w = new Worker('while (true);', { eval: true }); + + await once(w, 'online'); + + const report = process.report.getReport(); + helper.validateContent(report); + assert.strictEqual(report.workers.length, 1); + helper.validateContent(report.workers[0]); + + await w.terminate(); +} + +(async function() { + await basic(); + await interruptingJS(); +})().then(common.mustCall());