Merge pull request #8109 from ramya-rao-a/crash-for-children
Expose crash reporter start for child node processes
This commit is contained in:
commit
ce472315f4
7 changed files with 127 additions and 55 deletions
|
@ -7,18 +7,16 @@
|
||||||
#include "atom/app/uv_task_runner.h"
|
#include "atom/app/uv_task_runner.h"
|
||||||
#include "atom/browser/javascript_environment.h"
|
#include "atom/browser/javascript_environment.h"
|
||||||
#include "atom/browser/node_debugger.h"
|
#include "atom/browser/node_debugger.h"
|
||||||
|
#include "atom/common/api/atom_bindings.h"
|
||||||
|
#include "atom/common/crash_reporter/crash_reporter.h"
|
||||||
|
#include "atom/common/native_mate_converters/string16_converter.h"
|
||||||
#include "base/command_line.h"
|
#include "base/command_line.h"
|
||||||
#include "base/feature_list.h"
|
#include "base/feature_list.h"
|
||||||
#include "base/threading/thread_task_runner_handle.h"
|
#include "base/threading/thread_task_runner_handle.h"
|
||||||
#include "gin/array_buffer.h"
|
#include "gin/array_buffer.h"
|
||||||
#include "gin/public/isolate_holder.h"
|
#include "gin/public/isolate_holder.h"
|
||||||
#include "gin/v8_initializer.h"
|
#include "gin/v8_initializer.h"
|
||||||
|
|
||||||
#if defined(OS_WIN)
|
|
||||||
#include "atom/common/api/atom_bindings.h"
|
|
||||||
#include "atom/common/native_mate_converters/string16_converter.h"
|
|
||||||
#include "native_mate/dictionary.h"
|
#include "native_mate/dictionary.h"
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "atom/common/node_includes.h"
|
#include "atom/common/node_includes.h"
|
||||||
|
|
||||||
|
@ -58,10 +56,16 @@ int NodeMain(int argc, char *argv[]) {
|
||||||
if (node_debugger.IsRunning())
|
if (node_debugger.IsRunning())
|
||||||
env->AssignToContext(v8::Debug::GetDebugContext(gin_env.isolate()));
|
env->AssignToContext(v8::Debug::GetDebugContext(gin_env.isolate()));
|
||||||
|
|
||||||
#if defined(OS_WIN)
|
|
||||||
mate::Dictionary process(gin_env.isolate(), env->process_object());
|
mate::Dictionary process(gin_env.isolate(), env->process_object());
|
||||||
|
#if defined(OS_WIN)
|
||||||
process.SetMethod("log", &AtomBindings::Log);
|
process.SetMethod("log", &AtomBindings::Log);
|
||||||
#endif
|
#endif
|
||||||
|
process.SetMethod("crash", &AtomBindings::Crash);
|
||||||
|
|
||||||
|
// Setup process.crashReporter.start in child node processes
|
||||||
|
auto reporter = mate::Dictionary::CreateEmpty(gin_env.isolate());
|
||||||
|
reporter.SetMethod("start", &crash_reporter::CrashReporter::StartInstance);
|
||||||
|
process.Set("crashReporter", reporter);
|
||||||
|
|
||||||
node::LoadEnvironment(env);
|
node::LoadEnvironment(env);
|
||||||
|
|
||||||
|
|
|
@ -23,10 +23,6 @@ namespace {
|
||||||
// Dummy class type that used for crashing the program.
|
// Dummy class type that used for crashing the program.
|
||||||
struct DummyClass { bool crash; };
|
struct DummyClass { bool crash; };
|
||||||
|
|
||||||
void Crash() {
|
|
||||||
static_cast<DummyClass*>(nullptr)->crash = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Hang() {
|
void Hang() {
|
||||||
for (;;)
|
for (;;)
|
||||||
base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1));
|
base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1));
|
||||||
|
@ -76,7 +72,7 @@ v8::Local<v8::Value> GetSystemMemoryInfo(v8::Isolate* isolate,
|
||||||
// we can get the stack trace.
|
// we can get the stack trace.
|
||||||
void FatalErrorCallback(const char* location, const char* message) {
|
void FatalErrorCallback(const char* location, const char* message) {
|
||||||
LOG(ERROR) << "Fatal error in V8: " << location << " " << message;
|
LOG(ERROR) << "Fatal error in V8: " << location << " " << message;
|
||||||
Crash();
|
AtomBindings::Crash();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -95,7 +91,7 @@ void AtomBindings::BindTo(v8::Isolate* isolate,
|
||||||
v8::V8::SetFatalErrorHandler(FatalErrorCallback);
|
v8::V8::SetFatalErrorHandler(FatalErrorCallback);
|
||||||
|
|
||||||
mate::Dictionary dict(isolate, process);
|
mate::Dictionary dict(isolate, process);
|
||||||
dict.SetMethod("crash", &Crash);
|
dict.SetMethod("crash", &AtomBindings::Crash);
|
||||||
dict.SetMethod("hang", &Hang);
|
dict.SetMethod("hang", &Hang);
|
||||||
dict.SetMethod("log", &Log);
|
dict.SetMethod("log", &Log);
|
||||||
dict.SetMethod("getProcessMemoryInfo", &GetProcessMemoryInfo);
|
dict.SetMethod("getProcessMemoryInfo", &GetProcessMemoryInfo);
|
||||||
|
@ -159,4 +155,9 @@ void AtomBindings::Log(const base::string16& message) {
|
||||||
std::cout << message << std::flush;
|
std::cout << message << std::flush;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
void AtomBindings::Crash() {
|
||||||
|
static_cast<DummyClass*>(nullptr)->crash = true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace atom
|
} // namespace atom
|
||||||
|
|
|
@ -28,6 +28,7 @@ class AtomBindings {
|
||||||
void BindTo(v8::Isolate* isolate, v8::Local<v8::Object> process);
|
void BindTo(v8::Isolate* isolate, v8::Local<v8::Object> process);
|
||||||
|
|
||||||
static void Log(const base::string16& message);
|
static void Log(const base::string16& message);
|
||||||
|
static void Crash();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ActivateUVLoop(v8::Isolate* isolate);
|
void ActivateUVLoop(v8::Isolate* isolate);
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include "atom/browser/browser.h"
|
#include "atom/browser/browser.h"
|
||||||
#include "atom/common/atom_version.h"
|
#include "atom/common/atom_version.h"
|
||||||
|
#include "atom/common/native_mate_converters/file_path_converter.h"
|
||||||
#include "base/command_line.h"
|
#include "base/command_line.h"
|
||||||
#include "base/files/file_util.h"
|
#include "base/files/file_util.h"
|
||||||
#include "base/strings/string_number_conversions.h"
|
#include "base/strings/string_number_conversions.h"
|
||||||
|
@ -93,4 +94,26 @@ CrashReporter* CrashReporter::GetInstance() {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void CrashReporter::StartInstance(const mate::Dictionary& options) {
|
||||||
|
auto reporter = GetInstance();
|
||||||
|
if (!reporter) return;
|
||||||
|
|
||||||
|
std::string product_name;
|
||||||
|
options.Get("productName", &product_name);
|
||||||
|
std::string company_name;
|
||||||
|
options.Get("companyName", &company_name);
|
||||||
|
std::string submit_url;
|
||||||
|
options.Get("submitURL", &submit_url);
|
||||||
|
base::FilePath crashes_dir;
|
||||||
|
options.Get("crashesDirectory", &crashes_dir);
|
||||||
|
StringMap extra_parameters;
|
||||||
|
options.Get("extra", &extra_parameters);
|
||||||
|
|
||||||
|
extra_parameters["_productName"] = product_name;
|
||||||
|
extra_parameters["_companyName"] = company_name;
|
||||||
|
|
||||||
|
reporter->Start(product_name, company_name, submit_url, crashes_dir, true,
|
||||||
|
false, extra_parameters);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace crash_reporter
|
} // namespace crash_reporter
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
#include "base/files/file_path.h"
|
#include "base/files/file_path.h"
|
||||||
#include "base/macros.h"
|
#include "base/macros.h"
|
||||||
|
#include "native_mate/dictionary.h"
|
||||||
|
|
||||||
namespace crash_reporter {
|
namespace crash_reporter {
|
||||||
|
|
||||||
|
@ -21,6 +22,7 @@ class CrashReporter {
|
||||||
typedef std::pair<int, std::string> UploadReportResult; // upload-date, id
|
typedef std::pair<int, std::string> UploadReportResult; // upload-date, id
|
||||||
|
|
||||||
static CrashReporter* GetInstance();
|
static CrashReporter* GetInstance();
|
||||||
|
static void StartInstance(const mate::Dictionary& options);
|
||||||
|
|
||||||
void Start(const std::string& product_name,
|
void Start(const std::string& product_name,
|
||||||
const std::string& company_name,
|
const std::string& company_name,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
|
const childProcess = require('child_process')
|
||||||
const http = require('http')
|
const http = require('http')
|
||||||
const multiparty = require('multiparty')
|
const multiparty = require('multiparty')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
@ -40,51 +41,34 @@ describe('crashReporter module', function () {
|
||||||
|
|
||||||
this.timeout(120000)
|
this.timeout(120000)
|
||||||
|
|
||||||
var called = false
|
startServer({
|
||||||
var server = http.createServer(function (req, res) {
|
callback (port) {
|
||||||
server.close()
|
|
||||||
var form = new multiparty.Form()
|
|
||||||
form.parse(req, function (error, fields) {
|
|
||||||
if (error) throw error
|
|
||||||
if (called) return
|
|
||||||
called = true
|
|
||||||
assert.equal(fields['prod'], 'Electron')
|
|
||||||
assert.equal(fields['ver'], process.versions.electron)
|
|
||||||
assert.equal(fields['process_type'], 'renderer')
|
|
||||||
assert.equal(fields['platform'], process.platform)
|
|
||||||
assert.equal(fields['extra1'], 'extra1')
|
|
||||||
assert.equal(fields['extra2'], 'extra2')
|
|
||||||
assert.equal(fields['_productName'], 'Zombies')
|
|
||||||
assert.equal(fields['_companyName'], 'Umbrella Corporation')
|
|
||||||
assert.equal(fields['_version'], app.getVersion())
|
|
||||||
|
|
||||||
const reportId = 'abc-123-def-456-abc-789-abc-123-abcd'
|
|
||||||
res.end(reportId, () => {
|
|
||||||
waitForCrashReport().then(() => {
|
|
||||||
assert.equal(crashReporter.getLastCrashReport().id, reportId)
|
|
||||||
assert.notEqual(crashReporter.getUploadedReports().length, 0)
|
|
||||||
assert.equal(crashReporter.getUploadedReports()[0].id, reportId)
|
|
||||||
done()
|
|
||||||
}, done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
var port = remote.process.port
|
|
||||||
server.listen(port, '127.0.0.1', function () {
|
|
||||||
port = server.address().port
|
|
||||||
remote.process.port = port
|
|
||||||
const crashUrl = url.format({
|
const crashUrl = url.format({
|
||||||
protocol: 'file',
|
protocol: 'file',
|
||||||
pathname: path.join(fixtures, 'api', 'crash.html'),
|
pathname: path.join(fixtures, 'api', 'crash.html'),
|
||||||
search: '?port=' + port
|
search: '?port=' + port
|
||||||
})
|
})
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
crashReporter.start({
|
|
||||||
companyName: 'Umbrella Corporation',
|
|
||||||
submitURL: 'http://127.0.0.1:' + port
|
|
||||||
})
|
|
||||||
}
|
|
||||||
w.loadURL(crashUrl)
|
w.loadURL(crashUrl)
|
||||||
|
},
|
||||||
|
processType: 'renderer',
|
||||||
|
done: done
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should send minidump when node processes crash', function (done) {
|
||||||
|
if (isCI) return done()
|
||||||
|
|
||||||
|
this.timeout(120000)
|
||||||
|
|
||||||
|
startServer({
|
||||||
|
callback (port) {
|
||||||
|
const crashesDir = path.join(app.getPath('temp'), `${app.getName()} Crashes`)
|
||||||
|
const version = app.getVersion()
|
||||||
|
const crashPath = path.join(fixtures, 'module', 'crash.js')
|
||||||
|
childProcess.fork(crashPath, [port, version, crashesDir], {silent: true})
|
||||||
|
},
|
||||||
|
processType: 'browser',
|
||||||
|
done: done
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -155,3 +139,47 @@ const waitForCrashReport = () => {
|
||||||
checkForReport()
|
checkForReport()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const startServer = ({callback, processType, done}) => {
|
||||||
|
var called = false
|
||||||
|
var server = http.createServer((req, res) => {
|
||||||
|
server.close()
|
||||||
|
var form = new multiparty.Form()
|
||||||
|
form.parse(req, (error, fields) => {
|
||||||
|
if (error) throw error
|
||||||
|
if (called) return
|
||||||
|
called = true
|
||||||
|
assert.equal(fields.prod, 'Electron')
|
||||||
|
assert.equal(fields.ver, process.versions.electron)
|
||||||
|
assert.equal(fields.process_type, processType)
|
||||||
|
assert.equal(fields.platform, process.platform)
|
||||||
|
assert.equal(fields.extra1, 'extra1')
|
||||||
|
assert.equal(fields.extra2, 'extra2')
|
||||||
|
assert.equal(fields._productName, 'Zombies')
|
||||||
|
assert.equal(fields._companyName, 'Umbrella Corporation')
|
||||||
|
assert.equal(fields._version, app.getVersion())
|
||||||
|
|
||||||
|
const reportId = 'abc-123-def-456-abc-789-abc-123-abcd'
|
||||||
|
res.end(reportId, () => {
|
||||||
|
waitForCrashReport().then(() => {
|
||||||
|
assert.equal(crashReporter.getLastCrashReport().id, reportId)
|
||||||
|
assert.notEqual(crashReporter.getUploadedReports().length, 0)
|
||||||
|
assert.equal(crashReporter.getUploadedReports()[0].id, reportId)
|
||||||
|
done()
|
||||||
|
}, done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
let {port} = remote.process
|
||||||
|
server.listen(port, '127.0.0.1', () => {
|
||||||
|
port = server.address().port
|
||||||
|
remote.process.port = port
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
crashReporter.start({
|
||||||
|
companyName: 'Umbrella Corporation',
|
||||||
|
submitURL: 'http://127.0.0.1:' + port
|
||||||
|
})
|
||||||
|
}
|
||||||
|
callback(port)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
13
spec/fixtures/module/crash.js
vendored
Normal file
13
spec/fixtures/module/crash.js
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
process.crashReporter.start({
|
||||||
|
productName: 'Zombies',
|
||||||
|
companyName: 'Umbrella Corporation',
|
||||||
|
crashesDirectory: process.argv[4],
|
||||||
|
submitURL: `http://127.0.0.1:${process.argv[2]}`,
|
||||||
|
extra: {
|
||||||
|
extra1: 'extra1',
|
||||||
|
extra2: 'extra2',
|
||||||
|
_version: process.argv[3]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
process.nextTick(() => process.crash())
|
Loading…
Add table
Add a link
Reference in a new issue