GeezerVison is showing github.com — src: handle report options on fatalerror · nodejs/node@04237ec
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: src/node_options.cc +14-14Lines changed: 14 additions & 14 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -582,10 +582,6 @@ PerIsolateOptionsParser::PerIsolateOptionsParser( | 582 | 582 | "generate diagnostic report on uncaught exceptions", | 583 | 583 | &PerIsolateOptions::report_uncaught_exception, | 584 | 584 | kAllowedInEnvironment); | 585 | | - AddOption("--report-compact", | 586 | | - "output compact single-line JSON", | 587 | | - &PerIsolateOptions::report_compact, | 588 | | - kAllowedInEnvironment); | 589 | 585 | AddOption("--report-on-signal", | 590 | 586 | "generate diagnostic report upon receiving signals", | 591 | 587 | &PerIsolateOptions::report_on_signal, | @@ -596,16 +592,6 @@ PerIsolateOptionsParser::PerIsolateOptionsParser( | 596 | 592 | &PerIsolateOptions::report_signal, | 597 | 593 | kAllowedInEnvironment); | 598 | 594 | Implies("--report-signal", "--report-on-signal"); | 599 | | - AddOption("--report-filename", | 600 | | - "define custom report file name." | 601 | | - " (default: YYYYMMDD.HHMMSS.PID.SEQUENCE#.txt)", | 602 | | - &PerIsolateOptions::report_filename, | 603 | | - kAllowedInEnvironment); | 604 | | - AddOption("--report-directory", | 605 | | - "define custom report pathname." | 606 | | - " (default: current working directory of Node.js process)", | 607 | | - &PerIsolateOptions::report_directory, | 608 | | - kAllowedInEnvironment); | 609 | 595 |
| 610 | 596 | Insert(eop, &PerIsolateOptions::get_per_env_options); | 611 | 597 | } | @@ -663,6 +649,20 @@ PerProcessOptionsParser::PerProcessOptionsParser( | 663 | 649 | AddOption("--v8-options", | 664 | 650 | "print V8 command line options", | 665 | 651 | &PerProcessOptions::print_v8_help); | | 652 | + AddOption("--report-compact", | | 653 | + "output compact single-line JSON", | | 654 | + &PerProcessOptions::report_compact, | | 655 | + kAllowedInEnvironment); | | 656 | + AddOption("--report-directory", | | 657 | + "define custom report pathname." | | 658 | + " (default: current working directory of Node.js process)", | | 659 | + &PerProcessOptions::report_directory, | | 660 | + kAllowedInEnvironment); | | 661 | + AddOption("--report-filename", | | 662 | + "define custom report file name." | | 663 | + " (default: YYYYMMDD.HHMMSS.PID.SEQUENCE#.txt)", | | 664 | + &PerProcessOptions::report_filename, | | 665 | + kAllowedInEnvironment); | 666 | 666 | AddOption("--report-on-fatalerror", | 667 | 667 | "generate diagnostic report on fatal (internal) errors", | 668 | 668 | &PerProcessOptions::report_on_fatalerror, | |
Collapse file Copy file name to clipboardExpand all lines: src/node_options.h +3-3Lines changed: 3 additions & 3 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -186,10 +186,7 @@ class PerIsolateOptions : public Options { | 186 | 186 | bool no_node_snapshot = false; | 187 | 187 | bool report_uncaught_exception = false; | 188 | 188 | bool report_on_signal = false; | 189 | | - bool report_compact = false; | 190 | 189 | std::string report_signal = "SIGUSR2"; | 191 | | - std::string report_filename; | 192 | | - std::string report_directory; | 193 | 190 | inline EnvironmentOptions* get_per_env_options(); | 194 | 191 | void CheckOptions(std::vector<std::string>* errors) override; | 195 | 192 | }; | @@ -236,6 +233,9 @@ class PerProcessOptions : public Options { | 236 | 233 | bool trace_sigint = false; | 237 | 234 | std::vector<std::string> cmdline; | 238 | 235 | bool report_on_fatalerror = false; | | 236 | + bool report_compact = false; | | 237 | + std::string report_directory; | | 238 | + std::string report_filename; | 239 | 239 |
| 240 | 240 | inline PerIsolateOptions* get_per_isolate_options(); | 241 | 241 | void CheckOptions(std::vector<std::string>* errors) override; | |
Collapse file Copy file name to clipboardExpand all lines: src/node_report.cc +32-16Lines changed: 32 additions & 16 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -32,7 +32,6 @@ using node::DiagnosticFilename; | 32 | 32 | using node::Environment; | 33 | 33 | using node::Mutex; | 34 | 34 | using node::NativeSymbolDebuggingContext; | 35 | | -using node::PerIsolateOptions; | 36 | 35 | using node::TIME_TYPE; | 37 | 36 | using node::worker::Worker; | 38 | 37 | using v8::HeapSpaceStatistics; | @@ -45,6 +44,8 @@ using v8::String; | 45 | 44 | using v8::V8; | 46 | 45 | using v8::Value; | 47 | 46 |
| | 47 | +namespace per_process = node::per_process; | | 48 | + | 48 | 49 | // Internal/static function declarations | 49 | 50 | static void WriteNodeReport(Isolate* isolate, | 50 | 51 | Environment* env, | @@ -70,29 +71,32 @@ static void PrintCpuInfo(JSONWriter* writer); | 70 | 71 | static void PrintNetworkInterfaceInfo(JSONWriter* writer); | 71 | 72 |
| 72 | 73 | // External function to trigger a report, writing to file. | 73 | | -// The 'name' parameter is in/out: an input filename is used | 74 | | -// if supplied, and the actual filename is returned. | 75 | 74 | std::string TriggerNodeReport(Isolate* isolate, | 76 | 75 | Environment* env, | 77 | 76 | const char* message, | 78 | 77 | const char* trigger, | 79 | 78 | const std::string& name, | 80 | 79 | Local<String> stackstr) { | 81 | 80 | std::string filename; | 82 | | - std::shared_ptr<PerIsolateOptions> options; | 83 | | - if (env != nullptr) options = env->isolate_data()->options(); | 84 | 81 |
| 85 | 82 | // Determine the required report filename. In order of priority: | 86 | 83 | // 1) supplied on API 2) configured on startup 3) default generated | 87 | 84 | if (!name.empty()) { | 88 | 85 | // Filename was specified as API parameter. | 89 | 86 | filename = name; | 90 | | - } else if (env != nullptr && options->report_filename.length() > 0) { | 91 | | - // File name was supplied via start-up option. | 92 | | - filename = options->report_filename; | 93 | 87 | } else { | 94 | | - filename = *DiagnosticFilename(env != nullptr ? env->thread_id() : 0, | 95 | | - "report", "json"); | | 88 | + std::string report_filename; | | 89 | + { | | 90 | + Mutex::ScopedLock lock(per_process::cli_options_mutex); | | 91 | + report_filename = per_process::cli_options->report_filename; | | 92 | + } | | 93 | + if (report_filename.length() > 0) { | | 94 | + // File name was supplied via start-up option. | | 95 | + filename = report_filename; | | 96 | + } else { | | 97 | + filename = *DiagnosticFilename(env != nullptr ? env->thread_id() : 0, | | 98 | + "report", "json"); | | 99 | + } | 96 | 100 | } | 97 | 101 |
| 98 | 102 | // Open the report file stream for writing. Supports stdout/err, | @@ -104,9 +108,14 @@ std::string TriggerNodeReport(Isolate* isolate, | 104 | 108 | } else if (filename == "stderr") { | 105 | 109 | outstream = &std::cerr; | 106 | 110 | } else { | | 111 | + std::string report_directory; | | 112 | + { | | 113 | + Mutex::ScopedLock lock(per_process::cli_options_mutex); | | 114 | + report_directory = per_process::cli_options->report_directory; | | 115 | + } | 107 | 116 | // Regular file. Append filename to directory path if one was specified | 108 | | - if (env != nullptr && options->report_directory.length() > 0) { | 109 | | - std::string pathname = options->report_directory; | | 117 | + if (report_directory.length() > 0) { | | 118 | + std::string pathname = report_directory; | 110 | 119 | pathname += node::kPathSeparator; | 111 | 120 | pathname += filename; | 112 | 121 | outfile.open(pathname, std::ios::out | std::ios::binary); | @@ -117,8 +126,8 @@ std::string TriggerNodeReport(Isolate* isolate, | 117 | 126 | if (!outfile.is_open()) { | 118 | 127 | std::cerr << "\nFailed to open Node.js report file: " << filename; | 119 | 128 |
| 120 | | - if (env != nullptr && options->report_directory.length() > 0) | 121 | | - std::cerr << " directory: " << options->report_directory; | | 129 | + if (report_directory.length() > 0) | | 130 | + std::cerr << " directory: " << report_directory; | 122 | 131 |
| 123 | 132 | std::cerr << " (errno: " << errno << ")" << std::endl; | 124 | 133 | return ""; | @@ -127,7 +136,11 @@ std::string TriggerNodeReport(Isolate* isolate, | 127 | 136 | std::cerr << "\nWriting Node.js report to file: " << filename; | 128 | 137 | } | 129 | 138 |
| 130 | | - bool compact = env != nullptr ? options->report_compact : true; | | 139 | + bool compact; | | 140 | + { | | 141 | + Mutex::ScopedLock lock(per_process::cli_options_mutex); | | 142 | + compact = per_process::cli_options->report_compact; | | 143 | + } | 131 | 144 | WriteNodeReport(isolate, env, message, trigger, filename, *outstream, | 132 | 145 | stackstr, compact); | 133 | 146 |
| @@ -136,7 +149,10 @@ std::string TriggerNodeReport(Isolate* isolate, | 136 | 149 | outfile.close(); | 137 | 150 | } | 138 | 151 |
| 139 | | - std::cerr << "\nNode.js report completed" << std::endl; | | 152 | + // Do not mix JSON and free-form text on stderr. | | 153 | + if (filename != "stderr") { | | 154 | + std::cerr << "\nNode.js report completed" << std::endl; | | 155 | + } | 140 | 156 | return filename; | 141 | 157 | } | 142 | 158 |
| |
Collapse file Copy file name to clipboardExpand all lines: src/node_report_module.cc +12-7Lines changed: 12 additions & 7 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -69,47 +69,52 @@ void GetReport(const FunctionCallbackInfo<Value>& info) { | 69 | 69 | } | 70 | 70 |
| 71 | 71 | static void GetCompact(const FunctionCallbackInfo<Value>& info) { | 72 | | - Environment* env = Environment::GetCurrent(info); | 73 | | - info.GetReturnValue().Set(env->isolate_data()->options()->report_compact); | | 72 | + node::Mutex::ScopedLock lock(node::per_process::cli_options_mutex); | | 73 | + info.GetReturnValue().Set(node::per_process::cli_options->report_compact); | 74 | 74 | } | 75 | 75 |
| 76 | 76 | static void SetCompact(const FunctionCallbackInfo<Value>& info) { | | 77 | + node::Mutex::ScopedLock lock(node::per_process::cli_options_mutex); | 77 | 78 | Environment* env = Environment::GetCurrent(info); | 78 | 79 | Isolate* isolate = env->isolate(); | 79 | 80 | bool compact = info[0]->ToBoolean(isolate)->Value(); | 80 | | - env->isolate_data()->options()->report_compact = compact; | | 81 | + node::per_process::cli_options->report_compact = compact; | 81 | 82 | } | 82 | 83 |
| 83 | 84 | static void GetDirectory(const FunctionCallbackInfo<Value>& info) { | | 85 | + node::Mutex::ScopedLock lock(node::per_process::cli_options_mutex); | 84 | 86 | Environment* env = Environment::GetCurrent(info); | 85 | | - std::string directory = env->isolate_data()->options()->report_directory; | | 87 | + std::string directory = node::per_process::cli_options->report_directory; | 86 | 88 | auto result = String::NewFromUtf8(env->isolate(), | 87 | 89 | directory.c_str(), | 88 | 90 | v8::NewStringType::kNormal); | 89 | 91 | info.GetReturnValue().Set(result.ToLocalChecked()); | 90 | 92 | } | 91 | 93 |
| 92 | 94 | static void SetDirectory(const FunctionCallbackInfo<Value>& info) { | | 95 | + node::Mutex::ScopedLock lock(node::per_process::cli_options_mutex); | 93 | 96 | Environment* env = Environment::GetCurrent(info); | 94 | 97 | CHECK(info[0]->IsString()); | 95 | 98 | Utf8Value dir(env->isolate(), info[0].As<String>()); | 96 | | - env->isolate_data()->options()->report_directory = *dir; | | 99 | + node::per_process::cli_options->report_directory = *dir; | 97 | 100 | } | 98 | 101 |
| 99 | 102 | static void GetFilename(const FunctionCallbackInfo<Value>& info) { | | 103 | + node::Mutex::ScopedLock lock(node::per_process::cli_options_mutex); | 100 | 104 | Environment* env = Environment::GetCurrent(info); | 101 | | - std::string filename = env->isolate_data()->options()->report_filename; | | 105 | + std::string filename = node::per_process::cli_options->report_filename; | 102 | 106 | auto result = String::NewFromUtf8(env->isolate(), | 103 | 107 | filename.c_str(), | 104 | 108 | v8::NewStringType::kNormal); | 105 | 109 | info.GetReturnValue().Set(result.ToLocalChecked()); | 106 | 110 | } | 107 | 111 |
| 108 | 112 | static void SetFilename(const FunctionCallbackInfo<Value>& info) { | | 113 | + node::Mutex::ScopedLock lock(node::per_process::cli_options_mutex); | 109 | 114 | Environment* env = Environment::GetCurrent(info); | 110 | 115 | CHECK(info[0]->IsString()); | 111 | 116 | Utf8Value name(env->isolate(), info[0].As<String>()); | 112 | | - env->isolate_data()->options()->report_filename = *name; | | 117 | + node::per_process::cli_options->report_filename = *name; | 113 | 118 | } | 114 | 119 |
| 115 | 120 | static void GetSignal(const FunctionCallbackInfo<Value>& info) { | |
Collapse file Copy file name to clipboard +93-17Lines changed: 93 additions & 17 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 1 | 1 | 'use strict'; | 2 | 2 |
| | 3 | +// Testcases for situations involving fatal errors, like Javascript heap OOM | | 4 | + | 3 | 5 | require('../common'); | 4 | 6 | const assert = require('assert'); | 5 | | -// Testcase to produce report on fatal error (javascript heap OOM) | | 7 | +const fs = require('fs'); | | 8 | +const helper = require('../common/report.js'); | | 9 | +const spawnSync = require('child_process').spawnSync; | | 10 | +const tmpdir = require('../common/tmpdir'); | | 11 | + | 6 | 12 | if (process.argv[2] === 'child') { | 7 | 13 |
| 8 | 14 | const list = []; | @@ -16,30 +22,100 @@ if (process.argv[2] === 'child') { | 16 | 22 | this.id = 128; | 17 | 23 | this.account = 98454324; | 18 | 24 | } | 19 | | -} else { | 20 | | - const helper = require('../common/report.js'); | 21 | | - const tmpdir = require('../common/tmpdir'); | | 25 | +} | | 26 | + | | 27 | +// Common args that will cause an out-of-memory error for child process. | | 28 | +const ARGS = [ | | 29 | + '--max-old-space-size=20', | | 30 | + __filename, | | 31 | + 'child' | | 32 | +]; | | 33 | + | | 34 | +{ | | 35 | + // Verify that --report-on-fatalerror is respected when set. | 22 | 36 | tmpdir.refresh(); | 23 | | - const spawnSync = require('child_process').spawnSync; | 24 | | - let args = ['--report-on-fatalerror', | 25 | | - '--max-old-space-size=20', | 26 | | - __filename, | 27 | | - 'child']; | | 37 | + const args = ['--report-on-fatalerror', ...ARGS]; | | 38 | + const child = spawnSync(process.execPath, args, { cwd: tmpdir.path }); | | 39 | + assert.notStrictEqual(child.status, 0, 'Process exited unexpectedly'); | 28 | 40 |
| 29 | | - let child = spawnSync(process.execPath, args, { cwd: tmpdir.path }); | | 41 | + const reports = helper.findReports(child.pid, tmpdir.path); | | 42 | + assert.strictEqual(reports.length, 1); | 30 | 43 |
| | 44 | + const report = reports[0]; | | 45 | + helper.validate(report); | | 46 | + | | 47 | + // Errors occur in a context where env is not available, so thread ID is | | 48 | + // unknown. Assert this, to verify that the underlying env-less situation is | | 49 | + // actually reached. | | 50 | + assert.strictEqual(require(report).header.threadId, null); | | 51 | +} | | 52 | + | | 53 | +{ | | 54 | + // Verify that --report-on-fatalerror is respected when not set. | | 55 | + const args = ARGS; | | 56 | + const child = spawnSync(process.execPath, args, { cwd: tmpdir.path }); | | 57 | + assert.notStrictEqual(child.status, 0, 'Process exited unexpectedly'); | | 58 | + const reports = helper.findReports(child.pid, tmpdir.path); | | 59 | + assert.strictEqual(reports.length, 0); | | 60 | +} | | 61 | + | | 62 | +{ | | 63 | + // Verify that --report-directory is respected when set. | | 64 | + // Verify that --report-compact is respected when not set. | | 65 | + tmpdir.refresh(); | | 66 | + const dir = '--report-directory=' + tmpdir.path; | | 67 | + const args = ['--report-on-fatalerror', dir, ...ARGS]; | | 68 | + const child = spawnSync(process.execPath, args, { }); | 31 | 69 | assert.notStrictEqual(child.status, 0, 'Process exited unexpectedly'); | 32 | | - let reports = helper.findReports(child.pid, tmpdir.path); | | 70 | + | | 71 | + const reports = helper.findReports(child.pid, tmpdir.path); | | 72 | + assert.strictEqual(reports.length, 1); | | 73 | + | | 74 | + const report = reports[0]; | | 75 | + helper.validate(report); | | 76 | + assert.strictEqual(require(report).header.threadId, null); | | 77 | + const lines = fs.readFileSync(report, 'utf8').split('\n').length - 1; | | 78 | + assert(lines > 10); | | 79 | +} | | 80 | + | | 81 | +{ | | 82 | + // Verify that --report-compact is respected when set. | | 83 | + tmpdir.refresh(); | | 84 | + const args = ['--report-on-fatalerror', '--report-compact', ...ARGS]; | | 85 | + const child = spawnSync(process.execPath, args, { cwd: tmpdir.path }); | | 86 | + assert.notStrictEqual(child.status, 0, 'Process exited unexpectedly'); | | 87 | + | | 88 | + const reports = helper.findReports(child.pid, tmpdir.path); | 33 | 89 | assert.strictEqual(reports.length, 1); | | 90 | + | 34 | 91 | const report = reports[0]; | 35 | 92 | helper.validate(report); | 36 | | - // Verify that reports are not created on fatal error by default. | 37 | | - args = ['--max-old-space-size=20', | 38 | | - __filename, | 39 | | - 'child']; | | 93 | + assert.strictEqual(require(report).header.threadId, null); | | 94 | + // Subtract 1 because "xx\n".split("\n") => [ 'xx', '' ]. | | 95 | + const lines = fs.readFileSync(report, 'utf8').split('\n').length - 1; | | 96 | + assert.strictEqual(lines, 1); | | 97 | +} | 40 | 98 |
| 41 | | - child = spawnSync(process.execPath, args, { cwd: tmpdir.path }); | | 99 | +{ | | 100 | + // Verify that --report-compact is respected when set. | | 101 | + // Verify that --report-filename is respected when set. | | 102 | + tmpdir.refresh(); | | 103 | + const args = [ | | 104 | + '--report-on-fatalerror', | | 105 | + '--report-compact', | | 106 | + '--report-filename=stderr', | | 107 | + ...ARGS | | 108 | + ]; | | 109 | + const child = spawnSync(process.execPath, args, { encoding: 'utf8' }); | 42 | 110 | assert.notStrictEqual(child.status, 0, 'Process exited unexpectedly'); | 43 | | - reports = helper.findReports(child.pid, tmpdir.path); | | 111 | + | | 112 | + const reports = helper.findReports(child.pid, tmpdir.path); | 44 | 113 | assert.strictEqual(reports.length, 0); | | 114 | + | | 115 | + const lines = child.stderr.split('\n'); | | 116 | + // Skip over unavoidable free-form output from V8. | | 117 | + const report = lines[1]; | | 118 | + const json = JSON.parse(report); | | 119 | + | | 120 | + assert.strictEqual(json.header.threadId, null); | 45 | 121 | } |
|