Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invoking time-consuming JNI task as a thread

I'm having a tough problem with invoking a native function using JNI from a thread.

The native function is legacy code that performs a computation-intensive task. Since I'd like not to freeze the rest of the program, the computation should be performed in a background thread. EventBus is used to send the calculation result back to the main program.

Basically it should be quite simple, something like this:

public class CalculationEngine {
  private CalculationEngine(){}

  public static void calculateInBackground(final Parameters parameters) {

    new Thread(new Runnable() {
      public void run() {
        // Someone might change the parameters while our thread is running, so:
        final Parameters clonedParameters = parameters.clone();
        Results results = new Results();
        natCalc(clonedParameters, results);
        EventBus.publish("Results", results);
      }
    }).start();

  }

  public static void calculateNormally(final Parameters parameters) {
    Results results = new Results();
    natCalc(parameters, results);
    EventBus.publish("Results", results);
  }

  private static native synchronized void
    natCalc(Parameters parameters, Results results);      
}

Now, the calculateNormally method, which blocks the main program, works fine, but the calculateInBackground method, which just constructs a background thread to do the same thing, causes various crashes in the native code when it's invoked consecutively. By consecutively I mean that it's called again only after the previous thread has finished and returned the result. Note that the native code is marked synchronized to ensure that only one instance of it can be running at a time.

My question is, how on earth can the native code behave differently depending on whether it's invoked from the main thread, or from some other thread? It's like the native code were keeping "state", and not really quitting, when it's called from within a thread other than the main thread. Is there a way to "clean" or "flush" a thread after it's finished? There must be something in JNI & Threads that I simply don't know.

Thanks for any hints!

like image 564
Joonas Pulakka Avatar asked Dec 22 '22 10:12

Joonas Pulakka


1 Answers

I figured out a working solution, after googling and finding the phrase "I've found JNI to be very buggy when called from seperate threads... So make sure only one thread ever calls your native code!". It seems to be true; the solution is to keep a persistent, "reusable" thread around - I used Executors.newSingleThreadExecutor() - and to call the native code only from that thread. It works.

So the difference from JNI point of view was not between main thread vs. some other thread, but in using different threads in consecutive calls. Note that in the problematic code a new thread was constructed each time. It should work that way, but it doesn't. (And no, I'm not caching JNIEnv pointer.)

Whether it's a JNI bug, bug in the native code, something in the interaction between them and OS or whatever, would be interesting to know. But sometimes you just have no chance to debug 10000+ lines of existing code in detail, however, you're happy to get it to work. Here's working version of the example code, let's call this a workaround:

public class CalculationEngine {
  private CalculationEngine(){}

  private static Parameters parameters;
  private static ExecutorService executor = Executors.newSingleThreadExecutor();

  private static Runnable analysis = new Runnable() {
      public synchronized void run() {
        Results results = new Results();
        natCalc(parameters, results);
        EventBus.publish("Results", results);
      }
  };  

  public static synchronized void
    calculateInBackground(final Parameters parameters) {
      CalculationEngine.parameters = parameters.clone();
      executor.submit(analysis);
  }

  private static native synchronized void
    natCalc(Parameters parameters, Results results);      
}
like image 189
Joonas Pulakka Avatar answered May 24 '23 18:05

Joonas Pulakka