Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

265 lines
8.3KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. enum { magicMastSlaveConnectionHeader = 0x712baf04 };
  18. static const char* startMessage = "__ipc_st";
  19. static const char* killMessage = "__ipc_k_";
  20. static const char* pingMessage = "__ipc_p_";
  21. enum { specialMessageSize = 8, defaultTimeoutMs = 8000 };
  22. static String getCommandLinePrefix (const String& commandLineUniqueID)
  23. {
  24. return "--" + commandLineUniqueID + ":";
  25. }
  26. //==============================================================================
  27. // This thread sends and receives ping messages every second, so that it
  28. // can find out if the other process has stopped running.
  29. struct ChildProcessPingThread : public Thread,
  30. private AsyncUpdater
  31. {
  32. ChildProcessPingThread (int timeout) : Thread ("IPC ping"), timeoutMs (timeout)
  33. {
  34. pingReceived();
  35. }
  36. static bool isPingMessage (const MemoryBlock& m) noexcept
  37. {
  38. return memcmp (m.getData(), pingMessage, specialMessageSize) == 0;
  39. }
  40. void pingReceived() noexcept { countdown = timeoutMs / 1000 + 1; }
  41. void triggerConnectionLostMessage() { triggerAsyncUpdate(); }
  42. virtual bool sendPingMessage (const MemoryBlock&) = 0;
  43. virtual void pingFailed() = 0;
  44. int timeoutMs;
  45. private:
  46. Atomic<int> countdown;
  47. void handleAsyncUpdate() override { pingFailed(); }
  48. void run() override
  49. {
  50. while (! threadShouldExit())
  51. {
  52. if (--countdown <= 0 || ! sendPingMessage (MemoryBlock (pingMessage, specialMessageSize)))
  53. {
  54. triggerConnectionLostMessage();
  55. break;
  56. }
  57. wait (1000);
  58. }
  59. }
  60. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessPingThread)
  61. };
  62. //==============================================================================
  63. struct ChildProcessMaster::Connection : public InterprocessConnection,
  64. private ChildProcessPingThread
  65. {
  66. Connection (ChildProcessMaster& m, const String& pipeName, int timeout)
  67. : InterprocessConnection (false, magicMastSlaveConnectionHeader),
  68. ChildProcessPingThread (timeout),
  69. owner (m)
  70. {
  71. if (createPipe (pipeName, timeoutMs))
  72. startThread (4);
  73. }
  74. ~Connection()
  75. {
  76. stopThread (10000);
  77. }
  78. private:
  79. void connectionMade() override {}
  80. void connectionLost() override { owner.handleConnectionLost(); }
  81. bool sendPingMessage (const MemoryBlock& m) override { return owner.sendMessageToSlave (m); }
  82. void pingFailed() override { connectionLost(); }
  83. void messageReceived (const MemoryBlock& m) override
  84. {
  85. pingReceived();
  86. if (m.getSize() != specialMessageSize || ! isPingMessage (m))
  87. owner.handleMessageFromSlave (m);
  88. }
  89. ChildProcessMaster& owner;
  90. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Connection)
  91. };
  92. //==============================================================================
  93. ChildProcessMaster::ChildProcessMaster() {}
  94. ChildProcessMaster::~ChildProcessMaster()
  95. {
  96. if (connection != nullptr)
  97. {
  98. sendMessageToSlave (MemoryBlock (killMessage, specialMessageSize));
  99. connection->disconnect();
  100. connection = nullptr;
  101. }
  102. }
  103. void ChildProcessMaster::handleConnectionLost() {}
  104. bool ChildProcessMaster::sendMessageToSlave (const MemoryBlock& mb)
  105. {
  106. if (connection != nullptr)
  107. return connection->sendMessage (mb);
  108. jassertfalse; // this can only be used when the connection is active!
  109. return false;
  110. }
  111. bool ChildProcessMaster::launchSlaveProcess (const File& executable, const String& commandLineUniqueID, int timeoutMs)
  112. {
  113. connection = nullptr;
  114. jassert (childProcess.kill());
  115. const String pipeName ("p" + String::toHexString (Random().nextInt64()));
  116. StringArray args;
  117. args.add (executable.getFullPathName());
  118. args.add (getCommandLinePrefix (commandLineUniqueID) + pipeName);
  119. if (childProcess.start (args))
  120. {
  121. connection = new Connection (*this, pipeName, timeoutMs <= 0 ? defaultTimeoutMs : timeoutMs);
  122. if (connection->isConnected())
  123. {
  124. sendMessageToSlave (MemoryBlock (startMessage, specialMessageSize));
  125. return true;
  126. }
  127. connection = nullptr;
  128. }
  129. return false;
  130. }
  131. //==============================================================================
  132. struct ChildProcessSlave::Connection : public InterprocessConnection,
  133. private ChildProcessPingThread
  134. {
  135. Connection (ChildProcessSlave& p, const String& pipeName, int timeout)
  136. : InterprocessConnection (false, magicMastSlaveConnectionHeader),
  137. ChildProcessPingThread (timeout),
  138. owner (p)
  139. {
  140. connectToPipe (pipeName, timeoutMs);
  141. startThread (4);
  142. }
  143. ~Connection()
  144. {
  145. stopThread (10000);
  146. }
  147. private:
  148. ChildProcessSlave& owner;
  149. void connectionMade() override {}
  150. void connectionLost() override { owner.handleConnectionLost(); }
  151. bool sendPingMessage (const MemoryBlock& m) override { return owner.sendMessageToMaster (m); }
  152. void pingFailed() override { connectionLost(); }
  153. void messageReceived (const MemoryBlock& m) override
  154. {
  155. pingReceived();
  156. if (m.getSize() == specialMessageSize)
  157. {
  158. if (isPingMessage (m))
  159. return;
  160. if (memcmp (m.getData(), killMessage, specialMessageSize) == 0)
  161. {
  162. triggerConnectionLostMessage();
  163. return;
  164. }
  165. if (memcmp (m.getData(), startMessage, specialMessageSize) == 0)
  166. {
  167. owner.handleConnectionMade();
  168. return;
  169. }
  170. }
  171. owner.handleMessageFromMaster (m);
  172. }
  173. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Connection)
  174. };
  175. //==============================================================================
  176. ChildProcessSlave::ChildProcessSlave() {}
  177. ChildProcessSlave::~ChildProcessSlave() {}
  178. void ChildProcessSlave::handleConnectionMade() {}
  179. void ChildProcessSlave::handleConnectionLost() {}
  180. bool ChildProcessSlave::sendMessageToMaster (const MemoryBlock& mb)
  181. {
  182. if (connection != nullptr)
  183. return connection->sendMessage (mb);
  184. jassertfalse; // this can only be used when the connection is active!
  185. return false;
  186. }
  187. bool ChildProcessSlave::initialiseFromCommandLine (const String& commandLine,
  188. const String& commandLineUniqueID,
  189. int timeoutMs)
  190. {
  191. String prefix (getCommandLinePrefix (commandLineUniqueID));
  192. if (commandLine.trim().startsWith (prefix))
  193. {
  194. String pipeName (commandLine.fromFirstOccurrenceOf (prefix, false, false)
  195. .upToFirstOccurrenceOf (" ", false, false).trim());
  196. if (pipeName.isNotEmpty())
  197. {
  198. connection = new Connection (*this, pipeName, timeoutMs <= 0 ? defaultTimeoutMs : timeoutMs);
  199. if (! connection->isConnected())
  200. connection = nullptr;
  201. }
  202. }
  203. return connection != nullptr;
  204. }