Question about V8's Locker and HandleScope

1,000 views
Skip to first unread message

Reko Tiira

unread,
Apr 13, 2010, 8:12:10 AM4/13/10
to v8-users
Hey,

I want to embed V8 in my game engine, mainly because I prefer it's C++
API over that of SpiderMonkey's. However I'm having some thoughts
about thread-safety, since my engine would be executing each script in
its own thread.

I know that only a single thread can use V8 at a time, and that you
use the v8::Locker to lock access to V8 per-thread basis. This is all
good for me, because I can still unlock the thread if I have to wait
for some resource that is not related to V8. For sake of simplicity,
let's say I have to sleep for 2 seconds in a script, I could do this
by:

{
v8::Unlocker ul;
Sleep(2);
}

And while the thread sleeps, other scripts could still execute since
v8 is unlocked. However what I'm concerned about is how will
HandleScopes scope with this. Consider the following example:

{
v8::Locker l;
v8::HandleScope handle_scope;

{
v8::Unlocker ul;
Sleep(2);
}

v8::Local<v8::Value> str = v8::String::New("foobar");

// For sake of not having to write almost two identical functions,
// assume that ONLY thread 1 does this:
{
v8::Unlocker ul;
Sleep(2);
}
std::string s = String::AsciiValue ascii(str);

// And thread 2 would ONLY do this:
return;
}

Let's say that two threads run this same script. When the first thread
unlocks for the first time and goes to sleep, the 2nd thread will
create a new HandleScope, and thus all consecutive allocations are
done on the 2nd thread's handle scope. So when thread 2 goes to sleep,
and thread 1 wakes up, it'll create a new String using the HandleScope
that was created on the thread 2 while thread 1 was sleeping. Now
thread 1 will go to sleep again, and thread 2 will wake up. At this
point thread 2 simply returns, and since the HandleScope goes out of
scope, it'll free all the locals that it had allocated, _including_
the string created in thread 1 (since it was allocated using the wrong
HandleScope). After this the control returns back to the thread 1,
which tries to use the already de-allocated value, and probably crash
because of that.

Am I getting this right? If I am, is there any plausible way to get
around this, because I really need to be able to run multiple scripts
at the same time, but I have plenty of scripts that would block until
some sort of an event happens in the game, and other scripts need to
be able to run during the time the script waits.

Regards,
Reko Tiira

Reko Tiira

unread,
Apr 13, 2010, 8:25:08 AM4/13/10
to v8-users
I guess that one possible work around would be to mark str to be
persistent with Persistent::New(), and then Dispose() it at the end of
Thread 1?

Or maybe just make a decision that no unlocks should be done while a
HandleScope is active in the script.

Stephan Beal

unread,
Apr 13, 2010, 11:05:06 AM4/13/10
to v8-u...@googlegroups.com
On Tue, Apr 13, 2010 at 2:12 PM, Reko Tiira <reko....@gmail.com> wrote:
I want to embed V8 in my game engine, mainly because I prefer it's C++
API over that of SpiderMonkey's.

Amen, Reko!
 
{
   v8::Unlocker ul;
   Sleep(2);
}

Be aware that you must have a Locker in place before you do that, or the Unlocker will crash/assert. My case was very similar to yours, but since my code which uses Unlocker is plugin code (which might or might not be loaded by any given client), i had to go back and hack each client application to add a Locker before creating the context.
 
   // For sake of not having to write almost two identical functions,
   // assume that ONLY thread 1 does this:
   {
       v8::Unlocker ul;
       Sleep(2);
   }
   std::string s = String::AsciiValue ascii(str);

i might be mis-understanding the context here, but make sure that you do not use any v8 resources (e.g. the String constructor) while you are unlocked.
 
Let's say that two threads run this same script. When the first thread
unlocks for the first time and goes to sleep, the 2nd thread will
create a new HandleScope, and thus all consecutive allocations are
done on the 2nd thread's handle scope.

Another tip: get rid of the HandleScopes. i've spent many hours experimenting and they cause me nothing but grief. Mysterious segfaults when a bound function returns? 9 times out of 10 it's caused by the mere existence of a HandleScope. i don't use them at all any more, and i can't see a functional difference (other than that my apps don't crash when the bound functions return, unless i've done something else illegal).

 
So when thread 2 goes to sleep,
and thread 1 wakes up

will GET A CHANCE to wake up. Other threads might contend.
 
