Multiple Rhino (java) threads manipulate the same file

1.4k views Asked by At

I am writing a piece of javascript (ecmascript) within a 3rd-party application which uses embedded Rhino. The application may start multiple Java threads to handle data concurrently. It seems that every Java thread starts its own embedded Rhino context which in turn runs my script.

The purpose of my script is, to receive data from the application and use it to maintain the contents of a particular file. I need a fail-safe solution to handle the concurrency from my script.

So far, what I have come up with is to call out to java and use java.nio.channels.FileLock. However, the documentation here states:

File locks are held on behalf of the entire Java virtual machine. They are not suitable for controlling access to a file by multiple threads within the same virtual machine.

Sure enough, the blocking call FileChannel.lock() does not block but throws an exception, leading to the following ugly code:

var count = 0;
while ( count < 100 )
{
    try 
    {
        var rFile = new java.io.RandomAccessFile(this.mapFile, "rw");
        var lock = rFile.getChannel().lock();
        try
        {
            // Here I do whatever the script needs to do with the file
        }
        finally
        {
            lock.release();
        }
        rFile.close();
        break;
    } catch (ex) {
        // This is reached whenever another instance has a lock
        count++;
        java.lang.Thread.sleep( 10 );
    }       
}

Q: How can I solve this in a safe and reliable manner?

I have seen posts regarding Rhino sync() being similar to Java synchronized but that does not seem to work between multiple instances of Rhino.

UPDATE

I have tried the suggestion of using Synchronizer with org.mozilla.javascript.tools.shell.Global as a template:

function synchronize( fn, obj )
{
    return new Packages.org.mozilla.javascript.Synchronizer(fn).call(obj);
}

Next, I use this function as follows:

    var mapFile = new java.io.File(mapFilePath);
    // MapWriter is a js object
    var writer = new MapWriter( mapFile, tempMap );
    var on = Packages.java.lang.Class.forName("java.lang.Object");
    // Call the writer's update function synchronized
    synchronize( function() { writer.update() } , on );

However I see that two threads enter the update() function simultaneously. What is wrong with my code?

1

There are 1 answers

2
David P. Caldwell On BEST ANSWER

Depending how Rhino is embedded, there are two possibilities:

  1. If the code is executed in the Rhino shell, use the sync(f,lock) function to turn a function into a function that synchronizes on the second argument, or on the this object of its invocation if the second argument is absent. (Earlier versions only had the one-argument method, so unless your third-party application uses a recent version, you may need to use that or roll your own; see below.)

  2. If the application is not using the Rhino shell, but using a custom embedding that does not include concurrency tools, you'll need to roll your own version. The source code for sync is a good starting point (see the source code for Global and Synchronizer; you should be able to use Synchronizer pretty much out-of-the-box the same way Global uses it).

It is possible that the problem is that the object on which you are trying to synchronize is not shared across contexts, but is created multiple times by the embedding or something. If so, you may need to use some sort of hack, especially if you have no control over the embedding. If you have no control over the embedding, you could use some kind of VM-global object on which to synchronize, like Runtime.getRuntime() or something (I can't think of any that I immediately know are single objects, but I suspect several of those with singleton APIs like Runtime are.)

Another candidate for something on which to synchronize would be something like Packages.java.lang.Class.forName("java.lang.Object"), which should refer to the same object (the Object class) in all contexts unless the embedding's class loader setup is extremely unusual.