officecfg,stoc: allow running JVM UNO components out-of-process

The problem is that 32-bit Win32 applications have very little VM, and
soffice.bin can run out, so try to move the JVM to a separate process
(uno.bin) and connect to it via pipe.

Add a new config to enable this:
"org.openoffice.Office.Java/VirtualMachine/RunUnoComponentsOutOfProcess"

If enabled, ServiceManager instantiates *all* JVM components
out-of-process, by instantiating
"com.sun.star.java.theJavaVirtualMachine" out-of-process.

To ensure that the remote connection is disconnected at shutdown (and
thereby prevent crashes with remote calls during late shutdown),
JavaComponentLoader is now a "single-instance" service; this change
should be harmless for the default in-process configuration case.

Tested with these extensions:
  Wiki Publisher
  smoketest TestExtension.oxt
  odk CalcAddins.oxt Inspector.oxt ToDo.oxt

Also passed "make check" on Linux when enabled, if the variable
URE_BIN_DIR is set properly for CppunitTest_services.

Change-Id: I76bf17a9512414b67dbd20daee25a6d29c05f9d9
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/133218
Tested-by: Jenkins
Reviewed-by: Michael Stahl <michael.stahl@allotropia.de>
This commit is contained in:
Michael Stahl 2022-05-11 12:07:06 +02:00
parent c63b865018
commit 732fdafd9f
5 changed files with 245 additions and 14 deletions

View File

@ -456,6 +456,7 @@ certain functionality.
@section stoc @section stoc
@li @c stoc.corerefl - CoreReflection @li @c stoc.corerefl - CoreReflection
@li @c stoc.java - javaloader and javavm
@section VCL @section VCL

View File

@ -104,6 +104,13 @@
<desc>Specifies properties for use with the Java VM.</desc> <desc>Specifies properties for use with the Java VM.</desc>
</info> </info>
</prop> </prop>
<prop oor:name="RunUnoComponentsOutOfProcess" oor:type="xs:boolean" oor:nillable="false">
<info>
<desc>Specifies whether JVM based UNO components are run via uno command outside the LibreOffice process.</desc>
<label>Run UNO components out-of-process</label>
</info>
<value>false</value>
</prop>
</group> </group>
</component> </component>
</oor:component-schema> </oor:component-schema>

View File