, it'll create a new String using the HandleScope
that was created on the thread 2 while thread 1 was sleeping. Now
thread 1 will go to sleep again, and thread 2 will wake up. At this
point thread 2 simply returns, and since the HandleScope goes out of
scope, it'll free all the locals that it had allocated, _including_
the string created in thread 1 (since it was allocated using the wrong
HandleScope). After this the control returns back to the thread 1,
which tries to use the already de-allocated value, and probably crash
because of that.

That sounds right to me (i say, without having actually run the code).

Am I getting this right? If I am, is there any plausible way to get
around this, because I really need to be able to run multiple scripts
at the same time, but I have plenty of scripts that would block until
some sort of an event happens in the game, and other scripts need to
be able to run during the time the script waits.

To me it looks like you're on the right track, but:

- IMO you should not try to use a var created from Thread A over in Thread B - that just "sounds dangerous" to me. Maybe v8 does allow this, though.

- My opinions are limited to my experience and the meager docs on the topic.

Happy hacking!

--
----- stephan beal
http://wanderinghorse.net/home/stephan/

Reko Tiira

unread,
Apr 13, 2010, 11:41:16 AM4/13/10
to v8-users
> On Tue, Apr 13, 2010 at 2:12 PM, Reko Tiira <reko.ti...@gmail.com> wrote:
> > I want to embed V8 in my game engine, mainly because I prefer it's C++
> > API over that of SpiderMonkey's.
>
> Amen, Reko!
>
> > {
> >    v8::Unlocker ul;
> >    Sleep(2);
> > }
>
> Be aware that you must have a Locker in place before you do that, or the
> Unlocker will crash/assert. My case was very similar to yours, but since my
> code which uses Unlocker is plugin code (which might or might not be loaded
> by any given client), i had to go back and hack each client application to
> add a Locker before creating the context.
>
> >    // For sake of not having to write almost two identical functions,
> >    // assume that ONLY thread 1 does this:
> >    {
> >        v8::Unlocker ul;
> >        Sleep(2);
> >    }
> >    std::string s = String::AsciiValue ascii(str);
>
> i might be mis-understanding the context here, but make sure that you do not
> use any v8 resources (e.g. the String constructor) while you are unlocked.

Yeah I'm not doing that, the only thing that happens in this thread
during unlock is Sleep().

> > Let's say that two threads run this same script. When the first thread
> > unlocks for the first time and goes to sleep, the 2nd thread will
> > create a new HandleScope, and thus all consecutive allocations are
> > done on the 2nd thread's handle scope.
>
> Another tip: get rid of the HandleScopes. i've spent many hours
> experimenting and they cause me nothing but grief. Mysterious segfaults when
> a bound function returns? 9 times out of 10 it's caused by the mere
> existence of a HandleScope. i don't use them at all any more, and i can't
> see a functional difference (other than that my apps don't crash when the
> bound functions return, unless i've done something else illegal).

Thanks for the tip, I'll keep that in mind! I think that might be the
easiest solution. :)

> > So when thread 2 goes to sleep,
> > and thread 1 wakes up
>
> will GET A CHANCE to wake up. Other threads might contend.

Yeah, but for the sake of the example, let's just assume that there
are two threads.

> > , it'll create a new String using the HandleScope
> > that was created on the thread 2 while thread 1 was sleeping. Now
> > thread 1 will go to sleep again, and thread 2 will wake up. At this
> > point thread 2 simply returns, and since the HandleScope goes out of
> > scope, it'll free all the locals that it had allocated, _including_
> > the string created in thread 1 (since it was allocated using the wrong
> > HandleScope). After this the control returns back to the thread 1,
> > which tries to use the already de-allocated value, and probably crash
> > because of that.
>
> That sounds right to me (i say, without having actually run the code).
>
> Am I getting this right? If I am, is there any plausible way to get
>
> > around this, because I really need to be able to run multiple scripts
> > at the same time, but I have plenty of scripts that would block until
> > some sort of an event happens in the game, and other scripts need to
> > be able to run during the time the script waits.
>
> To me it looks like you're on the right track, but:
>
> - IMO you should not try to use a var created from Thread A over in Thread B
> - that just "sounds dangerous" to me. Maybe v8 does allow this, though.

Well the thing is that I don't really want to use a var created in
Thread A over in Thread B. The whole point of the example was that
while a thread is sleeping (and unlocked), some other thread might
create a HandleScope that will then "own" the variables created after
the thread wakes up, without the programmer really knowing about it.
The problem is with the fact that HandleScope's aren't really owned by
any thread, so when the control returns to the first thread, it'll
happily use the latest created HandleScope; even if it was created in
the other thread.

