Tuesday Jul 09, 2013

Nashorn Multithreading and MT-safety

Tobias Schlotte asked the question (via twitter @tobsch), "Where is thread-safety on your agenda for Nashorn?"  The quick answer is that multithreading is very high on the list, but MT-safety is a very complicated issue. I'll attempt to explain our plan of action.

As Attila responded http://mail.openjdk.java.net/pipermail/nashorn-dev/2013-July/001567.html "ECMAScript 5.1 language specification doesn't define multithreading semantics for programs written in the language; they are inherently single threaded. If we were to make them thread safe, we'd be sacrificing single threaded performance for a behaviour that falls outside of the specification."

Why is performance affected?  Well, generally, the internal structure of a script object is complex.  Any modification to the object would have to be done with mutex locks (synchronize) or compare/swap.  As an example, take obj.x = 1.  In a static language, this code would likely generate a simple native store that can be executed, somewhat, atomically.  With multiple threads, obj.x = ? would simply be "last thread that stores, wins" (ignoring coherency et al) and the application would continue to run.  In dynamic languages like JavaScript, what happens when property "x" doesn't exist yet?  Internally, the script object has to be restructured to include the new property before the store takes place.  Now add the fact that another thread could be adding or removing properties on the same object; objects would have to be locked for (nearly) every operation. Then add in the thread coherency issue…  Slowness, reigns.

On the other hand, having a language on the JVM and not supporting multithreading is just plain daft.  Especially, since our target platforms include multi-core servers.

So, our agenda is two fold.  The first is to provide a "workers" library (timeline is not tied to JDK8) which uses an onevent model that JavaScripters are familiar with.  No synchronization/locking constructs to be added to the language.  Communication between threads (and potentially nodes/servers) is done using JSON (under the covers.) Going this route allows object isolation between threads, but also allows maximal use of CPU capacity.

The second part is, we will not guarantee MT-safe structures, but we have to face reality.  Developers will naturally be drawn to using threads.  Many of the Java APIs require use of threads.  So, the best we can do is provide guidelines on how to not shoot yourself in the foot.  These guidelines will evolve and we'll post them 'somewhere' after we think them through.  In the meantime,  I follow some basic rules;
  • Avoid sharing script objects or script arrays across threads (this includes global.)  Sharing script objects is asking for trouble.  Share only primitive data types, or Java objects.
  • If you want to pass objects or arrays across threads, use JSON.stringify(obj) and JSON.parse(string) to transport using strings.
  • If you really really feel you have to pass a script object, treat the object as a constant and only pass the object to new threads (coherency.)  Consider using Object.freeze(obj).
  • If you really really really feel you have to share a script object, make sure the object's properties are stabilized.  No adding or removing properties after sharing starts.  Consider using Object.seal(obj).
  • Given enough time, any other use of a shared script object will eventually cause your app to fail.
The number of reallys in a rule reflects the uncertainty of the outcome.

Note in the first rule, I stated that you shouldn't share global.  The reason is simple, almost everything adds properties to global.  How do you avoid share modifying global?  Simply give each thread a new global.

Nashorn has two builtin functions for starting scripts; load and loadWithNewGlobal.  Each takes an arg specifying which script to load and evaluate; either an URL/file string or an object with a name and script properties (name is for source location, script is body of script.)  Additional arguments are passed as the arguments global when evaluating.  Unlike load, loadWithNewGlobal creates a fresh global before loading the script.  This isolates evaluation from the current global and thus suppresses sharing.

Going back to Tobias' example (the original is at https://gist.github.com/tobsch/5955518);

import javax.script.CompiledScript;
import javax.script.Compilable;
import javax.script.ScriptException;
 
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
import jdk.nashorn.api.scripting.NashornScriptEngine;
 
import java.util.concurrent.Future;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.Callable;
 
import java.util.ArrayList;
 
public class SSCCE {
  private static final NashornScriptEngineFactory engineFactory = new NashornScriptEngineFactory();
   
