TerminateExecution() does not terminate threads calling sleep() in FunctionTemplate

686 views
Skip to first unread message

Laszlo Szakony

unread,
Apr 4, 2013, 4:16:16 AM4/4/13
to v8-u...@googlegroups.com

Hi,

I'm using v8 - v8_13185_v3.15.11 - on Windows, Win7 64bit in multi-threaded environment: several scripts are running parallel.
At an arbitrary time I want to terminate certain running scripts or maybe all from another thread. Some of the scripts may have infinite loops. Everything works fine, until some scripts call dosleep() in a FunctionTemplate defined as follows: 

(In thread script[n]) 

v8::Handle<v8::Value> DoSleep(const v8::Arguments& args) {

  int millisec = args[0]->ToInt32()->Int32Value();

  if (millisec)

  {

    v8::Unlocker unlock;

    v8::internal::OS::Sleep(millisec);

  }

  return v8::Undefined();

}

v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();

global->Set(v8::String::New("dosleep"), v8::FunctionTemplate::New(DoSleep));

The JavaScript Code looks sg. like this:

v8::Script::Compile(v8::String::New("function f() { while(true) {dosleep(500);} } f()"))->Run();

 

In the main thread I call:

v8::Locker locker;

for (int i = 0; i < kThreads; i++) {

  v8::V8::TerminateExecution(threads[i]->GetV8ThreadId());

 

The threads calling dosleep() won`t terminate. If I change the DoSleep() Function() to:

v8::Handle<v8::Value> DoSleep(const v8::Arguments& args) {

  int millisec = args[0]->ToInt32()->Int32Value();

  if (millisec)

  {

    v8::Unlocker unlock;

    for (int i = count; i--;) for (int j = 1000; j--;) ;

  }

  return v8::Undefined();

}


then the threads calling dosleep() will be terminated.

I have modified test-thread-termination.cc to demonstrate the behavior. After replacing test-thread-termination.cc with the attached one and compiling of ccttest the tests can be called by:

C:\v8_13185_v3.15.11\build\Release>cctest test-thread-termination/TerminateMultipleV8ThreadsWithDelayDefaultIsolate => OK

C:\v8_13185_v3.15.11\build\Release>cctest test-thread-termination/TerminateMultipleV8ThreadsWithSleepDefaultIsolate => Failed: no termination


Is this a bug? If not, how can I terminate scripts calling sleep()?


Thank you,

Laszlo

test-thread-termination.cc

Jakob Kummerow

unread,
Apr 4, 2013, 9:45:25 AM4/4/13
to v8-u...@googlegroups.com
TerminateExecution() does not send a kill signal to the process/thread or anything like that. It sets a flag that requests termination of currently running generated code for JS functions. Execution in loops isn't necessarily terminated immediately, several iterations of the loop may still happen (this depends on the length of the loop). The idea is that you can quickly gain control over long-running scripts ("Your browser tab is hanging, would you like to stop it?"). V8 does not have, and in my opinion does not need, a mechanism to interrupt v8::internal::OS::Sleep(), as this is never called from JS (in vanilla V8).

The reason your alternative "DoSleep" implementation terminates much quicker is because the C++ function finishes much sooner, returning control to JS, which in turn is interrupted after a couple of loop iterations. I'm willing to bet that if you reduce the sleeping interval from 500ms to, say, 1ms in the other implementation, you'll observe much quicker termination there (and, conversely, if you wait long enough, the 500ms version will terminate eventually).

Long story short: v8::TerminateExecution() is not designed to interrupt v8::internal::OS::Sleep(), nor any other function or FunctionTemplate implemented in C++. If you want to be able to have sleeping threads and terminate them, you'll have to use either another sleep implementation, or another way to request termination. (Where "use another" means "implement your own".)


--
--
v8-users mailing list
v8-u...@googlegroups.com
http://groups.google.com/group/v8-users
---
You received this message because you are subscribed to the Google Groups "v8-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to v8-users+u...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Mike Moening

unread,
Apr 4, 2013, 1:41:34 PM4/4/13
to v8-u...@googlegroups.com
Don't use sleep().  Use WaitForSingleObject() instead with a timeout equal to the number of seconds you want to sleep.
Wait on an event that is never signaled unless your thread is being terminated.

HANDLE hSleepEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

//Here's how you do the sleep
WaitForSingleObject(hSleepEvent, 5000); //Wait for entire 5 seconds or until the event is signaled.

//Here's how to wake up the sleeper
SetEvent(hSleepEvent);

//Don't forget to do this when you are done with the handle.
CloseHandle(hSleepEvent);

Laszlo Szakony

unread,
Apr 8, 2013, 4:59:07 AM4/8/13
to v8-u...@googlegroups.com
Hi Jakob,

thank you for the detailed explanation.
You are right. The execution was terminated after a long time with sleep(500), and with sleep(1) the execution terminated in a few seconds. 
Though my formulation of title of theme was not correct, your answer helped me to analyse the real problem.
I don´t want to terminate the thread(s) running the isolate. I just want to terminate the execution the currently running JS function in the isolate.
Using sleep() was just a tool setup a reproducible test environment. The tests in my system were setup as follows:
- Start 20 isolates running the JS function "function f() { while(true){sleep(10);} } f();"
- Call v8::TerminateExecution(thread_id) for the 20 isolates running the same JS code.
- PROBLEM: All but one isolate terminated the execution of JS code in a few seconds.
- The remaining isolate continued running the JS code, even calling v8::TerminateExecution(thread_id) several times.

So I modified in 'cctest' the file 'test-thread-termination.cc' to reproduce the behavior, but without success. It worked fine.
But I observed, that terminating the first isolate happens mach faster, then terminating the remaining ones.
The test is as follows:
- Start 3 isolates running the JS funciton "function f() { sleep(1000); while(true){sleep(1);} } f();"
- Call v8::TerminateExecution(thread_id) for the 3 isolates running the same JS code.
- I stored the looping count in the while loop, and printed at the end. Here are the results:

Seconds      Message
====================================================
00.000  Started Thread : 2
00.007  Thread 2 sleeping for 1000 millisec
00.007  Started Thread : 3
00.008  Thread 3 sleeping for 1000 millisec
00.009  Started Thread : 4
00.017  Thread 4 sleeping for 1000 millisec
00.018  Terminating Thread 2
00.019  Terminating Thread 3
00.021  Terminating Thread 4
01.007  Thread 2 sleeping over for 1000 millisec
01.009  Thread 3 sleeping over for 1000 millisec
01.018  Thread 4 sleeping over for 1000 millisec
01.133  Finished Thread : 2 with loop count : 0
04.110  Finished Thread : 4 with loop count : 2939
04.111  Finished Thread : 3 with loop count : 2951
04.113  Sum of loop counts : 5890

I repeated the test with different number of isolates, the result was always the same:

00.000  Started Thread : 2
00.012  Thread 2 sleeping for 1000 millisec
00.013  Started Thread : 3
00.018  Thread 3 sleeping for 1000 millisec
00.018  Started Thread : 4
00.020  Thread 4 sleeping for 1000 millisec
00.020  Started Thread : 5
00.032  Started Thread : 6
00.034  Thread 6 sleeping for 1000 millisec
00.044  Thread 5 sleeping for 1000 millisec
00.045  Terminating Thread 2
00.046  Terminating Thread 5
00.047  Terminating Thread 3
00.048  Terminating Thread 6
00.048  Terminating Thread 4
01.012  Thread 2 sleeping over for 1000 millisec
01.018  Thread 3 sleeping over for 1000 millisec
01.020  Thread 4 sleeping over for 1000 millisec
01.044  Thread 6 sleeping over for 1000 millisec
01.045  Thread 5 sleeping over for 1000 millisec
01.065  Finished Thread : 2 with loop count : 0
02.568  Finished Thread : 3 with loop count : 1475
02.570  Finished Thread : 6 with loop count : 1471
02.572  Finished Thread : 5 with loop count : 1471
02.577  Finished Thread : 4 with loop count : 1475
02.580  Sum of loop counts : 5892

Interesting is, that the sum of the loop counts seems to be constant.
Would it mean that running 20 isolates terminates faster that running 2 or 3?

I suspect, that in my application the remaining isolate that does not terminate the execution of the running JS code is the "first" isolate.

Do you have any idea, what conditions could cause to prevent the termination of execution of JS Code?
Timing constraints, debugging ports, common global object, ...?

Thank you,

Laszlo

Jakob Kummerow

unread,
Apr 8, 2013, 5:14:57 AM4/8/13
to v8-u...@googlegroups.com
I didn't realize that you had several threads running at the same time in the same isolate. V8 is not designed for this, and you could hit all sorts of "interesting" behavior. You should use a separate isolate for each thread.

Laszlo Szakony

unread,
Apr 8, 2013, 8:55:22 AM4/8/13
to v8-u...@googlegroups.com
OK. I will use isolates. Can I share the global object between isolates?

Dmitry Lomov

unread,
Apr 8, 2013, 8:57:48 AM4/8/13
to v8-u...@googlegroups.com
No. Isolates are completely isolated "instances" of V8, including separate heaps. 
Generally, by design Javascript lacks facilities for shared-memory multithreaded programming.

Kind regards,
Dmitry

Sven Panne

unread,
Apr 8, 2013, 9:17:13 AM4/8/13
to v8-u...@googlegroups.com
Just a quick drive-by comment: You *can* use an Isolate from various threads, but you'll have to use a v8::Locker in all threads then. This way, you inform v8 about your thread switches and make sure that at any given point in time at most one thread is actively using an Isolate.

Laszlo Szakony

unread,
Apr 8, 2013, 9:35:39 AM4/8/13
to v8-u...@googlegroups.com
That`s what I`m doing. I have one Context in the default isolate - one global object - and several threads running JS functions. Each thread uses v8::Locker for synchronization.
Can I call in this case TerminateExecution() for each thread or not? It seems to work up to the sleep() problem, where 1 thread remains running.

Jakob Kummerow

unread,
Apr 8, 2013, 10:13:08 AM4/8/13
to v8-u...@googlegroups.com
Earlier you said that "several scripts are running [in] parallel". That's precisely what's not supported. Sven was pointing out that when several threads need to access an isolate, they can do so, but only *one at a time*. No two threads can be allowed to run in the same isolate at the same time, or very weird things will begin to happen (the observation about the constant sum of loop counts is just a mildly amusing, harmless example; you could also have data loss, crashes and whatnot).


--

Laszlo Szakony

unread,
Apr 8, 2013, 10:28:54 AM4/8/13
to v8-u...@googlegroups.com
I understand 'in parallel' that the threads running the JS functions use the same Context hence the same global object - with every implication of course. The synchronization is done by v8::Locker, so no thread is accessing the isolate without acquiring the  v8::Lock. Sorry for the confusion. The question is still, if some circumstances exists, where TerminateExecution() does not terminate the execution of a running JS function in such an environment?

Laszlo Szakony

unread,
Apr 22, 2013, 12:23:58 PM4/22/13
to v8-u...@googlegroups.com
Hi,
I´ve made several tests with TerminateExecution() running the endless loop with sleep.
As I mentioned, the test environment was:
1 Isolate, 1 common Context, 1 global object
Several threads – using v8::Lock/Unlock running the JS functions: while(true) { sleep(1); } – where sleep is a JS FunctionTemplate extension and it does what its name suggests.
Call TerminateExecution() for all of the threads running the JS function.

The result was almost every time that 1 thread did not terminate the JS function. However in some cases it worked fine. I just wanted to see more info, so I modified the JS script:
counter = {};
counter[scriptName] = 0;
while(true){
++counter[scriptName];
sleep(1);
}

The global variable counter is an object containing the loop count for each script. scriptName was different in each thread running the JS function. Before I called TerminateExecution(), I set counter[scriptName] = 0;

And … it worked. All threads terminated the execution. The loop counts were between some hundreds to some thousands.

It seemed to be a timing Problem. I’ve produced more loads on the computer, and I repeated the tests. And it happened again. One thread did not terminate the execution. All other threads – I‘ve tested up to 10 threads – terminated the execution with some thousands loop count, but 1 was still running. And it was not always the same. But I could display the loop count. I waited more than 600000 and one time I left to run through the night – no termination.
I wanted to stop the execution with the following JS function: ‘counter = {}’. I started this function in another thread, and expected a ReferenceError Exception as counter[scriptName] did not exist anymore. The execution terminated with an Exception; however it was an uncaughtable Exception. That should be produced by TerminateExecution(). 
As I understand, it means, that the v8 thread recognized the TerminateExecution() request, but it did not stop the iteration of the termination.
Unfortunately I cannot reproduce this behavior in simple test case. It just happens in my application. I hope, this info is useful for the development.

The tests showed, that requesting the termination may take several thousands of iterations and in some cases the iteration does not terminate – interestingly always only for one thread. I could not find any public v8 functions that reflect this state of the v8 thread: iterating for termination. Is it possible to interrogate this state? If not, it would be nice to have a function like v8::V8::IsExecutionTerminationRequested(). Time consuming C++ extensions could be aborted without waiting for end of termination iteration.

Thank you, 

Laszlo
Reply all
Reply to author
Forward
0 new messages