mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
300 lines
8 KiB
C++
300 lines
8 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE 6 technical preview.
|
|
Copyright (c) 2020 - Raw Material Software Limited
|
|
|
|
You may use this code under the terms of the GPL v3
|
|
(see www.gnu.org/licenses).
|
|
|
|
For this technical preview, this file is not subject to commercial licensing.
|
|
|
|
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
|
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
|
DISCLAIMED.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
#include "../Application/jucer_Headers.h"
|
|
#include "../Application/jucer_Application.h"
|
|
#include "jucer_CompileEngineServer.h"
|
|
#include "jucer_CompileEngineDLL.h"
|
|
#include "jucer_MessageIDs.h"
|
|
#include "jucer_CppHelpers.h"
|
|
#include "jucer_SourceCodeRange.h"
|
|
#include "jucer_ClassDatabase.h"
|
|
#include "jucer_DiagnosticMessage.h"
|
|
#include "jucer_ProjectBuildInfo.h"
|
|
#include "jucer_ClientServerMessages.h"
|
|
|
|
#if JUCE_LINUX
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#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();
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
//==============================================================================
|
|
/** Detects whether this process has hung, and kills it if so. */
|
|
struct ZombiePatrol : private Thread,
|
|
private AsyncUpdater,
|
|
private Timer
|
|
{
|
|
explicit ZombiePatrol (MessageHandler& mh)
|
|
: Thread ("Ping"), owner (mh)
|
|
{
|
|
startThread (2);
|
|
startTimer (1000);
|
|
}
|
|
|
|
~ZombiePatrol() override
|
|
{
|
|
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:
|
|
explicit 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.reset (new ZombiePatrol (*this));
|
|
}
|
|
|
|
~ServerIPC() override
|
|
{
|
|
zombieKiller.reset();
|
|
|
|
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<InterprocessConnection*> (static_cast<ServerIPC*> (userInfo))
|
|
->sendMessage (MemoryBlock (data, dataSize));
|
|
}
|
|
|
|
CompileEngineDLL dll;
|
|
LiveCodeBuilder liveCodeBuilder;
|
|
std::unique_ptr<ZombiePatrol> 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, "");
|
|
|
|
std::unique_ptr<ServerIPC> 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<ServerIPC*> (server);
|
|
}
|
|
|
|
void sendQuitMessageToIDE (void* server)
|
|
{
|
|
static_cast<ServerIPC*> (server)->sendQuitMessageToIDE();
|
|
}
|
|
|
|
//==============================================================================
|
|
#if JUCE_WINDOWS
|
|
#define STRICT 1
|
|
#define WIN32_LEAN_AND_MEAN 1
|
|
#include <windows.h>
|
|
|
|
static HANDLE parentProcessHandle = nullptr;
|
|
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
|