diff --git a/jpype/_core.py b/jpype/_core.py index e0e70f6db..955507d44 100644 --- a/jpype/_core.py +++ b/jpype/_core.py @@ -96,6 +96,10 @@ def decorate(func): def isJVMStarted(): + """ This method is horribly named. It should be named isJVMRunning as + isJVMStarted would seem to imply that the JVM was started at some + point without regard to whether it has been shutdown. + """ return _jpype.isStarted() @@ -327,9 +331,12 @@ def shutdownJVM(): restart the JVM after being terminated. """ import threading + import jpype.config if threading.current_thread() is not threading.main_thread(): raise RuntimeError("Shutdown must be called from main thread") - _jpype.shutdown() + if _jpype.isStarted(): + _jpype.JPypeContext.freeResources = jpype.config.free_resources + _jpype.shutdown(jpype.config.destroy_jvm, jpype.config.free_jvm) # In order to shutdown cleanly we need the reference queue stopped @@ -337,7 +344,12 @@ def shutdownJVM(): # for the GIL. def _JTerminate(): try: - _jpype.shutdown() + import jpype.config + # We are exiting anyway so no need to free resources + if _jpype.isStarted(): + _jpype.JPypeContext.freeResources = False + if jpype.config.onexit: + _jpype.shutdown(jpype.config.destroy_jvm, False) except RuntimeError: pass diff --git a/jpype/config.py b/jpype/config.py new file mode 100644 index 000000000..3dc4aba83 --- /dev/null +++ b/jpype/config.py @@ -0,0 +1,44 @@ +# ***************************************************************************** +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# See NOTICE file for details. +# +# ***************************************************************************** + +# This files holds hints used to control behavior of the JVM +# It is global variables rather than options to a function so that +# they can be set a module at any time rather than requiring arguments +# for a function. +# +# These options are to be treated as hints which may or may not be supported +# in future versions. Setting an unsupported option shall have no effect. +# Variables shall not be removed even if deprecated so that conditions +# based on these will be valid in all future versions. +# + +onexit = True +"""If this is False, the JVM not will be notified of Python exit. Java will not execute any +resource cleanup routines.""" + +destroy_jvm = True +""" If this is False, the JVM will not execute any cleanup routines and memory will +not be freed.""" + +free_resources = True +""" If this is False, the resources will be allowed to leak after the shutdown call. +""" + +free_jvm = True +""" If this is False, the shared library will not be unloaded which leaks memory. +""" diff --git a/native/common/include/jp_context.h b/native/common/include/jp_context.h index 1c532403d..c5a3134dd 100644 --- a/native/common/include/jp_context.h +++ b/native/common/include/jp_context.h @@ -127,7 +127,7 @@ class JPContext bool ignoreUnrecognized, bool convertStrings, bool interrupt); void attachJVM(JNIEnv* env); void initializeResources(JNIEnv* env, bool interrupt); - void shutdownJVM(); + void shutdownJVM(bool destroyJVM, bool freeJVM); void attachCurrentThread(); void attachCurrentThreadAsDaemon(); bool isThreadAttached(); diff --git a/native/common/jp_context.cpp b/native/common/jp_context.cpp index 623f876b3..62e3eafea 100644 --- a/native/common/jp_context.cpp +++ b/native/common/jp_context.cpp @@ -374,7 +374,7 @@ void JPContext::onShutdown() m_Running = false; } -void JPContext::shutdownJVM() +void JPContext::shutdownJVM(bool destroyJVM, bool freeJVM) { JP_TRACE_IN("JPContext::shutdown"); if (m_JavaVM == NULL) @@ -383,12 +383,21 @@ void JPContext::shutdownJVM() // JP_RAISE(PyExc_RuntimeError, "Cannot shutdown from embedded Python"); // Wait for all non-demon threads to terminate - JP_TRACE("Destroy JVM"); + if (destroyJVM) { + JP_TRACE("Destroy JVM"); JPPyCallRelease call; m_JavaVM->DestroyJavaVM(); } + // unload the jvm library + if (freeJVM) + { + JP_TRACE("Unload JVM"); + m_JavaVM = NULL; + JPPlatformAdapter::getAdapter()->unloadLibrary(); + } + JP_TRACE("Delete resources"); for (std::list::iterator iter = m_Resources.begin(); iter != m_Resources.end(); ++iter) @@ -397,10 +406,6 @@ void JPContext::shutdownJVM() } m_Resources.clear(); - // unload the jvm library - JP_TRACE("Unload JVM"); - m_JavaVM = NULL; - JPPlatformAdapter::getAdapter()->unloadLibrary(); JP_TRACE_OUT; } diff --git a/native/java/org/jpype/JPypeContext.java b/native/java/org/jpype/JPypeContext.java index 47d1d67c1..7be18df97 100644 --- a/native/java/org/jpype/JPypeContext.java +++ b/native/java/org/jpype/JPypeContext.java @@ -84,6 +84,7 @@ public class JPypeContext private final AtomicInteger shutdownFlag = new AtomicInteger(); private final List shutdownHooks = new ArrayList<>(); private final List postHooks = new ArrayList<>(); + public static boolean freeResources = true; static public JPypeContext getInstance() { @@ -241,20 +242,23 @@ private void shutdown() { } - // Release all Python references - try - { - JPypeReferenceQueue.getInstance().stop(); - } catch (Throwable th) - { - } - - // Release any C++ resources - try - { - this.typeManager.shutdown(); - } catch (Throwable th) + if (freeResources) { + // Release all Python references + try + { + JPypeReferenceQueue.getInstance().stop(); + } catch (Throwable th) + { + } + + // Release any C++ resources + try + { + this.typeManager.shutdown(); + } catch (Throwable th) + { + } } // Execute post hooks diff --git a/native/python/pyjp_module.cpp b/native/python/pyjp_module.cpp index e22bf9b6b..a95a7662a 100644 --- a/native/python/pyjp_module.cpp +++ b/native/python/pyjp_module.cpp @@ -277,10 +277,16 @@ static PyObject* PyJPModule_startup(PyObject* module, PyObject* pyargs) JP_PY_CATCH(NULL); } -static PyObject* PyJPModule_shutdown(PyObject* obj) +static PyObject* PyJPModule_shutdown(PyObject* obj, PyObject* pyargs, PyObject* kwargs) { JP_PY_TRY("PyJPModule_shutdown"); - JPContext_global->shutdownJVM(); + char destroyJVM = true; + char freeJVM = true; + + if (!PyArg_ParseTuple(pyargs, "bb", &destroyJVM, &freeJVM)) + return NULL; + + JPContext_global->shutdownJVM(destroyJVM, freeJVM); Py_RETURN_NONE; JP_PY_CATCH(NULL); } @@ -660,7 +666,7 @@ static PyMethodDef moduleMethods[] = { #else {"startup", (PyCFunction) PyJPModule_startup, METH_VARARGS, ""}, // {"attach", (PyCFunction) (&PyJPModule_attach), METH_VARARGS, ""}, - {"shutdown", (PyCFunction) PyJPModule_shutdown, METH_NOARGS, ""}, + {"shutdown", (PyCFunction) PyJPModule_shutdown, METH_VARARGS, ""}, #endif {"_getClass", (PyCFunction) PyJPModule_getClass, METH_O, ""}, {"_hasClass", (PyCFunction) PyJPModule_hasClass, METH_O, ""},