Skip to content

Commit 867ff41

Browse files
committed
src: handle report options on fatalerror
Follow on to #32207, 3 other options are also not respected under situations that the isolate is not available. PR-URL: #32497 Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com> Reviewed-By: Richard Lau <riclau@uk.ibm.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent e158218 commit 867ff41

5 files changed

+154
-57
lines changed

src/node_options.cc

+14-14
Original file line numberDiff line numberDiff line change
@@ -582,10 +582,6 @@ PerIsolateOptionsParser::PerIsolateOptionsParser(
582582
"generate diagnostic report on uncaught exceptions",
583583
&PerIsolateOptions::report_uncaught_exception,
584584
kAllowedInEnvironment);
585-
AddOption("--report-compact",
586-
"output compact single-line JSON",
587-
&PerIsolateOptions::report_compact,
588-
kAllowedInEnvironment);
589585
AddOption("--report-on-signal",
590586
"generate diagnostic report upon receiving signals",
591587
&PerIsolateOptions::report_on_signal,
@@ -596,16 +592,6 @@ PerIsolateOptionsParser::PerIsolateOptionsParser(
596592
&PerIsolateOptions::report_signal,
597593
kAllowedInEnvironment);
598594
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);
609595

610596
Insert(eop, &PerIsolateOptions::get_per_env_options);
611597
}
@@ -663,6 +649,20 @@ PerProcessOptionsParser::PerProcessOptionsParser(
663649
AddOption("--v8-options",
664650
"print V8 command line options",
665651
&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);
666666
AddOption("--report-on-fatalerror",
667667
"generate diagnostic report on fatal (internal) errors",
668668
&PerProcessOptions::report_on_fatalerror,

src/node_options.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,7 @@ class PerIsolateOptions : public Options {
186186
bool no_node_snapshot = false;
187187
bool report_uncaught_exception = false;
188188
bool report_on_signal = false;
189-
bool report_compact = false;
190189
std::string report_signal = "SIGUSR2";
191-
std::string report_filename;
192-
std::string report_directory;
193190
inline EnvironmentOptions* get_per_env_options();
194191
void CheckOptions(std::vector<std::string>* errors) override;
195192
};
@@ -236,6 +233,9 @@ class PerProcessOptions : public Options {
236233
bool trace_sigint = false;
237234
std::vector<std::string> cmdline;
238235
bool report_on_fatalerror = false;
236+
bool report_compact = false;
237+
std::string report_directory;
238+
std::string report_filename;
239239

240240
inline PerIsolateOptions* get_per_isolate_options();
241241
void CheckOptions(std::vector<std::string>* errors) override;

src/node_report.cc

+32-16
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ using node::DiagnosticFilename;
3232
using node::Environment;
3333
using node::Mutex;
3434
using node::NativeSymbolDebuggingContext;
35-
using node::PerIsolateOptions;
3635
using node::TIME_TYPE;
3736
using node::worker::Worker;
3837
using v8::HeapSpaceStatistics;
@@ -45,6 +44,8 @@ using v8::String;
4544
using v8::V8;
4645
using v8::Value;
4746

47+
namespace per_process = node::per_process;
48+
4849
// Internal/static function declarations
4950
static void WriteNodeReport(Isolate* isolate,
5051
Environment* env,
@@ -70,29 +71,32 @@ static void PrintCpuInfo(JSONWriter* writer);
7071
static void PrintNetworkInterfaceInfo(JSONWriter* writer);
7172

7273
// 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.
7574
std::string TriggerNodeReport(Isolate* isolate,
7675
Environment* env,
7776
const char* message,
7877
const char* trigger,
7978
const std::string& name,
8079
Local<String> stackstr) {
8180
std::string filename;
82-
std::shared_ptr<PerIsolateOptions> options;
83-
if (env != nullptr) options = env->isolate_data()->options();
8481

8582
// Determine the required report filename. In order of priority:
8683
// 1) supplied on API 2) configured on startup 3) default generated
8784
if (!name.empty()) {
8885
// Filename was specified as API parameter.
8986
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;
9387
} 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+
}
96100
}
97101

98102
// Open the report file stream for writing. Supports stdout/err,
@@ -104,9 +108,14 @@ std::string TriggerNodeReport(Isolate* isolate,
104108
} else if (filename == "stderr") {
105109
outstream = &std::cerr;
106110
} 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+
}
107116
// 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;
110119
pathname += node::kPathSeparator;
111120
pathname += filename;
112121
outfile.open(pathname, std::ios::out | std::ios::binary);
@@ -117,8 +126,8 @@ std::string TriggerNodeReport(Isolate* isolate,
117126
if (!outfile.is_open()) {
118127
std::cerr << "\nFailed to open Node.js report file: " << filename;
119128

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;
122131

123132
std::cerr << " (errno: " << errno << ")" << std::endl;
124133
return "";
@@ -127,7 +136,11 @@ std::string TriggerNodeReport(Isolate* isolate,
127136
std::cerr << "\nWriting Node.js report to file: " << filename;
128137
}
129138

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+
}
131144
WriteNodeReport(isolate, env, message, trigger, filename, *outstream,
132145
stackstr, compact);
133146

@@ -136,7 +149,10 @@ std::string TriggerNodeReport(Isolate* isolate,
136149
outfile.close();
137150
}
138151

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+
}
140156
return filename;
141157
}
142158

src/node_report_module.cc

+12-7
Original file line numberDiff line numberDiff line change
@@ -69,47 +69,52 @@ void GetReport(const FunctionCallbackInfo<Value>& info) {
6969
}
7070

7171
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);
7474
}
7575

