diff --git a/include/RemotePluginClient.h b/include/RemotePluginClient.h index 1e5f9b0b9..8056b3e0e 100644 --- a/include/RemotePluginClient.h +++ b/include/RemotePluginClient.h @@ -27,6 +27,15 @@ #include "RemotePluginBase.h" +#ifndef LMMS_BUILD_WIN32 +# include +# include +# include + +# include +# include +#endif + class RemotePluginClient : public RemotePluginBase { public: @@ -126,6 +135,47 @@ private: fpp_t m_bufferSize; } ; +#ifndef LMMS_BUILD_WIN32 +class PollParentThread +{ +public: + PollParentThread() : + m_stop{false}, + m_thread{ + [this] + { + using namespace std::literals::chrono_literals; + auto lock = std::unique_lock{m_mutex}; + while (!m_cv.wait_for(lock, 500ms, [this] { return m_stop; })) + { + if (getppid() == 1) + { + kill(getpid(), SIGHUP); + break; + } + } + } + } + { } + + ~PollParentThread() + { + { + const auto lock = std::unique_lock{m_mutex}; + m_stop = true; + } + m_cv.notify_all(); + m_thread.join(); + } + +private: + bool m_stop; + std::mutex m_mutex; + std::condition_variable m_cv; + std::thread m_thread; +}; +#endif + #ifdef SYNC_WITH_SHM_FIFO RemotePluginClient::RemotePluginClient( key_t _shm_in, key_t _shm_out ) : RemotePluginBase( new shmFifo( _shm_in ), new shmFifo( _shm_out ) ), diff --git a/plugins/VstBase/RemoteVstPlugin.cpp b/plugins/VstBase/RemoteVstPlugin.cpp index abd8408fd..21da830cc 100644 --- a/plugins/VstBase/RemoteVstPlugin.cpp +++ b/plugins/VstBase/RemoteVstPlugin.cpp @@ -2473,6 +2473,11 @@ int main( int _argc, char * * _argv ) fprintf( stderr, "not enough arguments\n" ); return -1; } + +#ifndef LMMS_BUILD_WIN32 + const auto pollParentThread = PollParentThread{}; +#endif + #ifndef NATIVE_LINUX_VST OleInitialize(nullptr); #else diff --git a/plugins/VstBase/RemoteVstPlugin/CMakeLists.txt b/plugins/VstBase/RemoteVstPlugin/CMakeLists.txt index 2f978341d..b4773c4b4 100644 --- a/plugins/VstBase/RemoteVstPlugin/CMakeLists.txt +++ b/plugins/VstBase/RemoteVstPlugin/CMakeLists.txt @@ -35,10 +35,13 @@ add_executable(${EXE_NAME} WIN32 ${LMMS_COMMON_SRCS} ) if(IS_WIN) - target_link_libraries(${EXE_NAME} ole32) + target_link_libraries(${EXE_NAME} ole32) else() - target_link_libraries(${EXE_NAME} pthread dl X11) -ENDIF() + target_link_libraries(${EXE_NAME} dl X11) +endif() +if(NOT WIN32) + target_link_libraries(${EXE_NAME} pthread) +endif() target_include_directories(${EXE_NAME} PRIVATE diff --git a/plugins/ZynAddSubFx/RemoteZynAddSubFx.cpp b/plugins/ZynAddSubFx/RemoteZynAddSubFx.cpp index 018b76a1b..25b23e851 100644 --- a/plugins/ZynAddSubFx/RemoteZynAddSubFx.cpp +++ b/plugins/ZynAddSubFx/RemoteZynAddSubFx.cpp @@ -267,6 +267,10 @@ int main( int _argc, char * * _argv ) return -1; } +#ifndef LMMS_BUILD_WIN32 + const auto pollParentThread = PollParentThread{}; +#endif + #ifdef LMMS_BUILD_WIN32 #ifndef __WINPTHREADS_VERSION // (non-portable) initialization of statically linked pthread library diff --git a/src/core/RemotePlugin.cpp b/src/core/RemotePlugin.cpp index ee6dc0027..4b5056381 100644 --- a/src/core/RemotePlugin.cpp +++ b/src/core/RemotePlugin.cpp @@ -29,6 +29,10 @@ #include #endif +#ifdef LMMS_BUILD_WIN32 +#include +#endif + #include "BufferManager.h" #include "AudioEngine.h" #include "Engine.h" @@ -43,6 +47,29 @@ #include #endif +#ifdef LMMS_BUILD_WIN32 + +namespace { + +HANDLE getRemotePluginJob() +{ + static const auto job = [] + { + const auto job = CreateJobObject(nullptr, nullptr); + + auto limitInfo = JOBOBJECT_EXTENDED_LIMIT_INFORMATION{}; + limitInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + SetInformationJobObject(job, JobObjectExtendedLimitInformation, &limitInfo, sizeof(limitInfo)); + + return job; + }(); + + return job; +} + +} // namespace + +#endif // LMMS_BUILD_WIN32 // simple helper thread monitoring our RemotePlugin - if process terminates // unexpectedly invalidate plugin so LMMS doesn't lock up @@ -56,18 +83,41 @@ ProcessWatcher::ProcessWatcher( RemotePlugin * _p ) : void ProcessWatcher::run() { - m_plugin->m_process.start( m_plugin->m_exec, m_plugin->m_args ); - exec(); - m_plugin->m_process.moveToThread( m_plugin->thread() ); - while( !m_quit && m_plugin->messagesLeft() ) - { - msleep( 200 ); - } - if( !m_quit ) - { - fprintf( stderr, - "remote plugin died! invalidating now.\n" ); + auto& process = m_plugin->m_process; + process.start(m_plugin->m_exec, m_plugin->m_args); +#ifdef LMMS_BUILD_WIN32 + // Add the process to our job so it is killed if we crash + if (process.waitForStarted(-1)) + { + if (const auto processHandle = OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, false, process.processId())) + { + // Ensure the process is still running, otherwise the handle we + // obtained may be for a different process that happened to reuse + // the same process id. + // QProcess::state() alone is insufficient as it only returns a + // cached state variable that is updated asynchronously. To query + // the process itself, we can use QProcess::waitForFinished() with a + // zero timeout, but that too is insufficient as it fails if the + // process has already finished. Therefore, we check both. + if (!process.waitForFinished(0) && process.state() == QProcess::Running) + { + AssignProcessToJobObject(getRemotePluginJob(), processHandle); + } + CloseHandle(processHandle); + } + } +#endif // LMMS_BUILD_WIN32 + + exec(); + process.moveToThread(m_plugin->thread()); + while (!m_quit && m_plugin->messagesLeft()) + { + msleep(200); + } + if (!m_quit) + { + fprintf(stderr, "remote plugin died! invalidating now.\n"); m_plugin->invalidate(); } }