This project has moved. For the latest updates, please go here.

Holding references in C# to functions that exist in JS and cleanup

Sep 26, 2014 at 7:38 PM
So here's the scenario I'm working with: I have a data coming in from a socket and I'd like to have user provided javascript components processing that data. I have js that conceptually looks like this:
components.init = function(container) {
  container.register('typeOfData1', myFunction1);
  container.register('typeOfData2', myFunction2);
}

function myFunction1(args) {
  // do stuff
}

function myFunction(args) {
  // do more stuff
}
Boiled down to essentials my c# code looks something like this:
public class Container
{
   ...
   public void register(string dataType, object obj) 
   {
       SaveFunctionObj(dataType, obj);
   } 
   public void Execute(string dataType, string args)
   {
      object obj = GetFunctionObj(dataType);
      dynamic method = obj;
      method(args);
   }
}

static void Main()
{
   using (var engine = new V8ScriptEngine())
   {
     var container = new Container();
     dynamic components = new ExpandoObject();
     engine.AddHostObject("components", components);     

     // get scripts that look like the js above
     foreach(string script in GetTheScripts())
     {
       engine.Evaluate(script);
       components.init(container);
     }

     // start a long-running loop
     while (true) 
     {
       var data = GetData();
       container.Execute(data.DataType, data.Args);
     } 
  }
}
Sorry for all the code, just wanted to be clear in how I was approaching the problem. I basically have a bunch of scripts which respond to certain data coming in. I have some C# code that holds references to functions that exist in the scripts and runs each as needed.

I built this prototype and to my amazement the whole thing just worked, right off the bat. So, amazing job ClearScript team. Really incredible framework you've written.

I am a complete ClearScript newb, so my questions are:
  1. Is this a good idea? Should I be holding on to references to functions that exist in the script the way I am? Are there bad performance implications of doing so?
  2. Does it make sense to have one engine for all scripts? From what I've been reading it's expensive to create multiple engines. I may have 20-30 different scripts, so it sounds like I don't want 30 different engines running.
  3. I'd love to listen for file changes and automatically update the running engine using the method above. I can do book keeping and get rid of my C# references to the js functions for that file. However the engine would still hold those references wouldn't it? Would the engine know to clean those up?
Coordinator
Sep 27, 2014 at 5:21 PM
Greetings!

Thank you for the positive feedback!

Is this a good idea? Should I be holding on to references to functions that exist in the script the way I am? Are there bad performance implications of doing so?

Easy access to script objects and functions is one of ClearScript's fundamental goals. That said, accessing a script object or invoking a script function is relatively expensive. It involves the use of .NET's dynamic infrastructure, managed-to-native transitions, argument marshaling, lock acquisition, etc.

Does it make sense to have one engine for all scripts? From what I've been reading it's expensive to create multiple engines. I may have 20-30 different scripts, so it sounds like I don't want 30 different engines running.

It really depends on your application's requirements. If you need concurrent script execution, you need multiple engines. If you need to run each script in a pristine environment, you can use one V8 runtime to efficiently spawn a separate engine for each script, or just create and dispose engines as necessary. There are definitely tradeoffs, but even for server applications that require maximum concurrency, we recommend using a small pool of engines or runtimes. As with worker threads, the sweet spot is somewhere in the neighborhood of your CPU core count.

I'd love to listen for file changes and automatically update the running engine using the method above. I can do book keeping and get rid of my C# references to the js functions for that file. However the engine would still hold those references wouldn't it? Would the engine know to clean those up?

The CLR and the script engine have independent object systems and garbage collectors, and proxies are used on each side to refer to objects on the other. A proxy by necessity prevents its target from being collected. When the proxy itself is collected, its target is released to its garbage collector only if no other reachable references exist on either side.

In your case, the script leaves global references to myFunction1() and myFunction2(), so they can't be collected even after the managed references are gone. It would be different if your script looked like this:
components.init = function (container) {
    container.register('typeOfData1', function (args) { /* do stuff */ });
    container.register('typeOfData2', function (args) { /* do stuff */ });
}
Another consideration is that V8's garbage collector is extremely lazy. If your application uses long-lived V8ScriptEngine or V8Runtime instances, we strongly recommend that you periodically call CollectGarbage().

Thanks again, and good luck!
Sep 29, 2014 at 11:27 PM
That's just what I needed to know. I'll give it a try using the runtime to spawn the needed engines as I indeed need concurrent execution and it would ensure a pristine script environment. Thanks so much for addressing all of my questions!
Coordinator
Sep 30, 2014 at 1:12 PM
No problem at all!

Just to clarify, each V8 runtime supports single-threaded access only, but multiple runtimes can operate concurrently. In ClearScript, the V8ScriptEngine constructor creates a private runtime for each instance, but you can also create a runtime explicitly, and then call V8Runtime.CreateScriptEngine() to create one or more engines that share that runtime. Engines that share a runtime cannot be used concurrently.

Cheers!
Sep 30, 2014 at 3:41 PM
Just to make sure I understand correctly:

If I were to use one V8Runtime to efficiently spawn the needed engines, in my scenario each engine would only be tied to one script and the script would be evaluated only once. Then on the C# side I'd call something similar to the code above: container.Execute(data.DataType, data.Args) concurrently. So in the end, the functions which are contained in the script and hence each engine, would be accessed concurrently. And that is not supported because of the shared runtime, right?

If I have that right and opt to not share a runtime are there performance implications?
Coordinator
Sep 30, 2014 at 6:12 PM
Hi again,

So in the end, the functions which are contained in the script and hence each engine, would be accessed concurrently. And that is not supported because of the shared runtime, right?

That's correct. If the engines share a runtime, the scripts will be executed sequentially.

If I have that right and opt to not share a runtime are there performance implications?

Yes. Two engines with private runtimes are heavier than two engines that share a runtime, mostly in terms of memory usage. V8 runtimes also reserve large blocks of address space, and while that's not as expensive as actual allocation, a few dozen runtimes can easily exhaust the address space of a 32-bit process.

This is why we recommend that servers and other multithreaded applications use small runtime pools. Another possibility is simply to create and dispose runtimes on demand, effectively letting the thread pool be the master resource manager, but because runtimes have startup and teardown costs, this approach will not perform as well.

Good luck!