Concurrently locking isolate + entering exiting persistent context

43 views
Skip to first unread message

jer...@fly.io

unread,
May 11, 2018, 1:36:55 PM5/11/18
to v8-users
I've been wondering if it's possible to concurrently use v8::Locker.

I'm binding to v8 from a single-threaded language that uses fibers for concurrency (Crystal.) Since it's a single thread, v8::Locker is always instantly available.

This seems fine, the bigger issue if with Context::Scope.

I'm concurrently entering and exiting the same context from multiple fibers and I keep stumbling upon:

#
# Fatal error in v8::Context::Exit()
# Cannot exit non-entered context
#

Which came to make sense to me. I'm passing around a pointer to a Persistent<Context>. In the function I concurrently access, I'm getting the Local from it (with the isolate) and creating a Context::Scope. The same Local<Context> might be exited more than once. I expect Enter is a noop if the Context has already been entered.

My fix was to use a fiber-safe mutex to only allow one isolate locking and context scoping at any given time.

Is this normal? Any way around it?

Ben Noordhuis

unread,
May 11, 2018, 2:45:06 PM5/11/18
to v8-users
On Fri, May 11, 2018 at 7:36 PM, <jer...@fly.io> wrote:
> I've been wondering if it's possible to concurrently use v8::Locker.
>
> I'm binding to v8 from a single-threaded language that uses fibers for
> concurrency (Crystal.) Since it's a single thread, v8::Locker is always
> instantly available.
>
> This seems fine, the bigger issue if with Context::Scope.
>
> I'm concurrently entering and exiting the same context from multiple fibers
> and I keep stumbling upon:
>
> #
> # Fatal error in v8::Context::Exit()
> # Cannot exit non-entered context
> #
>
> Which came to make sense to me. I'm passing around a pointer to a
> Persistent<Context>. In the function I concurrently access, I'm getting the
> Local from it (with the isolate) and creating a Context::Scope. The same
> Local<Context> might be exited more than once. I expect Enter is a noop if
> the Context has already been entered.

It isn't. Enter() and Exit() calls need to match up.

> My fix was to use a fiber-safe mutex to only allow one isolate locking and
> context scoping at any given time.
>
> Is this normal? Any way around it?

You may want to take a look at node-fibers. It uses a thread-local
storage hack to trick V8 into supporting fibers.

https://github.com/laverdet/node-fibers/blob/4786aef736ab8285d29e9476e3615abbd3935d37/src/coroutine.cc#L76-L123

jer...@fly.io

unread,
May 11, 2018, 4:05:06 PM5/11/18
to v8-users


On Friday, May 11, 2018 at 2:45:06 PM UTC-4, Ben Noordhuis wrote:
On Fri, May 11, 2018 at 7:36 PM,  <jer...@fly.io> wrote:
> I've been wondering if it's possible to concurrently use v8::Locker.
>
> I'm binding to v8 from a single-threaded language that uses fibers for
> concurrency (Crystal.) Since it's a single thread, v8::Locker is always
> instantly available.
>
> This seems fine, the bigger issue if with Context::Scope.
>
> I'm concurrently entering and exiting the same context from multiple fibers
> and I keep stumbling upon:
>
> #
> # Fatal error in v8::Context::Exit()
> # Cannot exit non-entered context
> #
>
> Which came to make sense to me. I'm passing around a pointer to a
> Persistent<Context>. In the function I concurrently access, I'm getting the
> Local from it (with the isolate) and creating a Context::Scope. The same
> Local<Context> might be exited more than once. I expect Enter is a noop if
> the Context has already been entered.

It isn't.  Enter() and Exit() calls need to match up.

Yea, that makes sense. They do match up, but the function I use to get the context handle may be called more than once at the same time. I mean, the same context may be entered multiple times concurrently and exited at different times. As soon as it's exited once by one of the concurrent tasks, it will throw that error I pasted if it's exited again.

void __crystal_ctx_scoped(char *id);

void v8_with_ctx_scope(Isolate* iso, Context* ctxptr, char* id) {
  v8::HandleScope handle_scope(iso);
  v8::Local<v8::Context> ctx = ctxptr->Get(iso);
  {
    v8::Context::Scope context_scope(ctx);
    __crystal_ctx_scoped(id);
  }


The __crystal_ctx_scoped is a callback exposed from Crystal to C. It dispatches to the right context via the provided id.

I added the extra curly braces as a test, but I don't think they're necessary.


> My fix was to use a fiber-safe mutex to only allow one isolate locking and
> context scoping at any given time.
>
> Is this normal? Any way around it?

You may want to take a look at node-fibers.  It uses a thread-local
storage hack to trick V8 into supporting fibers.

https://github.com/laverdet/node-fibers/blob/4786aef736ab8285d29e9476e3615abbd3935d37/src/coroutine.cc#L76-L123

I've looked at it a bit. I've also been chatting with its author.

I don't necessarily want fibers to work from within v8 (like node-fibers), I mostly want to be able to call v8 from any fiber concurrently. But maybe the requirements for that are similar.

I'm already assigning the stack limit to the stack_bottom (or top depending on who you're talking to) + padding to the isolate on after each lock. Apparently that's not enough and it somehow started crashing a bit. Discussing with the node-fibers authors on maybe fixing that.