@ -93,7 +93,7 @@ $(if $(URE),\
$(if $(strip $(UNO_TYPES)),\ $(if $(strip $(UNO_TYPES)),\
"-env:UNO_TYPES=$(foreach item,$(UNO_TYPES),$(call gb_Helper_make_url,$(item)))") \ "-env:UNO_TYPES=$(foreach item,$(UNO_TYPES),$(call gb_Helper_make_url,$(item)))") \
$(if $(strip $(UNO_SERVICES)),\ $(if $(strip $(UNO_SERVICES)),\
"-env:UNO_SERVICES=$(foreach item,$(UNO_SERVICES),$(call gb_Helper_make_url,$(item)))") \ "-env:UNO_SERVICES=$(foreach item,$(UNO_SERVICES),$(call gb_Helper_make_url,$(item)))" -env:URE_BIN_DIR=$(call gb_Helper_make_url,$(INSTROOT)/$(LIBO_URE_BIN_FOLDER))) \
-env:URE_INTERNAL_LIB_DIR=$(call gb_Helper_make_url,$(INSTROOT)/$(LIBO_URE_LIB_FOLDER)) \ -env:URE_INTERNAL_LIB_DIR=$(call gb_Helper_make_url,$(INSTROOT)/$(LIBO_URE_LIB_FOLDER)) \
-env:LO_LIB_DIR=$(call gb_Helper_make_url,$(INSTROOT)/$(LIBO_LIB_FOLDER)) \ -env:LO_LIB_DIR=$(call gb_Helper_make_url,$(INSTROOT)/$(LIBO_LIB_FOLDER)) \
-env:LO_JAVA_DIR=$(call gb_Helper_make_url,$(INSTROOT)/$(LIBO_SHARE_JAVA_FOLDER)) \ -env:LO_JAVA_DIR=$(call gb_Helper_make_url,$(INSTROOT)/$(LIBO_SHARE_JAVA_FOLDER)) \

View File

@ -20,7 +20,8 @@
<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" <component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@"
xmlns="http://openoffice.org/2010/uno-components"> xmlns="http://openoffice.org/2010/uno-components">
<implementation name="com.sun.star.comp.stoc.JavaComponentLoader" <implementation name="com.sun.star.comp.stoc.JavaComponentLoader"
constructor="stoc_JavaComponentLoader_get_implementation"> constructor="stoc_JavaComponentLoader_get_implementation"
single-instance="true">
<service name="com.sun.star.loader.Java"/> <service name="com.sun.star.loader.Java"/>
<service name="com.sun.star.loader.Java2"/> <service name="com.sun.star.loader.Java2"/>
</implementation> </implementation>

View File

@ -43,20 +43,31 @@
#pragma clang diagnostic pop #pragma clang diagnostic pop
#endif #endif
#include <rtl/random.h>
#include <rtl/ustrbuf.hxx>
#include <osl/security.hxx>
#include <osl/thread.hxx>
#include <cppuhelper/factory.hxx> #include <cppuhelper/factory.hxx>
#include <cppuhelper/implbase.hxx> #include <cppuhelper/basemutex.hxx>
#include <cppuhelper/compbase.hxx>
#include <cppuhelper/supportsservice.hxx> #include <cppuhelper/supportsservice.hxx>
#include <com/sun/star/bridge/UnoUrlResolver.hpp>
#include <com/sun/star/container/XHierarchicalNameAccess.hpp>
#include <com/sun/star/loader/XImplementationLoader.hpp> #include <com/sun/star/loader/XImplementationLoader.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp> #include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/lang/XInitialization.hpp> #include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/uno/XComponentContext.hpp> #include <com/sun/star/uno/XComponentContext.hpp>
#include <com/sun/star/util/theMacroExpander.hpp>
#include <jvmaccess/unovirtualmachine.hxx> #include <jvmaccess/unovirtualmachine.hxx>
#include <jvmaccess/virtualmachine.hxx> #include <jvmaccess/virtualmachine.hxx>
// this one is header-only
#include <comphelper/sequence.hxx>
namespace com::sun::star::registry { class XRegistryKey; } namespace com::sun::star::registry { class XRegistryKey; }
using namespace css::java; using namespace css::java;
@ -72,10 +83,167 @@ namespace stoc_javaloader {
namespace { namespace {
class JavaComponentLoader : public WeakImplHelper<XImplementationLoader, XServiceInfo> // from desktop/source/deployment/misc/dp_misc.cxx
OUString generateRandomPipeId()
{ {
// compute some good pipe id:
static rtlRandomPool s_hPool = rtl_random_createPool();
if (s_hPool == nullptr)
throw RuntimeException( "cannot create random pool!?", nullptr );
sal_uInt8 bytes[ 32 ];
if (rtl_random_getBytes(
s_hPool, bytes, SAL_N_ELEMENTS(bytes) ) != rtl_Random_E_None) {
throw RuntimeException( "random pool error!?", nullptr );
}
OUStringBuffer buf;
for (unsigned char byte : bytes) {
buf.append( static_cast<sal_Int32>(byte), 0x10 );
}
return buf.makeStringAndClear();
}
// from desktop/source/deployment/registry/component/dp_component.cxx
/** return a vector of bootstrap variables which have been provided
as command arguments.
*/
std::vector<OUString> getCmdBootstrapVariables()
{
std::vector<OUString> ret;
sal_uInt32 count = osl_getCommandArgCount();
for (sal_uInt32 i = 0; i < count; i++)
{
OUString arg;
osl_getCommandArg(i, &arg.pData);
if (arg.startsWith("-env:"))
ret.push_back(arg);
}
return ret;
}
// from desktop/source/deployment/misc/dp_misc.cxx
oslProcess raiseProcess(
OUString const & appURL, Sequence<OUString> const & args )
{
::osl::Security sec;
oslProcess hProcess = nullptr;
oslProcessError rc = osl_executeProcess(
appURL.pData,
reinterpret_cast<rtl_uString **>(
const_cast<OUString *>(args.getConstArray()) ),
args.getLength(),
osl_Process_DETACHED,
sec.getHandle(),
nullptr, // => current working dir
nullptr, 0, // => no env vars
&hProcess );
switch (rc) {
case osl_Process_E_None:
break;
case osl_Process_E_NotFound:
throw RuntimeException( "image not found!", nullptr );
case osl_Process_E_TimedOut:
throw RuntimeException( "timeout occurred!", nullptr );
case osl_Process_E_NoPermission:
throw RuntimeException( "permission denied!", nullptr );
case osl_Process_E_Unknown:
throw RuntimeException( "unknown error!", nullptr );
case osl_Process_E_InvalidError:
default:
throw RuntimeException( "unmapped error!", nullptr );
}
return hProcess;
}
// from desktop/source/deployment/registry/component/dp_component.cxx
Reference<XComponentContext> raise_uno_process(
Reference<XComponentContext> const & xContext)
{
OSL_ASSERT( xContext.is() );
OUString const url(css::util::theMacroExpander::get(xContext)->expandMacros("$URE_BIN_DIR/uno"));
const OUString connectStr = "uno:pipe,name=" + generateRandomPipeId() + ";urp;uno.ComponentContext";
// raise core UNO process to register/run a component,
// javavm service uses unorc next to executable to retrieve deployed
// jar typelibs
std::vector<OUString> args{
#if OSL_DEBUG_LEVEL == 0
"--quiet",
#endif
"--singleaccept",
"-u",
connectStr,
// don't inherit from unorc:
"-env:INIFILENAME=" };
//now add the bootstrap variables which were supplied on the command line
std::vector<OUString> bootvars = getCmdBootstrapVariables();
args.insert(args.end(), bootvars.begin(), bootvars.end());
oslProcess hProcess;
try {
hProcess = raiseProcess(url, comphelper::containerToSequence(args));
}
catch (...) {
OUStringBuffer sMsg = "error starting process: " + url;
for (const auto& arg : args) {
sMsg.append(" " + arg);
}
throw css::uno::RuntimeException(sMsg.makeStringAndClear());
}
try {
// from desktop/source/deployment/misc/dp_misc.cxx
Reference<css::bridge::XUnoUrlResolver> const xUnoUrlResolver(
css::bridge::UnoUrlResolver::create(xContext) );
for (int i = 0; i <= 40; ++i) // 20 seconds
{
try {
return Reference<XComponentContext>(
xUnoUrlResolver->resolve(connectStr),
UNO_QUERY_THROW );
}
catch (const css::connection::NoConnectException &) {
if (i < 40) {
::osl::Thread::wait( std::chrono::milliseconds(500) );
}
else throw;
}
}
return nullptr; // warning C4715
}
catch (...) {
// try to terminate process:
if ( osl_terminateProcess( hProcess ) != osl_Process_E_None )
{
OSL_ASSERT( false );
}
throw;
}
}
class JavaComponentLoader
: protected ::cppu::BaseMutex
, public WeakComponentImplHelper<XImplementationLoader, XServiceInfo>
{
/** local context */
css::uno::Reference<XComponentContext> m_xComponentContext; css::uno::Reference<XComponentContext> m_xComponentContext;
/** possible remote process' context (use depends on configuration).
note: lifetime must be effectively "static" as this JavaComponentLoader
has no control over the lifetime of the services created via this
context; hence JavaComponentLoader is a single-instance service.
*/
css::uno::Reference<XComponentContext> m_xRemoteComponentContext;
/** Do not use m_javaLoader directly. Instead use getJavaLoader. /** Do not use m_javaLoader directly. Instead use getJavaLoader.
This is either an in-process loader implemented in Java,
or a remote instance of JavaComponentLoader running in uno process,
acting as a proxy.
*/ */
css::uno::Reference<XImplementationLoader> m_javaLoader; css::uno::Reference<XImplementationLoader> m_javaLoader;
/** The returned Reference contains a null pointer if the office is not configured /** The returned Reference contains a null pointer if the office is not configured
@ -85,7 +253,7 @@ class JavaComponentLoader : public WeakImplHelper<XImplementationLoader, XServic
If the Java implementation of the loader could not be obtained, for reasons other If the Java implementation of the loader could not be obtained, for reasons other
then that java was not configured the RuntimeException is thrown. then that java was not configured the RuntimeException is thrown.
*/ */
const css::uno::Reference<XImplementationLoader> & getJavaLoader(); const css::uno::Reference<XImplementationLoader> & getJavaLoader(OUString &);
public: public:
@ -98,6 +266,8 @@ public:
virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override;
virtual Sequence<OUString> SAL_CALL getSupportedServiceNames() override; virtual Sequence<OUString> SAL_CALL getSupportedServiceNames() override;
virtual void SAL_CALL disposing() override;
// XImplementationLoader // XImplementationLoader
virtual css::uno::Reference<XInterface> SAL_CALL activate( virtual css::uno::Reference<XInterface> SAL_CALL activate(
const OUString& implementationName, const OUString& implementationLoaderUrl, const OUString& implementationName, const OUString& implementationLoaderUrl,
@ -109,7 +279,21 @@ public:
} }
const css::uno::Reference<XImplementationLoader> & JavaComponentLoader::getJavaLoader() void JavaComponentLoader::disposing()
{
// Explicitly drop all remote refs to shut down the uno.bin process
// and particularly the connection to it, so that it can't do more calls
// during late shutdown.
m_javaLoader.clear();
if (m_xRemoteComponentContext.is()) {
Reference<XComponent> const xComp(m_xRemoteComponentContext, UNO_QUERY);
assert(xComp.is());
xComp->dispose();
m_xRemoteComponentContext.clear();
}
}
const css::uno::Reference<XImplementationLoader> & JavaComponentLoader::getJavaLoader(OUString & rRemoteArg)
{ {
static Mutex ourMutex; static Mutex ourMutex;
MutexGuard aGuard(ourMutex); MutexGuard aGuard(ourMutex);
@ -117,6 +301,42 @@ const css::uno::Reference<XImplementationLoader> & JavaComponentLoader::getJavaL
if (m_javaLoader.is()) if (m_javaLoader.is())
return m_javaLoader; return m_javaLoader;
// check if the JVM should be instantiated out-of-process
if (rRemoteArg.isEmpty()) {
if (!m_xRemoteComponentContext.is()) {
Reference<css::container::XHierarchicalNameAccess> const xConf(
m_xComponentContext->getServiceManager()->createInstanceWithArgumentsAndContext(
"com.sun.star.configuration.ReadOnlyAccess",
{ Any(OUString("*")) }, // locale isn't relevant here
m_xComponentContext),
UNO_QUERY);
// configmgr is not part of URE, so may not exist!
if (xConf.is()) {
Any const value(xConf->getByHierarchicalName(
"org.openoffice.Office.Java/VirtualMachine/RunUnoComponentsOutOfProcess"));
bool b;
if ((value >>= b) && b) {
SAL_INFO("stoc.java", "JavaComponentLoader: starting uno process");
m_xRemoteComponentContext = raise_uno_process(m_xComponentContext);
}
}
}
if (m_xRemoteComponentContext.is()) {
SAL_INFO("stoc.java", "JavaComponentLoader: creating remote instance to start JVM in uno process");
// create JVM service in remote uno.bin process
Reference<XImplementationLoader> const xLoader(
m_xRemoteComponentContext->getServiceManager()->createInstanceWithContext(
"com.sun.star.loader.Java2", m_xRemoteComponentContext),
UNO_QUERY_THROW);
assert(xLoader.is());
m_javaLoader = xLoader;
rRemoteArg = "remote";
SAL_INFO("stoc.java", "JavaComponentLoader: remote proxy instance created: " << m_javaLoader.get());
return m_javaLoader;
}
}
uno_Environment * pJava_environment = nullptr; uno_Environment * pJava_environment = nullptr;
uno_Environment * pUno_environment = nullptr; uno_Environment * pUno_environment = nullptr;
typelib_InterfaceTypeDescription * pType_XImplementationLoader = nullptr; typelib_InterfaceTypeDescription * pType_XImplementationLoader = nullptr;
@ -275,9 +495,9 @@ const css::uno::Reference<XImplementationLoader> & JavaComponentLoader::getJavaL
return m_javaLoader; return m_javaLoader;
} }
JavaComponentLoader::JavaComponentLoader(const css::uno::Reference<XComponentContext> & xCtx) : JavaComponentLoader::JavaComponentLoader(const css::uno::Reference<XComponentContext> & xCtx)
m_xComponentContext(xCtx) : WeakComponentImplHelper(m_aMutex)
, m_xComponentContext(xCtx)
{ {
} }
@ -304,27 +524,29 @@ sal_Bool SAL_CALL JavaComponentLoader::writeRegistryInfo(
const css::uno::Reference<XRegistryKey> & xKey, const OUString & blabla, const css::uno::Reference<XRegistryKey> & xKey, const OUString & blabla,
const OUString & rLibName) const OUString & rLibName)
{ {
const css::uno::Reference<XImplementationLoader> & loader = getJavaLoader(); OUString remoteArg(blabla);
const css::uno::Reference<XImplementationLoader> & loader = getJavaLoader(remoteArg);
if (!loader.is()) if (!loader.is())
throw CannotRegisterImplementationException("Could not create Java implementation loader"); throw CannotRegisterImplementationException("Could not create Java implementation loader");
return loader->writeRegistryInfo(xKey, blabla, rLibName); return loader->writeRegistryInfo(xKey, remoteArg, rLibName);
} }
css::uno::Reference<XInterface> SAL_CALL JavaComponentLoader::activate( css::uno::Reference<XInterface> SAL_CALL JavaComponentLoader::activate(
const OUString & rImplName, const OUString & blabla, const OUString & rLibName, const OUString & rImplName, const OUString & blabla, const OUString & rLibName,
const css::uno::Reference<XRegistryKey> & xKey) const css::uno::Reference<XRegistryKey> & xKey)
{ {
OUString remoteArg(blabla);
if (rImplName.isEmpty() && blabla.isEmpty() && rLibName.isEmpty()) if (rImplName.isEmpty() && blabla.isEmpty() && rLibName.isEmpty())
{ {
// preload JVM was requested // preload JVM was requested
(void)getJavaLoader(); (void)getJavaLoader(remoteArg);
return css::uno::Reference<XInterface>(); return css::uno::Reference<XInterface>();
} }
const css::uno::Reference<XImplementationLoader> & loader = getJavaLoader(); const css::uno::Reference<XImplementationLoader> & loader = getJavaLoader(remoteArg);
if (!loader.is()) if (!loader.is())
throw CannotActivateFactoryException("Could not create Java implementation loader"); throw CannotActivateFactoryException("Could not create Java implementation loader");
return loader->activate(rImplName, blabla, rLibName, xKey); return loader->activate(rImplName, remoteArg, rLibName, xKey);
} }
extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*