Resuming use of an Isolate after TerminateExecution

221 views
Skip to first unread message

Chris Dumoulin

unread,
Jan 24, 2019, 10:29:59 AM1/24/19
to v8-users
I'm trying to understand if an how an Isolate, and the Contexts within the Isolate, can be used after Isolate::TerminateExecution is called.
In v8.h, the documentation for Isolate::CancelTerminateExecution says:

Resume execution capability in the given isolate, whose execution
was previously forcefully terminated using TerminateExecution().
When execution is forcefully terminated using TerminateExecution(),
the isolate can not resume execution until all JavaScript frames
have propagated the uncatchable exception which is generated.  This
method allows the program embedding the engine to handle the
termination event and resume execution capability, even if
JavaScript frames remain on the stack.
This method can be used by any thread even if that thread has not
acquired the V8 lock with a Locker object.

It's not clear to me what this means for the Contexts that exist within the Isolate at the time TerminateExecution was called.
Does the terminate exception get raised in all Contexts within the Isolate?
Does the terminate exception only get raised in the current active Context?
After calling CancelTerminateExecution, can the existing Contexts still be used?

Thanks,
Chris

Jianyong Chen

unread,
Oct 24, 2022, 9:41:54 PM10/24/22
to v8-users
I also have these questions and if any v8 experts(or people with
replated knowledge/experience) see this post please give us some
information about them.

We are embedding v8 into our HTTP server and creating one isolate
per thread, which may run multiple JavaScript scripts concurrently
(in separate contexts).

In case scripts run too long, we use a watchdog thread to kill the
worker thread, and inside the installed signal handler, we call
isolate->TerminateExecution to stop the script. Since the isolate
is running multiple scripts, we just want to kill the one which
really timed out but preserve others(CancelTerminateExecution?).

Is that possible to achieve? If so, what should I pay attention to?

Any advice will be appreciated!

--
Jianyong Chen

theratliter theratliter

unread,
Oct 25, 2022, 2:03:41 AM10/25/22
to v8-users
Interesting! What i know is we just can run context one by one in an isolate per thread. For example
```
const vm = require('vm');
try {
   // Node.js call TerminateExecution after 5 ms, then call CancelTerminateExecution and throw a timeout error
    vm.runInNewContext('while(1) {}', {}, { timeout: 5 });
} catch(e) {
    console.log(e.message);
}
console.log(1);
vm.runInNewContext(''", {});
console.log(2);
```
Maybe you can read the source code of `node_contextify.cc` and `vm.js` in Node.js. Hope it will be helpful to you.

Ben Noordhuis

unread,
Oct 25, 2022, 6:58:48 AM10/25/22
to v8-u...@googlegroups.com
On Tue, Oct 25, 2022 at 3:41 AM Jianyong Chen <balu...@gmail.com> wrote:
>
> I also have these questions and if any v8 experts(or people with
> replated knowledge/experience) see this post please give us some
> information about them.
>
> We are embedding v8 into our HTTP server and creating one isolate
> per thread, which may run multiple JavaScript scripts concurrently
> (in separate contexts).
>
> In case scripts run too long, we use a watchdog thread to kill the
> worker thread, and inside the installed signal handler, we call
> isolate->TerminateExecution to stop the script. Since the isolate
> is running multiple scripts, we just want to kill the one which
> really timed out but preserve others(CancelTerminateExecution?).
>
> Is that possible to achieve? If so, what should I pay attention to?
>
> Any advice will be appreciated!

Yes, that can be made to work. V8 unwinds the JS call stack until C++
code calls isolate->CancelTerminateExecution().

Assuming your execution contexts don't interleave (context A can't
call functions from context B), then all you have to do is place
CancelTerminateExecution calls in the right spots.

If they do interleave, well, you're in for a rough time. No real way
to make that work.

Jianyong Chen

unread,
Oct 25, 2022, 11:26:56 PM10/25/22
to v8-users
> Interesting! What i know is we just can run context one by one in an isolate per thread. For example