  public static void main(String[] args) throws ScriptException, InterruptedException, ExecutionException {
    Compilable engine = (Compilable) engineFactory.getScriptEngine();
     
    String script = new StringBuilder("i = 0;")
      .append("i += 1;")
      .append("shortly_later = new Date()/1000 + Math.random;") // 0..1 sec later
      .append("while( (new Date()/1000) < shortly_later) { Math.random() };") //prevent optimizations
      .append("i += 1;")
      .toString();
     
    final CompiledScript onePlusOne = engine.compile(script);
     
    Callable<Double> addition = new Callable<Double>() {
      @Override
      public Double call() {
        try {
          return (Double) onePlusOne.eval();
        }
        catch(ScriptException e) {
          throw new RuntimeException(e);
        }
      }
    };
     
    ExecutorService executor = Executors.newCachedThreadPool();
    ArrayList<Future<Double>> results = new ArrayList<Future<Double>>();
     
    for(int i = 0; i < 50; i++) {
      results.add(executor.submit(addition));
    }
     
    int miscalculations = 0;
    for(Future<Double> result : results) {
      int jsResult = result.get().intValue();
       
      if(jsResult != 2) {
        System.out.println("Incorrect result from js, expected 1 + 1 = 2, but got " + jsResult);
        miscalculations += 1;
      }
    }
     
    executor.awaitTermination(1, TimeUnit.SECONDS);
    executor.shutdownNow();
     
    System.out.println("Overall: " + miscalculations + " wrong values for 1 + 1.");
  }
}

Output;

Incorrect result from js, expected 1 + 1 = 2, but got 11
Incorrect result from js, expected 1 + 1 = 2, but got 4
Incorrect result from js, expected 1 + 1 = 2, but got 9
Incorrect result from js, expected 1 + 1 = 2, but got 6
Incorrect result from js, expected 1 + 1 = 2, but got 4
Incorrect result from js, expected 1 + 1 = 2, but got 4
Incorrect result from js, expected 1 + 1 = 2, but got 6
Incorrect result from js, expected 1 + 1 = 2, but got 5
Incorrect result from js, expected 1 + 1 = 2, but got 6
Incorrect result from js, expected 1 + 1 = 2, but got 3
Incorrect result from js, expected 1 + 1 = 2, but got 3
Incorrect result from js, expected 1 + 1 = 2, but got 13
Incorrect result from js, expected 1 + 1 = 2, but got 10
Incorrect result from js, expected 1 + 1 = 2, but got 6
Incorrect result from js, expected 1 + 1 = 2, but got 13
Incorrect result from js, expected 1 + 1 = 2, but got 5
Incorrect result from js, expected 1 + 1 = 2, but got 6
Incorrect result from js, expected 1 + 1 = 2, but got 11
Incorrect result from js, expected 1 + 1 = 2, but got 9
Incorrect result from js, expected 1 + 1 = 2, but got 7
Incorrect result from js, expected 1 + 1 = 2, but got 11
Incorrect result from js, expected 1 + 1 = 2, but got 11
Incorrect result from js, expected 1 + 1 = 2, but got 7
Incorrect result from js, expected 1 + 1 = 2, but got 9
Incorrect result from js, expected 1 + 1 = 2, but got 3
Incorrect result from js, expected 1 + 1 = 2, but got 3
Incorrect result from js, expected 1 + 1 = 2, but got 13
Incorrect result from js, expected 1 + 1 = 2, but got 11
Incorrect result from js, expected 1 + 1 = 2, but got 12
Incorrect result from js, expected 1 + 1 = 2, but got 8
Incorrect result from js, expected 1 + 1 = 2, but got 4
Incorrect result from js, expected 1 + 1 = 2, but got 4
Incorrect result from js, expected 1 + 1 = 2, but got 4
Incorrect result from js, expected 1 + 1 = 2, but got 8
Incorrect result from js, expected 1 + 1 = 2, but got 9
Incorrect result from js, expected 1 + 1 = 2, but got 3
Incorrect result from js, expected 1 + 1 = 2, but got 5
Incorrect result from js, expected 1 + 1 = 2, but got 3
Incorrect result from js, expected 1 + 1 = 2, but got 9
Incorrect result from js, expected 1 + 1 = 2, but got 6
Incorrect result from js, expected 1 + 1 = 2, but got 7
Overall: 41 wrong values for 1 + 1.

