From 221855c6fe22abc5c70587482bae3e8c4809eb39 Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Tue, 21 Aug 2018 16:12:42 +0100 Subject: igt/shell: Flesh out the os.fork() interface --- shell/igt-shell.cc | 1 + shell/include/os.h | 1 + shell/lib/os-fork.cc | 275 +++++++++++++++++++++++++++++++++++++++++++++++ shell/lib/os.cc | 1 + shell/meson.build | 1 + shell/samples/os.fork.js | 30 ++++++ 6 files changed, 309 insertions(+) create mode 100644 shell/lib/os-fork.cc create mode 100644 shell/samples/os.fork.js diff --git a/shell/igt-shell.cc b/shell/igt-shell.cc index 0e542e4e..3bcc6fb1 100644 --- a/shell/igt-shell.cc +++ b/shell/igt-shell.cc @@ -68,6 +68,7 @@ static bool ExecuteString(Isolate* iso, Local source, Local name, bool print_result, bool report_exceptions) { + MicrotasksScope tasks(iso, MicrotasksScope::kRunMicrotasks); HandleScope handle_scope(iso); TryCatch try_catch(iso); diff --git a/shell/include/os.h b/shell/include/os.h index 6e1f01de..18c7ec44 100644 --- a/shell/include/os.h +++ b/shell/include/os.h @@ -4,6 +4,7 @@ #include "v8.h" extern void os_file_ctor(v8::Isolate *iso, v8::Handle os); +extern void os_fork_ctor(v8::Isolate *iso, v8::Handle os); extern void os_throw_errno(v8::Isolate *iso, int err); diff --git a/shell/lib/os-fork.cc b/shell/lib/os-fork.cc new file mode 100644 index 00000000..992da5ce --- /dev/null +++ b/shell/lib/os-fork.cc @@ -0,0 +1,275 @@ +#include +#include +#include +#include + +#include "igt-shell.h" +#include "os.h" +#include "v8-helpers.h" + +using namespace v8; + +struct job { + Persistent weakref; + unsigned int count; + int result; + pid_t pids[]; +}; + +static int __job_wait(Isolate *iso, struct job *j, unsigned int flags) +{ + unsigned int alive = 0; + int result = j->result; + + for (unsigned int i = 0; i < j->count; i++) { + int status, ret; + + while ((ret = waitpid(j->pids[i], &status, flags)) < 0) { + int err = errno; + + if (err == SIGINT) + continue; + + for (unsigned int x = 0; x < j->count; x++) + kill(j->pids[x], SIGTERM); + + if (iso) + os_throw_errno(iso, err); + } + + if (ret) { + if (WIFSIGNALED(status) && !WIFSIGNALED(result)) + result = status; /* take exception to signals, esp. SIGBUS/SIGSEGV */ + if (status && !result) + result = status; + } else { + j->pids[alive++] = j->pids[i]; + } + } + + j->count = alive; + j->result = result; + return result; +} + +/* XXX lazy gc is lazy */ +static void job_dtor(const WeakCallbackInfo& weak) +{ + struct job *j = weak.GetParameter(); + + j->weakref.Reset(); + + if (j->count) { + auto iso = weak.GetIsolate(); + + if (__job_wait(iso, j, 0)) + iso->ThrowException(Exception::Error(AsString(iso, "unchecked job error").ToLocalChecked())); + } + + free(j); +} + +static void job_cancel(struct job *j, unsigned int count) +{ + for (unsigned int i = 0; i < count; i++) + kill(j->pids[i], SIGTERM); + + for (unsigned int i = 0; i < count; i++) + waitpid(j->pids[i], NULL, 0); + + free(j); +} + +static void job_wait(const FunctionCallbackInfo& args) +{ + struct job *j = + (struct job *)args.This()->GetAlignedPointerFromInternalField(0); + + args.GetReturnValue().Set(__job_wait(args.GetIsolate(), j, 0)); +} + +static void job_kill(const FunctionCallbackInfo& args) +{ + auto ctx = args.GetIsolate()->GetCurrentContext(); + int sig = args[0]->Int32Value(ctx).FromMaybe(SIGINT); + + struct job *j = + (struct job *)args.This()->GetAlignedPointerFromInternalField(0); + + for (unsigned int i = 0; i < j->count; i++) + kill(j->pids[i], sig); +} + +static struct job *job_create(unsigned int count) +{ + struct job *j = (struct job *)malloc(sizeof(*j) + count * sizeof(pid_t)); + if (!j) + return NULL; + + memset(static_cast(j), 0, sizeof(*j)); + j->count = count; + return j; +} + +static void job_result(Local prop, + const PropertyCallbackInfo& info) +{ + struct job *j = (struct job *)info.This()->GetAlignedPointerFromInternalField(0); + auto iso = info.GetIsolate(); + + __job_wait(iso, j, WNOHANG); + if (j->count == 0) + info.GetReturnValue().Set(Uint32::New(iso, j->result)); +} + +static void attach_job(Isolate *iso, ReturnValue rv, struct job *j) +{ + HandleScope scope(iso); + + Local tmpl = ObjectTemplate::New(iso); + tmpl->SetInternalFieldCount(1); + tmpl->Set(AsString(iso, "wait").ToLocalChecked(), + FunctionTemplate::New(iso, job_wait)); + tmpl->Set(AsString(iso, "kill").ToLocalChecked(), + FunctionTemplate::New(iso, job_kill)); + tmpl->SetAccessor(AsString(iso, "result").ToLocalChecked(), job_result); + + auto ctx = iso->GetCurrentContext(); + Local obj = tmpl->NewInstance(ctx).ToLocalChecked(); + obj->SetAlignedPointerInInternalField(0, j); + + j->weakref.Reset(iso, obj); + j->weakref.SetWeak(j, job_dtor, WeakCallbackType::kParameter); + + rv.Set(obj); +} + +static void fork_caller(const FunctionCallbackInfo& args) +{ + auto iso = args.GetIsolate(); + + struct job *j = job_create(1); + if (!j) { + os_throw_errno(iso, ENOMEM); + return; + } + + j->pids[0] = fork(); + if (j->pids[0] < 0) { + os_throw_errno(iso, errno); + return; + } + + if (!j->pids[0]) /* child */ + return; + + attach_job(iso, args.GetReturnValue(), j); +} + +__attribute__((noreturn)) +static void do_child(Isolate *iso, Local v_fn, Local arg) +{ + HandleScope scope(iso); + TryCatch try_catch(iso); + + Local ctx = iso->GetCurrentContext(); + Local fn = Local::Cast(v_fn->ToObject()); + Local result = fn->Call(ctx, fn, 1, &arg).ToLocalChecked(); + + if (try_catch.HasCaught()) + abort(); + + exit(result->Int32Value(ctx).FromMaybe(0)); +} + +static void fork_once(const FunctionCallbackInfo& args) +{ + auto iso = args.GetIsolate(); + + struct job *j = job_create(1); + if (!j) { + os_throw_errno(iso, ENOMEM); + return; + } + + j->pids[0] = fork(); + if (j->pids[0] < 0) { + os_throw_errno(iso, errno); + return; + } + + if (!j->pids[0]) + do_child(iso, args[1], args[0]); + + attach_job(iso, args.GetReturnValue(), j); +} + +static void fork_array(const FunctionCallbackInfo& args) +{ + auto iso = args.GetIsolate(); + + Local a = Local::Cast(args[0]->ToObject()); + + struct job *j = job_create(a->Length()); + if (!j) { + os_throw_errno(iso, ENOMEM); + return; + } + + for (unsigned int i = 0; i < a->Length(); i++) { + j->pids[i] = fork(); + if (j->pids[i] < 0) { + job_cancel(j, i); + os_throw_errno(iso, errno); + return; + } + + if (!j->pids[i]) { + Local v = a->Get(iso->GetCurrentContext(), i).ToLocalChecked(); + do_child(iso, args[1], v); + } + } + + attach_job(iso, args.GetReturnValue(), j); +} + +static void os_fork(const FunctionCallbackInfo& args) +{ + auto iso = args.GetIsolate(); + + if (args.Length() == 0) { + fork_caller(args); + return; + } + + if (args.Length() != 2) { + iso->ThrowException(Exception::SyntaxError(AsString(iso, "bad args").ToLocalChecked())); + return; + } + + if (!args[1]->IsFunction()) { + iso->ThrowException(Exception::TypeError(AsString(iso, "bad function arg").ToLocalChecked())); + return; + } + + if (args[0]->IsArray()) + fork_array(args); + else + fork_once(args); +} + +static void os_exit(const FunctionCallbackInfo& args) +{ + auto ctx = args.GetIsolate()->GetCurrentContext(); + exit(args[0]->Int32Value(ctx).FromMaybe(0)); +} + +void os_fork_ctor(Isolate *iso, Handle os) +{ + HandleScope handle_scope(iso); + + os->Set(AsString(iso, "fork").ToLocalChecked(), + FunctionTemplate::New(iso, os_fork)); + os->Set(AsString(iso, "exit").ToLocalChecked(), + FunctionTemplate::New(iso, os_exit)); +} diff --git a/shell/lib/os.cc b/shell/lib/os.cc index 64fe64eb..c23670fd 100644 --- a/shell/lib/os.cc +++ b/shell/lib/os.cc @@ -93,6 +93,7 @@ static void os_builtin_ctor(Isolate *iso, Handle igt) tmpl->SetAccessor(AsString(iso, "now").ToLocalChecked(), os_now); os_file_ctor(iso, tmpl); + os_fork_ctor(iso, tmpl); igt->Set(iso, "os", tmpl); } diff --git a/shell/meson.build b/shell/meson.build index a361172b..8b76ffac 100644 --- a/shell/meson.build +++ b/shell/meson.build @@ -4,6 +4,7 @@ executable('igt', [ 'lib/igt-toy.cc', 'lib/os.cc', 'lib/os-file.cc', + 'lib/os-fork.cc', 'lib/v8-helpers.cc', ], objects: 'v8/libv8_monolith.a', diff --git a/shell/samples/os.fork.js b/shell/samples/os.fork.js new file mode 100644 index 00000000..debdebcc --- /dev/null +++ b/shell/samples/os.fork.js @@ -0,0 +1,30 @@ +igt.os.fork('hello', print).wait(); + +igt.os.fork([ {delay: 1, msg:'goodbye' }, {delay:0, msg:'world'} ], + function(x) { igt.os.sleep(x.delay); print(x.msg) }).wait(); + +var job = igt.os.fork('', function(x) { igt.os.exit(1); igt.os.sleep(999); }); +print('Exitcode = ' + job.wait()); + +job = igt.os.fork(); +if (job) { + print("Parent shall wait for its child!"); + job.wait(); +} else { + print("The child wants to play!"); + for (var x = 0; x < 10; x++) { + print("Play " + x); + igt.os.sleep(0.1); + } + print("Tired now!"); + igt.os.exit(0); +} +print("Only parents allowed after children sleep, exitcode:", job.result); + +job = igt.os.fork(); +if (job) { + print("No Mr Bond, I expect you to", job.wait()); +} else { + badwolf(); +} +print("Onwards, all by myself."); -- cgit v1.2.3