Ben Noordhuis

unread,
May 11, 2018, 4:36:11 PM5/11/18
to v8-users
On Fri, May 11, 2018 at 10:05 PM, <jer...@fly.io> wrote:
> Yea, that makes sense. They do match up, but the function I use to get the
> context handle may be called more than once at the same time. I mean, the
> same context may be entered multiple times concurrently and exited at
> different times. As soon as it's exited once by one of the concurrent tasks,
> it will throw that error I pasted if it's exited again.
>
> void __crystal_ctx_scoped(char *id);
>
> void v8_with_ctx_scope(Isolate* iso, Context* ctxptr, char* id) {
> v8::HandleScope handle_scope(iso);
> v8::Local<v8::Context> ctx = ctxptr->Get(iso);
> {
> v8::Context::Scope context_scope(ctx);
> __crystal_ctx_scoped(id);
> }
> }
>
> The __crystal_ctx_scoped is a callback exposed from Crystal to C. It
> dispatches to the right context via the provided id.

You mean __crystal_ctx_scoped() directly or indirectly calls
v8_with_ctx_scope() again? Yes, that's going to be problematic.

At what point or points do you call into V8? You could maintain a
stack of contexts and call Enter() and Exit() manually but that only
works when there are no V8 stack frames (either JS or C++) on the call
stack.

Jérôme Gravel-Niquet

unread,
May 11, 2018, 7:20:09 PM5/11/18
to Ben Noordhuis, v8-u...@googlegroups.com


On May 11, 2018 at 4:36:12 PM, Ben Noordhuis (in...@bnoordhuis.nl) wrote:

On Fri, May 11, 2018 at 10:05 PM, <jer...@fly.io> wrote: 
> Yea, that makes sense. They do match up, but the function I use to get the 
> context handle may be called more than once at the same time. I mean, the 
> same context may be entered multiple times concurrently and exited at 
> different times. As soon as it's exited once by one of the concurrent tasks, 
> it will throw that error I pasted if it's exited again. 
> 
> void __crystal_ctx_scoped(char *id); 
> 
> void v8_with_ctx_scope(Isolate* iso, Context* ctxptr, char* id) { 
> v8::HandleScope handle_scope(iso); 
> v8::Local<v8::Context> ctx = ctxptr->Get(iso); 
> { 
> v8::Context::Scope context_scope(ctx); 
> __crystal_ctx_scoped(id); 
> } 
> } 
> 
> The __crystal_ctx_scoped is a callback exposed from Crystal to C. It 
> dispatches to the right context via the provided id. 

You mean __crystal_ctx_scoped() directly or indirectly calls 
v8_with_ctx_scope() again? Yes, that's going to be problematic. 

Another fiber might call v8_with_ctx_scope() again before the first call to it is done. With the same context. When I added a mutex, the problem seemed fixed. I just want to make sure this is limitation: you can't enter the same context more than one at the same time, or else exiting will exit from both "enters".



At what point or points do you call into V8? You could maintain a 
stack of contexts and call Enter() and Exit() manually but that only 
works when there are no V8 stack frames (either JS or C++) on the call 
stack. 

Hmm. It's called into when there's an HTTP request coming in or when there's a callback fired (like a setTimeout.)

Not entirely sure what kind of information you're looking for :) There's probably going to be frames on the call stack.




-- 
-- 
v8-users mailing list 
v8-u...@googlegroups.com 
http://groups.google.com/group/v8-users 
--- 
You received this message because you are subscribed to a topic in the Google Groups "v8-users" group. 
To unsubscribe from this topic, visit https://groups.google.com/d/topic/v8-users/5EbYZlO5VTY/unsubscribe. 
To unsubscribe from this group and all its topics, send an email to v8-users+u...@googlegroups.com. 
For more options, visit https://groups.google.com/d/optout. 

Ben Noordhuis

unread,
May 12, 2018, 7:22:01 AM5/12/18
to Jérôme Gravel-Niquet, v8-users
On Sat, May 12, 2018 at 1:20 AM, Jérôme Gravel-Niquet <jer...@fly.io> wrote:
> Another fiber might call v8_with_ctx_scope() again before the first call to
> it is done. With the same context. When I added a mutex, the problem seemed
> fixed. I just want to make sure this is limitation: you can't enter the same
> context more than one at the same time, or else exiting will exit from both
> "enters".

Yes, another fiber calling v8_with_ctx_scope() is what I mean.

Entering and exiting the same context more than once should be okay.
When Crystal switches fibers, I assume it switches stack and registers
but does it also switch thread-local storage? That might explain why
you're hitting that check, V8 stores state in thread-local storage.

> Hmm. It's called into when there's an HTTP request coming in or when there's
> a callback fired (like a setTimeout.)
>
> Not entirely sure what kind of information you're looking for :) There's
> probably going to be frames on the call stack.

Okay, that might also be a factor. I don't think you're supposed to
exit the context when there are stack frames under you that belong to
that context. I.e.:

enter context -> call JS -> JS calls C++ -> exit context -- not good

Is there code I can look at or try out?
Reply all
Reply to author
Forward
0 new messages