Over a million developers have joined DZone.

Optimizing JavaScript and Solving the Halting Problem: Part II

DZone's Guide to

Optimizing JavaScript and Solving the Halting Problem: Part II

In the conclusion of this series, we take a look at how tackling the issue of running untrusted code and the issues that come with it. Read on for how the RavenDB team dealt with just that.

Free Resource

SignalFx is the only real-time cloud monitoring platform for infrastructure, microservices, and applications. The platform collects metrics and traces across every component in your cloud environment, replacing traditional point tools with a single integrated solution that works across the stack.

In the previous post, I talked about the issues we had with wanting to run untrusted code and wanting to use Jurassic to do so. The problem is that when the IL code is generated, it is then JITed and run on the machine directly, we have no control over what it is doing. And that is a pretty scary thing to have. A simple mistake causing an infinite loop is going to take down a thread, which requires us to abort it, which means that we are now in a funny state. I don’t like funny states in production systems.

So we were stuck, and then I realized that Jurrasic is actually generating IL for the scripts. If it generates the IL, maybe we can put something in the IL? Indeed we can, here is the relevant PR for Jurrasic code. Here is the most important piece of code here:

public void TryEmitOnLoopIteration(ILGenerator generator)
if (EmitOnLoopIteration == null)

 if (EmitOnLoopIteration.Target != null)

The key here is that we can provide Jurrasic with a delegate that will be called at specific locations in the code. Basically, we do that whenever you evaluate a loop condition or just before you jump using goto. That means that our code can be called, which means that we can then check whatever limits such as time/number of iterations/memory used have been exceeded and aborted.

Now, the code is written in a pretty funny way. Instead of invoking the delegate we gave to the engine, we are actually embedding the invocation directly in each loop prolog. Why do we do that? Calling a delegate is something that has a fair amount of cost when you are doing that a lot. There is a bit of indirection and pointer chasing that you must do.

The code above, however, is actually statically embedding the call to our methods (providing the instance pointer if needed). If we are careful, we can take advantage of that. For example, if we mark our method with aggressive inlining, that will be taken into account. That means that the JavaScript code will turn into IL and then into machine code and our watchdog routine will be there as well. Here is an example of such a method:

private int _numberOfLoopIterations;
private int _totalIterations;
private int _iterationsBudget;

public void Watchdog()
  if(++this._numberOfLoopIterations < 1000)

public void Watchdog_Stage2()
   this._totalIterations+= this._numberOfLoopIterations;
   this._numberOfLoopIterations = 0;// reset counter
   if(this._iterationsBudget > this._totalIterations)

public void ThrowTooManyIterations()
  throw new InvalidOperationException("Too many steps...");

We register the watchdog method with the engine. You’ll notice that the code smells funny. Why do we have a watchdog and then a stage 2 watchdog? The reason for that is that we want to inline the watchdog method, so we want it to be small. In the case above, here are the assembly instructions that will be added to a loop prolog.

mov         ecx,dword ptr [rsi+8]  
inc         ecx  
mov         eax,ecx  
mov         dword ptr [rsi+8],eax  
cmp         eax,400h  
jae         00007FF83EEB0506  

These are 6 machine instructions that are going to have great branch prediction and in general be extremely lightweight. Once in a while, we’ll do a more extensive check. For example, we can check the time or use GC.GetAllocatedBytesForCurrentThread() to see that we didn’t allocate too much. The stage 2 check is still expected to be fast, but by making it only rarely, it means that we can actually afford to do this in each and every place and we don’t have to worry about the performance of it.

This means that our road is opened to now move to Jurassic fully, since the major limiting factor, trusting the scripts, is now handled.

SignalFx is built on a massively scalable streaming architecture that applies advanced predictive analytics for real-time problem detection. With its NoSample™ distributed tracing capabilities, SignalFx reliably monitors all transactions across microservices, accurately identifying all anomalies. And through data-science-powered directed troubleshooting SignalFx guides the operator to find the root cause of issues in seconds.

infinite loop ,javascript ,performance ,database performance

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}