asdjfiasd
asdjfiasd

Reputation: 1730

libv8: c++ program sometimes segfaults when running JS code in SIGINT handler (after Ctrl+C)

I have C++ program that runs in terminal and uses libv8. I wanted to catch Ctrl+C and do some checks and cleanups before exit. To catch Ctrl+C I use signal(SIGINT, intHandler), it works but occasionally it segfaults. Here is minimal demo (just hold Ctrl+C and after about 100 catches it segfaults, more complex programs segfaults more often, this is simple demo so it crashes less often but it still does crash).

#include <stdio.h>
#include <signal.h>
#include <node/v8.h>
#include <v8/libplatform/libplatform.h>

const char* ToCString(const v8::String::Utf8Value& value) {//, char * place
    return *value ? *value : "<string conversion failed>";
}

void echo(const v8::FunctionCallbackInfo < v8::Value > &args) {
    // Print first argument
    v8::HandleScope handle_scope(args.GetIsolate());
    v8::String::Utf8Value str(args.GetIsolate(), args[0]);
    const char *cstr = ToCString(str);
    printf("%s\n", cstr);
    fflush(stdout);
};

v8::Isolate* globalIsolate;
v8::Local<v8::Context> globalContext;

void intHandler(int dummy) {
    // compile script
    v8::TryCatch try_catch(globalIsolate);
    v8::Local<v8::String> source = v8::String::NewFromUtf8(globalIsolate, "echo('ctrl+c was pressed')");
    v8::Local<v8::Script> script;
    if (!v8::Script::Compile(globalContext, source).ToLocal(&script)) {
        fprintf(stderr, "error 1: compiled failed!\n");
    }
    // Run the script to get the result.
    v8::Local<v8::Value> result;
    if (!script->Run(globalContext).ToLocal(&result)) {
        fprintf(stderr, "error 2: run failed!\n");
    }
}

int main(int argc, char* argv[]) {
    signal(SIGINT, intHandler);
    // Initialize V8
    v8::V8::InitializeICUDefaultLocation(argv[0]);
    v8::V8::InitializeExternalStartupData(argv[0]);
    std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
    v8::V8::InitializePlatform(platform.get());
    v8::V8::Initialize();
    v8::Isolate::CreateParams create_params;
    create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
    v8::Isolate* isolate = v8::Isolate::New(create_params);
    v8::Isolate::Scope isolate_scope(isolate);
    globalIsolate = isolate;
    v8::HandleScope handle_scope(isolate);
    v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
    global->Set(isolate, "echo", v8::FunctionTemplate::New(isolate, echo));
    v8::Local<v8::Context> context = v8::Context::New(isolate, NULL, global);
    v8::Context::Scope context_scope(context);
    globalContext = context;
    // compile script
    v8::TryCatch try_catch(isolate);
    v8::Local<v8::String> source = v8::String::NewFromUtf8(isolate, "var x = 1; while (true) { x++; if (x % 10000000 === 0) echo(x); };");
    v8::Local<v8::Script> script;
    if (!v8::Script::Compile(context, source).ToLocal(&script)) {
        fprintf(stderr, "error 3: compiled failed!\n");
        return 1;
    }
    // Run the script to get the result.
    v8::Local<v8::Value> result;
    if (!script->Run(context).ToLocal(&result)) {
        fprintf(stderr, "error 4: run failed!\n");
        return 2;
    }
    // Dispose the isolate and tear down V8.
    //isolate->Dispose();
    //v8::V8::Dispose();
    //v8::V8::DisposePlatform();
    //delete create_params.array_buffer_allocator;
    printf("Program finished\n");
    return 0;
}

I compile with:

g++ -o demo demo.cpp -lv8 -lpthread -lssl -lcrypto -Wall -ggdb -I/usr/include/v8

Run the program and keep pressing ctrl+c until it segfaults.

I assume I need to run JS code differently when I am inside that sigint handler but I don't know how. Thanks.

Notes:


however demo.cpp:27 is this line inside the intHandler:

    if (!v8::Script::Compile(globalContext, source).ToLocal(&script)) {

Upvotes: 0

Views: 73

Answers (1)

jmrk
jmrk

Reputation: 40691

(V8 developer here.)

I am, frankly, surprised that this works at all, even sometimes. It's definitely not a supported use case: V8 wasn't designed for this, and isn't tested for this, and there is no safe or proper way to do this. The signal could arrive at an arbitrary point in time, hence interrupting execution in an arbitrary state, and no part of V8 (in particular: not the stack) is built to support this.

In short: don't run JS code in signal handlers. Don't even call v8::Script::Compile. Don't do anything that allocates on the managed heap, such as v8::String::NewFromUtf8.
(Or do, and enjoy the crashes -- as the meme goes, I'm just a recommendation, not a cop.)

What you can do instead, especially for handling Ctrl+C, is use the v8::Isolate::TerminateExecution() feature. Internally it'll throw a special kind of exception that will unwind JS execution to the nearest script->Run(...) in your code, i.e. you should get to the line fprintf(stderr, "error 4: run failed!\n");. You can then run whatever checks you want to run and terminate the process gracefully.

Upvotes: 1

Related Questions