/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) b) the Affero GPL v3 Details of these licenses can be found at: www.gnu.org/licenses JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.juce.com for more information. ============================================================================== */ #include "../jucer_Headers.h" #include "../Utility/jucer_PresetIDs.h" #include "../Utility/jucer_FileHelpers.h" #include "../Application/jucer_AppearanceSettings.h" #include "../Application/jucer_Application.h" #include "../Utility/jucer_CodeHelpers.h" #include "projucer_CompileEngineDLL.h" #include "projucer_MessageIDs.h" #include "projucer_CppHelpers.h" #include "projucer_SourceCodeRange.h" #include "projucer_ClassDatabase.h" #include "projucer_DiagnosticMessage.h" #include "projucer_ProjectBuildInfo.h" #include "projucer_ClientServerMessages.h" #if JUCE_LINUX #include #include #endif #ifndef RUN_CLANG_IN_CHILD_PROCESS #error #endif #if RUN_CLANG_IN_CHILD_PROCESS static bool parentProcessHasExited(); #endif #if JUCE_WINDOWS static void setParentProcessID (int); static int getCurrentProcessID(); #endif //============================================================================== /** Detects whether this process has hung, and kills it if so. */ struct ZombiePatrol : private Thread, private AsyncUpdater, private Timer { ZombiePatrol (MessageHandler& mh) : Thread ("Ping"), owner (mh) { startThread (2); startTimer (1000); } ~ZombiePatrol() { stopThread (1000); } private: MessageHandler& owner; int failedPings = 0; void run() override { while (! threadShouldExit()) { #if RUN_CLANG_IN_CHILD_PROCESS if (parentProcessHasExited()) { killProcess(); break; } #endif wait (1000); } } void handleAsyncUpdate() override { DBG ("Server: quitting"); stopTimer(); ProjucerApplication::getApp().systemRequestedQuit(); } void timerCallback() override { if (! MessageTypes::sendPing (owner)) { if (++failedPings == 10) { killProcess(); return; } } else { failedPings = 0; } } void killProcess() { triggerAsyncUpdate(); // give the messagequeue a chance to do things cleanly. static UnstoppableKillerThread* k = new UnstoppableKillerThread(); // (allowed to leak, but static so only one is created) ignoreUnused (k); } struct UnstoppableKillerThread : public Thread { UnstoppableKillerThread() : Thread ("Killer") { startThread(); } void run() override { wait (15000); if (! threadShouldExit()) Process::terminate(); } }; }; //============================================================================== class ServerIPC : public InterprocessConnection, public MessageHandler { public: ServerIPC (const StringArray& info) : InterprocessConnection (true), liveCodeBuilder (nullptr) { if (! createPipe (info[0], -1)) { Logger::writeToLog ("*** Couldn't create pipe!"); ProjucerApplication::getApp().systemRequestedQuit(); return; } if (dll.isLoaded()) liveCodeBuilder = dll.projucer_createBuilder (sendMessageCallback, this, info[1].toRawUTF8(), info[2].toRawUTF8()); #if JUCE_WINDOWS setParentProcessID (info[3].getHexValue32()); #endif zombieKiller = new ZombiePatrol (*this); } ~ServerIPC() { zombieKiller = nullptr; if (dll.isLoaded()) dll.projucer_deleteBuilder (liveCodeBuilder); dll.shutdown(); DBG ("Server: finished closing down"); } void connectionMade() override { DBG ("Server: client connected"); } void connectionLost() override { Logger::writeToLog ("Server: client lost"); JUCEApplication::quit(); } void sendQuitMessageToIDE() { MessageTypes::sendShouldCloseIDE (*this); } bool sendMessage (const ValueTree& m) override { return InterprocessConnection::sendMessage (MessageHandler::convertMessage (m)); } void messageReceived (const MemoryBlock& message) override { jassert (dll.isLoaded()); dll.projucer_sendMessage (liveCodeBuilder, message.getData(), message.getSize()); } static bool sendMessageCallback (void* userInfo, const void* data, size_t dataSize) { return static_cast (static_cast (userInfo)) ->sendMessage (MemoryBlock (data, dataSize)); } CompileEngineDLL dll; LiveCodeBuilder liveCodeBuilder; ScopedPointer zombieKiller; }; //============================================================================== const char* commandPrefix = "--server:"; const char* commandTokenSeparator = "\x01"; String createCommandLineForLaunchingServer (const String& pipeName, const String& projectUID, const File& cacheLocation) { StringArray info; info.add (pipeName); info.add (projectUID); info.add (cacheLocation.getFullPathName()); #if JUCE_WINDOWS info.add (String::toHexString (getCurrentProcessID())); #endif const File exe (File::getSpecialLocation (File::currentExecutableFile).getFullPathName()); return "\"" + exe.getFullPathName() + "\" " + commandPrefix + info.joinIntoString (commandTokenSeparator); } static ServerIPC* currentServer = nullptr; static void crashCallback (const char* message) { if (currentServer != nullptr) { #if RUN_CLANG_IN_CHILD_PROCESS MessageTypes::sendCrash (*currentServer, message); Logger::writeToLog ("*** Crashed! " + String (message)); #else ignoreUnused (message); jassertfalse; #endif currentServer->disconnect(); } } static void quitCallback() { ProjucerApplication::getApp().systemRequestedQuit(); } void* createClangServer (const String& commandLine) { StringArray info; info.addTokens (commandLine.fromFirstOccurrenceOf (commandPrefix, false, false), commandTokenSeparator, ""); ScopedPointer ipc = new ServerIPC (info); if (ipc->dll.isLoaded()) { ipc->dll.initialise (crashCallback, quitCallback, (bool) RUN_CLANG_IN_CHILD_PROCESS); currentServer = ipc.release(); return currentServer; } return nullptr; } void destroyClangServer (void* server) { currentServer = nullptr; delete static_cast (server); } void sendQuitMessageToIDE (void* server) { static_cast (server)->sendQuitMessageToIDE(); } //============================================================================== #if JUCE_WINDOWS #define STRICT 1 #define WIN32_LEAN_AND_MEAN 1 #include static HANDLE parentProcessHandle = 0; static void setParentProcessID (int pid) { parentProcessHandle = OpenProcess (SYNCHRONIZE, FALSE, (DWORD) pid); } static int getCurrentProcessID() { return (int) GetCurrentProcessId(); } #endif #if RUN_CLANG_IN_CHILD_PROCESS bool parentProcessHasExited() { #if JUCE_WINDOWS return WaitForSingleObject (parentProcessHandle, 0) == WAIT_OBJECT_0; #else return getppid() == 1; #endif } #endif