Yes, it's pretty exciting. We have already implemented such a
mechanism for Lua and it works perfectly with the minimum overhead
, so we want to apply it to JavaScript.

As to the vm module, I'm wondering whether the contexts will be
switched back and forth if I call `vm.runInNewContext` multiple
times, where I write some async code(the timeout argument should
only restrict the on-cpu time, including promise callbacks, I guess).


> Maybe you can read the source code of `node_contextify.cc` and `vm.js` in Node.js. Hope it will be helpful to you.

I haven't used the vm module before so those are my guess.
But anyway, I'll read the code for details. Thanks!

Jianyong Chen

unread,
Oct 25, 2022, 11:29:17 PM10/25/22
to v8-users
Thanks, Ben.


> Assuming your execution contexts don't interleave (context A can't
> call functions from context B), then all you have to do is place
> CancelTerminateExecution calls in the right spots.

Yes, we do not have contexts interleaving(but context switch due to
async calls).

As to the "right spots" could you please give me more details or
advice about it? Now I'm planning to `CancelTerminateExecution`
when the `v8::TryCatch` is `HasTerminated`, which is created before
entering js code, And mark the current execution environment as
terminated so that we can skip Resolve/Reject when async operations
are completed and thus ensure that no new microtask is generated
or ran(now we use the auto policy for microtask).

Besides the right spots, I'm also concerned that how the
per-isolate(shared by all contexts) data are processed when I call
'TerminateExecution'. Such as the microtask queue, will the queue be
cleared up by v8 automatically or I should do it manually?

Ben Noordhuis

unread,
Oct 27, 2022, 3:13:52 AM10/27/22
to v8-u...@googlegroups.com
On Wed, Oct 26, 2022 at 5:29 AM Jianyong Chen <balu...@gmail.com> wrote:
>
> Thanks, Ben.
>
> > Assuming your execution contexts don't interleave (context A can't
> > call functions from context B), then all you have to do is place
> > CancelTerminateExecution calls in the right spots.
>
> Yes, we do not have contexts interleaving(but context switch due to
> async calls).
>
> As to the "right spots" could you please give me more details or
> advice about it? Now I'm planning to `CancelTerminateExecution`
> when the `v8::TryCatch` is `HasTerminated`, which is created before

Yes, that should work. "Right spots" means C++ -> JS calls (ex.
v8::Function::Call) and JS->C++ calls (ex. v8::FunctionTemplate
callbacks.)

> entering js code, And mark the current execution environment as
> terminated so that we can skip Resolve/Reject when async operations
> are completed and thus ensure that no new microtask is generated
> or ran(now we use the auto policy for microtask).
>
> Besides the right spots, I'm also concerned that how the
> per-isolate(shared by all contexts) data are processed when I call
> 'TerminateExecution'. Such as the microtask queue, will the queue be
> cleared up by v8 automatically or I should do it manually?

The microtask queue is a good point. V8 clears it out when microtasks
are about to run but execution is terminating.

However, it clears microtasks for _all_ contexts. Presumably that's
not what you want.

You can run microtasks manually and call CancelTerminateExecution
beforehand but then microtasks from the terminated context still run.

I don't think you can really avoid that unless you control all places
where promises are created, e.g., because you use
`v8::Promise::Resolver` for everything.

Jianyong Chen

unread,
Oct 29, 2022, 1:08:11 AM10/29/22
to v8-users
> However, it clears microtasks for _all_ contexts. Presumably that's
> not what you want.

Yes, but I now guess it won't happen that microtasks of different
contexts exist at the same time. We will keep executing js code
of context-1 until async API is called, and then we switch to
context-2, before which all the microtasks of context-1 should be
executed by v8 since we use the auto policy.

The assumption is based on my superficial understanding of v8 and
it's microtask policy so please correct me if anything is wrong.


> You can run microtasks manually and call CancelTerminateExecution
> beforehand but then microtasks from the terminated context still run.

That's not what I want either, but unfortunately, I didn't find any
v8 utility to help clean up the microtask queue manually.
Reply all
Reply to author
Forward
0 new messages