7676
static void SetCompact(const FunctionCallbackInfo<Value>& info) {
77+
node::Mutex::ScopedLock lock(node::per_process::cli_options_mutex);
7778
Environment* env = Environment::GetCurrent(info);
7879
Isolate* isolate = env->isolate();
7980
bool compact = info[0]->ToBoolean(isolate)->Value();
80-
env->isolate_data()->options()->report_compact = compact;
81+
node::per_process::cli_options->report_compact = compact;
8182
}
8283

8384
static void GetDirectory(const FunctionCallbackInfo<Value>& info) {
85+
node::Mutex::ScopedLock lock(node::per_process::cli_options_mutex);
8486
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;
8688
auto result = String::NewFromUtf8(env->isolate(),
8789
directory.c_str(),
8890
v8::NewStringType::kNormal);
8991
info.GetReturnValue().Set(result.ToLocalChecked());
9092
}
9193

9294
static void SetDirectory(const FunctionCallbackInfo<Value>& info) {
95+
node::Mutex::ScopedLock lock(node::per_process::cli_options_mutex);
9396
Environment* env = Environment::GetCurrent(info);
9497
CHECK(info[0]->IsString());
9598
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;
97100
}
98101

99102
static void GetFilename(const FunctionCallbackInfo<Value>& info) {
103+
node::Mutex::ScopedLock lock(node::per_process::cli_options_mutex);
100104
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;
102106
auto result = String::NewFromUtf8(env->isolate(),
103107
filename.c_str(),
104108
v8::NewStringType::kNormal);
105109
info.GetReturnValue().Set(result.ToLocalChecked());
106110
}
107111

108112
static void SetFilename(const FunctionCallbackInfo<Value>& info) {
113+
node::Mutex::ScopedLock lock(node::per_process::cli_options_mutex);
109114
Environment* env = Environment::GetCurrent(info);
110115
CHECK(info[0]->IsString());
111116
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;
113118
}
114119

115120
static void GetSignal(const FunctionCallbackInfo<Value>& info) {
+93-17
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
'use strict';
22

3+
// Testcases for situations involving fatal errors, like Javascript heap OOM
4+
35
require('../common');
46
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+
612
if (process.argv[2] === 'child') {
713

814
const list = [];
@@ -16,30 +22,100 @@ if (process.argv[2] === 'child') {
1622
this.id = 128;
1723
this.account = 98454324;
1824
}
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.
2236
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');
2840

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);
3043

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, { });
3169
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);
3389
assert.strictEqual(reports.length, 1);
90+
3491
const report = reports[0];
3592
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+
}
4098

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' });
42110
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);
44113
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);
45121
}

0 commit comments

Comments
 (0)