Skip to content

Commit c1de6d2

Browse files
committed
core: implement runtime flag to trace sync io
Use the --trace-sync-io flag to print a stack trace whenever a sync method is used after the first tick, excluding during the process exit event. (e.g. fs.readFileSync()) It does not track if the warning has occurred at a specific location in the past and so will print the warning every time. Reason for not printing during the first tick of the appication is so all necessary resources can be required. Also by excluding synchronous calls during exit is necessary in case any data needs to be logged out by the application before it shuts down. Fixes: #1674 PR-URL: #1707 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Fedor Indutny <fedor@indutny.com> Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com> Reviewed-By: Petka Antonov <petka_antonov@hotmail.com>
1 parent 0662747 commit c1de6d2

10 files changed

+137
-1
lines changed

node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
'sources': [
100100
'src/debug-agent.cc',
101101
'src/async-wrap.cc',
102+
'src/env.cc',
102103
'src/fs_event_wrap.cc',
103104
'src/cares_wrap.cc',
104105
'src/handle_wrap.cc',

src/env-inl.h

+5
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ inline Environment::Environment(v8::Local<v8::Context> context,
176176
using_abort_on_uncaught_exc_(false),
177177
using_asyncwrap_(false),
178178
printed_error_(false),
179+
trace_sync_io_(false),
179180
debugger_agent_(this),
180181
context_(context->GetIsolate(), context) {
181182
// We'll be creating new objects so make sure we've entered the context.
@@ -325,6 +326,10 @@ inline void Environment::set_printed_error(bool value) {
325326
printed_error_ = value;
326327
}
327328

329+
inline void Environment::set_trace_sync_io(bool value) {
330+
trace_sync_io_ = value;
331+
}
332+
328333
inline Environment* Environment::from_cares_timer_handle(uv_timer_t* handle) {
329334
return ContainerOf(&Environment::cares_timer_handle_, handle);
330335
}

src/env.cc

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#include "env.h"
2+
#include "env-inl.h"
3+
#include "v8.h"
4+
#include <stdio.h>
5+
6+
namespace node {
7+
8+
using v8::HandleScope;
9+
using v8::Local;
10+
using v8::Message;
11+
using v8::StackFrame;
12+
using v8::StackTrace;
13+
14+
void Environment::PrintSyncTrace() const {
15+
if (!trace_sync_io_)
16+
return;
17+
18+
HandleScope handle_scope(isolate());
19+
Local<v8::StackTrace> stack =
20+
StackTrace::CurrentStackTrace(isolate(), 10, StackTrace::kDetailed);
21+
22+
fprintf(stderr, "WARNING: Detected use of sync API\n");
23+
24+
for (int i = 0; i < stack->GetFrameCount() - 1; i++) {
25+
Local<StackFrame> stack_frame = stack->GetFrame(i);
26+
node::Utf8Value fn_name_s(isolate(), stack_frame->GetFunctionName());
27+
node::Utf8Value script_name(isolate(), stack_frame->GetScriptName());
28+
const int line_number = stack_frame->GetLineNumber();
29+
const int column = stack_frame->GetColumn();
30+
31+
if (stack_frame->IsEval()) {
32+
if (stack_frame->GetScriptId() == Message::kNoScriptIdInfo) {
33+
fprintf(stderr, " at [eval]:%i:%i\n", line_number, column);
34+
} else {
35+
fprintf(stderr,
36+
" at [eval] (%s:%i:%i)\n",
37+
*script_name,
38+
line_number,
39+
column);
40+
}
41+
break;
42+
}
43+
44+
if (fn_name_s.length() == 0) {
45+
fprintf(stderr, " at %s:%i:%i\n", *script_name, line_number, column);
46+
} else {
47+
fprintf(stderr,
48+
" at %s (%s:%i:%i)\n",
49+
*fn_name_s,
50+
*script_name,
51+
line_number,
52+
column);
53+
}
54+
}
55+
fflush(stderr);
56+
}
57+
58+
} // namespace node

src/env.h

+4
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,9 @@ class Environment {
420420
inline bool printed_error() const;
421421
inline void set_printed_error(bool value);
422422

423+
void PrintSyncTrace() const;
424+
inline void set_trace_sync_io(bool value);
425+
423426
inline void ThrowError(const char* errmsg);
424427
inline void ThrowTypeError(const char* errmsg);
425428
inline void ThrowRangeError(const char* errmsg);
@@ -506,6 +509,7 @@ class Environment {
506509
bool using_abort_on_uncaught_exc_;
507510
bool using_asyncwrap_;
508511
bool printed_error_;
512+
bool trace_sync_io_;
509513
debugger::Agent debugger_agent_;
510514

511515
HandleWrapQueue handle_wrap_queue_;

src/node.cc

+18
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ using v8::Promise;
102102
using v8::PromiseRejectMessage;
103103
using v8::PropertyCallbackInfo;
104104
using v8::SealHandleScope;
105+
using v8::StackFrame;
106+
using v8::StackTrace;
105107
using v8::String;
106108
using v8::TryCatch;
107109
using v8::Uint32;
@@ -114,6 +116,7 @@ static bool force_repl = false;
114116
static bool trace_deprecation = false;
115117
static bool throw_deprecation = false;
116118
static bool abort_on_uncaught_exception = false;
119+
static bool trace_sync_io = false;
117120
static const char* eval_string = nullptr;
118121
static unsigned int preload_module_count = 0;
119122
static const char** preload_modules = nullptr;
@@ -2834,6 +2837,13 @@ void SetupProcessObject(Environment* env,
28342837
READONLY_PROPERTY(process, "traceDeprecation", True(env->isolate()));
28352838
}
28362839

2840+
// --trace-sync-io
2841+
if (trace_sync_io) {
2842+
READONLY_PROPERTY(process, "traceSyncIO", True(env->isolate()));
2843+
// Don't env->set_trace_sync_io(true) because it will be enabled
2844+
// after LoadEnvironment() has run.
2845+
}
2846+
28372847
size_t exec_path_len = 2 * PATH_MAX;
28382848
char* exec_path = new char[exec_path_len];
28392849
Local<String> exec_path_value;
@@ -3060,6 +3070,8 @@ static void PrintHelp() {
30603070
" --throw-deprecation throw an exception anytime a deprecated "
30613071
"function is used\n"
30623072
" --trace-deprecation show stack traces on deprecations\n"
3073+
" --trace-sync-io show stack trace when use of sync IO\n"
3074+
" is detected after the first tick\n"
30633075
" --v8-options print v8 command line options\n"
30643076
#if defined(NODE_HAVE_I18N_SUPPORT)
30653077
" --icu-data-dir=dir set ICU data load path to dir\n"
@@ -3180,6 +3192,8 @@ static void ParseArgs(int* argc,
31803192
no_deprecation = true;
31813193
} else if (strcmp(arg, "--trace-deprecation") == 0) {
31823194
trace_deprecation = true;
3195+
} else if (strcmp(arg, "--trace-sync-io") == 0) {
3196+
trace_sync_io = true;
31833197
} else if (strcmp(arg, "--throw-deprecation") == 0) {
31843198
throw_deprecation = true;
31853199
} else if (strcmp(arg, "--abort-on-uncaught-exception") == 0 ||
@@ -3887,6 +3901,8 @@ static void StartNodeInstance(void* arg) {
38873901

38883902
LoadEnvironment(env);
38893903

3904+
env->set_trace_sync_io(trace_sync_io);
3905+
38903906
// Enable debugger
38913907
if (instance_data->use_debug_agent())
38923908
EnableDebug(env);
@@ -3911,6 +3927,8 @@ static void StartNodeInstance(void* arg) {
39113927
} while (more == true);
39123928
}
39133929

3930+
env->set_trace_sync_io(false);
3931+
39143932
int exit_code = EmitExit(env);
39153933
if (instance_data->is_main())
39163934
instance_data->set_exit_code(exit_code);

src/node_crypto.cc

+2
Original file line numberDiff line numberDiff line change
@@ -4630,6 +4630,7 @@ void PBKDF2(const FunctionCallbackInfo<Value>& args) {
46304630
EIO_PBKDF2,
46314631
EIO_PBKDF2After);
46324632
} else {
4633+
env->PrintSyncTrace();
46334634
Local<Value> argv[2];
46344635
EIO_PBKDF2(req);
46354636
EIO_PBKDF2After(req, argv);
@@ -4786,6 +4787,7 @@ void RandomBytes(const FunctionCallbackInfo<Value>& args) {
47864787
RandomBytesAfter);
47874788
args.GetReturnValue().Set(obj);
47884789
} else {
4790+
env->PrintSyncTrace();
47894791
Local<Value> argv[2];
47904792
RandomBytesWork(req->work_req());
47914793
RandomBytesCheck(req, argv);

src/node_file.cc

+1
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ struct fs_req_wrap {
280280

281281
#define SYNC_DEST_CALL(func, path, dest, ...) \
282282
fs_req_wrap req_wrap; \
283+
env->PrintSyncTrace(); \
283284
int err = uv_fs_ ## func(env->event_loop(), \
284285
&req_wrap.req, \
285286
__VA_ARGS__, \

src/node_zlib.cc

+1
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ class ZCtx : public AsyncWrap {
181181

182182
if (!async) {
183183
// sync version
184+
ctx->env()->PrintSyncTrace();
184185
Process(work_req);
185186
if (CheckError(ctx))
186187
AfterSync(ctx, args);

src/spawn_sync.cc

+3-1
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,9 @@ void SyncProcessRunner::Initialize(Handle<Object> target,
349349

350350

351351
void SyncProcessRunner::Spawn(const FunctionCallbackInfo<Value>& args) {
352-
SyncProcessRunner p(Environment::GetCurrent(args));
352+
Environment* env = Environment::GetCurrent(args);
353+
env->PrintSyncTrace();
354+
SyncProcessRunner p(env);
353355
Local<Value> result = p.Run(args[0]);
354356
args.GetReturnValue().Set(result);
355357
}

test/parallel/test-sync-io-option.js

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
const spawn = require('child_process').spawn;
5+
6+
7+
if (process.argv[2] === 'child') {
8+
setImmediate(function() {
9+
require('fs').readFileSync(__filename);
10+
process.exit();
11+
});
12+
13+
} else {
14+
(function runTest(flags) {
15+
var execArgv = [flags.pop()];
16+
var args = [__filename, 'child'];
17+
var child = spawn(process.execPath, execArgv.concat(args));
18+
var cntr = 0;
19+
20+
child.stdout.on('data', function(chunk) {
21+
throw new Error('UNREACHABLE');
22+
});
23+
24+
child.stderr.on('data', function(chunk) {
25+
// Prints twice for --trace-sync-io. First for the require() and second
26+
// for the fs operation.
27+
if (/^WARNING[\s\S]*fs\.readFileSync/.test(chunk.toString()))
28+
cntr++;
29+
});
30+
31+
child.on('exit', function() {
32+
if (execArgv[0] === '--trace-sync-io')
33+
assert.equal(cntr, 2);
34+
else if (execArgv[0] === ' ')
35+
assert.equal(cntr, 0);
36+
else
37+
throw new Error('UNREACHABLE');
38+
39+
if (flags.length > 0)
40+
setImmediate(runTest, flags);
41+
});
42+
}(['--trace-sync-io', ' ']));
43+
}
44+

0 commit comments

Comments
 (0)