ViktorC / PP4J

A multiprocessing library for Java that features process pool implementations and a flexible API.
Apache License 2.0
32 stars 6 forks source link

Question about task submit and ProcessPool #9

Closed dmascle closed 5 years ago

dmascle commented 5 years ago

Hi,

Firstly, congratulation for this great library, it is very helpful. I just had a question about it (not an issue) !

I use this library to call some C++ code and I was wondering if it could be possible to terminate a process in which a crash happened while running a task, so that it won't be used anymore for new tasks. The goal is to stop using process in which unmanaged C++ memory may be corrupted even if the crash exception was catched correctly.

For ex: jvmPool.submit((Callable & Serializable) () -> { SomeCppFunctionCall(); // A CRASH HAPPENS HERE return rand.nextLong(); }));

Is there a way to "tell" the ProcessPool to terminate the current process in which the crash happened ?

Thank you !

ViktorC commented 5 years ago

Hi,

Thank you, I'm glad you find it helpful.

Admittedly, it's not very elegant, but you should be able to achieve this by invoking System.exit(int) in your task. For example like this:

jvmPool.submit((Callable<Long> & Serializable) () -> {
  SomeCppFunctionCall(); // A CRASH HAPPENS HERE
  if (HasCppCallCrashed()) {
    System.exit(1);
  }
  return rand.nextLong();
});

I hope that helps.

dmascle commented 5 years ago

Thank you for your answer !

I'm sorry I forgot to mention that I already tried that solution and there is a problem with it. Actually, the process terminates as excepted after System.exit() call, however it seems the submitted task isn't considered as finished, thus the process pool restarts a new process and re-run the task from the beginning... which is not what I would want in such case.

Would it be possible to "tell" the process pool that the task is finished before calling System.exit() ?

Thank you !

ViktorC commented 5 years ago

Ah, okay, that makes the situation a bit more complicated.

Unfortunately, the API doesn't allow for doing this at the moment. I definitely think it should, though, as it's a completely valid use case. The solution could be as simple as making the exit static method of JavaProcess public.

At any rate, if you need an immediate workaround, this should do the trick:

jvmPool.submit((Callable<Long> & Serializable) () -> {
  SomeCppFunctionCall(); // A CRASH HAPPENS HERE
  if (HasCppCallCrashed()) {
    try {
      Method exitMethod = JavaProcess.class.getDeclaredMethod("exit");
      exitMethod.setAccessible(true);
      exitMethod.invoke(null);
    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
      throw new RuntimeException(e);
    }
  }
  return rand.nextLong();
});
ViktorC commented 5 years ago

Hi,

I have released a new version, 3.0.2, that allows you to terminate the Java process from within a task in an orderly fashion like this:

jvmPool.submit((Callable<Long> & Serializable) () -> {
  SomeCppFunctionCall(); // A CRASH HAPPENS HERE
  if (HasCppCallCrashed()) {
    JavaProcess.exit();
  }
  return rand.nextLong();
});

This ensures that the process terminates after finishing the task. Also, the submission will be considered successfully executed so it won't be delegated to other processes.

Let me know if that solves your problem.

Cheers, Viktor

dmascle commented 5 years ago

Hi,

I tried with the new version and calling JavaProcess.exit() and it works great ! Thank you for your quick answers !

I just modified a little bit the code to throw an exception after JavaProcess.exit() call, because if I don't do so, the caller that submitted the task doesn't detect there was an issue in the task. Actually, my code is more like:

Future<Long> ret =jvmPool.submit((Callable<Long> & Serializable) () -> {
            try {
                return SomeCppFunctionCall();
            } catch(Exception e) {
                JavaProcess.exit();
                throw e;
            }
});

try {
     return ret.get();      
} catch (ExecutionException e) {
    HandleCommandException(e);
}

That way, I can catch the ExecutionException at caller side. Do you think it is the right way to do it ?

thank you again !

ViktorC commented 5 years ago

Hi,

Yeah, that all looks good to me. Any exception thrown from the task will be wrapped in an ExecutionException and rethrown in Future.get(), so your approach is guaranteed to work. 👍

Kind regards, Viktor