diff --git a/Source/Thunder/PluginServer.h b/Source/Thunder/PluginServer.h index 709da94e95..059e4af815 100644 --- a/Source/Thunder/PluginServer.h +++ b/Source/Thunder/PluginServer.h @@ -1686,6 +1686,15 @@ namespace PluginHost { } _composit.ReleaseInterfaces(); + _handler = nullptr; + + _pluginHandling.Unlock(); + + if (currentIF != nullptr) { + _external.SetInterface(nullptr); + currentIF->Release(); + } + if (_connection != nullptr) { // Lets record the ID associated with this connection. // If the other end of this connection (indicated by the @@ -1697,15 +1706,6 @@ namespace PluginHost { _connection = nullptr; } - _handler = nullptr; - - _pluginHandling.Unlock(); - - if (currentIF != nullptr) { - _external.SetInterface(nullptr); - currentIF->Release(); - } - if (_library.IsLoaded() == true) { Core::ServiceAdministrator::Instance().ReleaseLibrary(std::move(_library)); } diff --git a/Source/ThunderPlugin/Process.cpp b/Source/ThunderPlugin/Process.cpp index a7ed3c9ac1..529bcb147d 100644 --- a/Source/ThunderPlugin/Process.cpp +++ b/Source/ThunderPlugin/Process.cpp @@ -81,9 +81,11 @@ POP_WARNING() else { TRACE_L1("All living objects are killed. Time for HaraKiri!!."); - // Seems there is no more live here, time to signal the - // WorkerPool to quit running and close down... - _parent.Stop(); + if (_parent.HostsPluginInterface() == false) { + // IPlugin interface is released by the host, which + // terminates this process after the final proxy release completes + _parent.Stop(); + } } } void Destructed() override { @@ -111,10 +113,11 @@ POP_WARNING() WorkerPoolImplementation& operator=(const WorkerPoolImplementation&) = delete; PUSH_WARNING(DISABLE_WARNING_THIS_IN_MEMBER_INITIALIZER_LIST) - WorkerPoolImplementation(const uint8_t threads, const uint32_t stackSize, const uint32_t queueSize, const string& callsign, const uint8_t additionalThreads = 1) + WorkerPoolImplementation(const uint8_t threads, const uint32_t stackSize, const uint32_t queueSize, const string& callsign, const uint32_t interfaceId, const uint8_t additionalThreads = 1) : WorkerPool((threads - additionalThreads), stackSize, queueSize, &_dispatcher, this, (threads > 2 ? (threads - 1) : threads), (threads > 2 ? (threads - 1) : threads), additionalThreads) , _dispatcher(callsign) , _sink(*this) + , _interfaceId(interfaceId) { Core::ServiceAdministrator::Instance().Callback(&_sink); @@ -147,6 +150,10 @@ POP_WARNING() { Core::WorkerPool::Stop(); } + bool HostsPluginInterface() const + { + return (_interfaceId == PluginHost::IPlugin::ID); + } void Idle() override { // If we handled all pending requests, it is safe to "unload" @@ -165,6 +172,7 @@ POP_WARNING() private: Dispatcher _dispatcher; Sink _sink; + const uint32_t _interfaceId; }; class ConsoleOptions : public Core::Options { @@ -503,10 +511,10 @@ class ProcessFlow { _lock.Unlock(); } - void Startup(const uint8_t threadCount, const Core::NodeId& remoteNode, const string& callsign) + void Startup(const uint8_t threadCount, const Core::NodeId& remoteNode, const string& callsign, const uint32_t interfaceId) { // Seems like we have enough information, open up the Process communcication Channel. - _engine = Core::ProxyType::Create(threadCount, Core::Thread::DefaultStackSize(), 16, callsign); + _engine = Core::ProxyType::Create(threadCount, Core::Thread::DefaultStackSize(), 16, callsign, interfaceId); // Whenever someone is looking for a WorkerPool, here it is, register it.. Core::IWorkerPool::Assign(&(*_engine)); @@ -709,7 +717,7 @@ int main(int argc, char** argv) Core::ProcessCurrent().User(string(options.User)); } - process.Startup(options.Threads, remoteNode, callsign); + process.Startup(options.Threads, remoteNode, callsign, options.InterfaceId); // Register an interface to handle incoming requests for interfaces. if ((base = Process::AcquireInterfaces(options)) != nullptr) { diff --git a/docs/plugin/execution-modes/outofprocess.md b/docs/plugin/execution-modes/outofprocess.md index dde4830e1a..9b756c7419 100644 --- a/docs/plugin/execution-modes/outofprocess.md +++ b/docs/plugin/execution-modes/outofprocess.md @@ -7,6 +7,29 @@ When the plugin is activated, Thunder will automatically spawn a ThunderPlugin i !!! tip For larger, more complex out-of-process plugins, it is often useful to split a plugin into two separate libraries - see [here](../split-implementation) for more details. +## Whole plugin OOP + +It is also possible to run the full plugin out of process. This allows a plugin to be hosted by `ThunderPlugin` even if it was not split or specifically designed for the traditional OOP plugin model. In this mode, `PluginHost::IPlugin` itself is hosted by `ThunderPlugin`. + +This is mainly useful as a development and debugging option. For example, it can be used to isolate leaks,crashes, or shutdown behavior in plugins that were originally designed to run in process. + +To enable this mode, add a top-level `root` section to the plugin configuration: + +```json +{ + "locator": "libThunderSamplePlugin.so", + "classname": "SamplePlugin", + "startmode": "Activated", + "root": { + "mode": "Local" + }, + "configuration": { + } +} +``` + +Other root modes may also work, but `Local` is the mode covered by this documentation. + **Advantages** * Reliability - if a plugin crashes, it will only bring down the ThunderPlugin instance and therefore not affect any other plugin. It can then be restarted as necessary