But I think I'll just do what you suggested, and don't use
HandleScopes at all. Thanks for the feedback!

Regards,
Reko Tiira

Stephan Beal

unread,
Apr 13, 2010, 1:02:45 PM4/13/10
to v8-u...@googlegroups.com
On Tue, Apr 13, 2010 at 5:41 PM, Reko Tiira <reko....@gmail.com> wrote:
The whole point of the example was that
while a thread is sleeping (and unlocked), some other thread might
create a HandleScope that will then "own" the variables created after
the thread wakes up, without the programmer really knowing about it.
The problem is with the fact that HandleScope's aren't really owned by
any thread, so when the control returns to the first thread, it'll
happily use the latest created HandleScope; even if it was created in
the other thread.

Ah, i see. i unfortunately have no answer but would love to hear one.
 
But I think I'll just do what you suggested, and don't use
HandleScopes at all. Thanks for the feedback!

If i'm not mistaken, handlescopes are implicitly created as part of callback function bindings, so removing them might not solve the problem. (???)

Erik Corry

unread,
Apr 14, 2010, 3:33:05 AM4/14/10
to v8-u...@googlegroups.com
No, this case is taken care of.  The Locker and Unlocker objects make sure to activate the right handle scopes, so when thread 1 wakes up, the allocation takes place with the correct HandleScope.
 
thread 1 will go to sleep again, and thread 2 will wake up. At this
point thread 2 simply returns, and since the HandleScope goes out of
scope, it'll free all the locals that it had allocated, _including_
the string created in thread 1 (since it was allocated using the wrong
HandleScope). After this the control returns back to the thread 1,
which tries to use the already de-allocated value, and probably crash
because of that.

Am I getting this right? If I am, is there any plausible way to get
around this, because I really need to be able to run multiple scripts
at the same time, but I have plenty of scripts that would block until
some sort of an event happens in the game, and other scripts need to
be able to run during the time the script waits.

Regards,
Reko Tiira

Reko Tiira

unread,
Apr 14, 2010, 4:45:55 AM4/14/10
to v8-users
Oh awesome! Thanks for clarifying that up Erik.

Regards,
Reko Tiira

On Apr 14, 10:33 am, Erik Corry <erik.co...@gmail.com> wrote:

Matthias Ernst

unread,
Apr 14, 2010, 6:57:37 AM4/14/10
to v8-u...@googlegroups.com

That is a tip on the verge of FUD ;-) Can you be any more specific
about your problems? It sounds to me like you're accessing a Local
after the HandleScope in which it was created went away.

Matthias

Stephan Beal

unread,
Apr 14, 2010, 11:16:46 AM4/14/10
to v8-u...@googlegroups.com
On Wed, Apr 14, 2010 at 12:57 PM, Matthias Ernst <matt...@mernst.org> wrote:
On Tue, Apr 13, 2010 at 5:05 PM, Stephan Beal <sgb...@googlemail.com> wrote:
 > Another tip: get rid of the HandleScopes. i've spent many hours
> experimenting and they cause me nothing but grief. Mysterious segfaults when
> a bound function returns? 9 times out of 10 it's caused by the mere
> existence of a HandleScope. i don't use them at all any more, and i can't
> see a functional difference (other than that my apps don't crash when the
> bound functions return, unless i've done something else illegal).

That is a tip on the verge of FUD ;-) Can you be any more specific
about your problems? It sounds to me like you're accessing a Local 
after the HandleScope in which it was created went away.

It probably does sound like unqualified FUD, but it's a topic i've mentioned on the list several times and never seen a concrete solution for. Nor have i ever heard a concrete reason as to why HandleScopes are so important in client code (as opposed to inside the v8 engine). The VM should (and does, _i assume_) add a scope when a function is called, and local vars created in that scope should become eligible for GC when the function returns. i (as the client/embedder) shouldn't need an explicit scope to ensure that (my InvocationCallback function body is all the scope i need). Only in certain local scopes which create oodles of objects in a loop would i consider adding a HandleScope.

MANY times i've encountered post-function-return crashes simply because i instantiated a HandleScope in the function. After finally recognizing that the HandleScopes caused (or were one cause of) my crashes, i stopped using them. i haven't had any post-v8::InvocationCallback-return crashes since then except where i've done something else was illegal.

Reply all
Reply to author
Forward
0 new messages