I rewrote in JavaScript and introduced loadWithNewGlobal;

#!/usr/bin/env jjs -scripting

var Executors = java.util.concurrent.Executors;
var TimeUnit  = java.util.concurrent.TimeUnit;
var ArrayList = java.util.ArrayList;

var script = <<EOD
    i = 0;
    i += 1;
    shortly_later = new Date()/1000 + Math.random;
    while( (new Date()/1000) < shortly_later) { Math.random() };
    i += 1;
EOD

function addition() {
    return loadWithNewGlobal({ name: "addition", script: script });
}

var executor = Executors.newCachedThreadPool();
var results = new ArrayList();

for(var i = 0; i < 50; i++) {
    // Clarify Runnable versus Callable
    results.add(executor["submit(java.util.concurrent.Callable)"](addition));
}

var miscalculations = 0;
for each (var result in results) {
    var jsResult = result.get().intValue();
    if (jsResult != 2) {
        print("Incorrect result from js, expected 1 + 1 = 2, but got " + jsResult);
        miscalculations += 1;
    }
}

executor.awaitTermination(1, TimeUnit.SECONDS);
executor.shutdownNow();
print("Overall: " + miscalculations + " wrong values for 1 + 1.");

Works as expected;

Overall: 0 wrong values for 1 + 1.

Wednesday Jun 19, 2013

Nashorn and Lambda, What the Hey!

Yesterday, Brian Goetz (Lambda architect) suggested that I produce an example of Lambda being used from Nashorn.  Since, I've been heads down in Nashorn, I really haven't played with Lambda that much.  After looking at some examples from Stuart Marks,  I figured, what the hey, it doesn't look that hard.  Details of the Lambda APIs are available at JDK 8 b92 API .

Many constructs are familiar to JavaScript developers.  The main things to note;
  • where you can use a Lambda you can use a JavaScript function
  • JavaScript arrays need to be converted to Java collections
  • JavaScript syntax requires the '.' end a phrase, not start one (forces continuation)
Other than that, the JavaScript source and Java source looks very much the same.

#!/usr/bin/env jjs -scripting

var copyright = <<<EOS;
/*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
EOS

var Collectors = java.util.stream.Collectors;

// Break the copyright into tokens.
var tokens = copyright.split(/\s+/);

// Convert to ArrayList.
var list = new java.util.ArrayList();
tokens.map(function(e) list.add(e));

// The JavaScript collection for the result.
var result = [];

// Parallelize some of the activity.
list.parallelStream().
    // Select only words.
    filter(function(t) t.match(/^[A-Za-z]+$/)).
    // Make case comparable.
    map(function(t) t.toLowerCase()).
    // Fold duplicates.
    collect(Collectors.groupingBy(function(t) t)).
    // Move results to JavaScript collection.
    forEach(function(t) result.push(t));

// Sort the result.
result.sort();

print(result);
The result;
a,above,advised,all,and,any,are,arising,be,binary,business,but,by,caused,code,
conditions,consequential,contributors,copyright,damages,derived,disclaimer,
documentation,endorse,even,event,express,fitness,following,for,form,from,
goods,holders,however,if,implied,in,is,its,liable,limited,list,loss,materials,
may,merchantability,must,name,names,negligence,neither,no,nor,not,of,on,or,
oracle,other,out,owner,particular,permitted,possibility,prior,procurement,
products,promote,provided,purpose,redistribution,redistributions,reproduce,
retain,rights,shall,software,source,specific,strict,substitute,such,that,
the,theory,this,to,tort,use,used,warranties,way,whether,with,without,written

Wednesday Mar 20, 2013

Nashorn Events Coming Up

The team will be going to several events in the next few weeks.

Attila will be at Devoxx UK next week,  starting with a JUG Hackday on Sunday March 24, 2013 and presentation titled "Project Nashorn In Java 8 - JavaScript As A First Class Language On The JVM" on Wednesday March 27, 2012.

Jim (me) will be at EclipseCon 2013 for the Thursday March 28, 2013 keynote.

About

Technical discussions and status of the Nashorn JavaScript Project.

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today