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.

260 lines
7.9KB

  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 };
  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() : Thread ("IPC ping"), timeoutMs (8000)
  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)
  67. : InterprocessConnection (false, magicMastSlaveConnectionHeader), owner (m)
  68. {
  69. if (createPipe (pipeName, timeoutMs))
  70. startThread (4);
  71. }
  72. ~Connection()
  73. {
  74. stopThread (10000);
  75. }
  76. private:
  77. void connectionMade() override {}
  78. void connectionLost() override { owner.handleConnectionLost(); }
  79. bool sendPingMessage (const MemoryBlock& m) override { return owner.sendMessageToSlave (m); }
  80. void pingFailed() override { connectionLost(); }
  81. void messageReceived (const MemoryBlock& m) override
  82. {
  83. pingReceived();
  84. if (m.getSize() != specialMessageSize || ! isPingMessage (m))
  85. owner.handleMessageFromSlave (m);
  86. }
  87. ChildProcessMaster& owner;
  88. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Connection)
  89. };
  90. //==============================================================================
  91. ChildProcessMaster::ChildProcessMaster() {}
  92. ChildProcessMaster::~ChildProcessMaster()
  93. {
  94. if (connection != nullptr)
  95. {
  96. sendMessageToSlave (MemoryBlock (killMessage, specialMessageSize));
  97. connection->disconnect();
  98. connection = nullptr;
  99. }
  100. }
  101. void ChildProcessMaster::handleConnectionLost() {}
  102. bool ChildProcessMaster::sendMessageToSlave (const MemoryBlock& mb)
  103. {
  104. if (connection != nullptr)
  105. return connection->sendMessage (mb);
  106. jassertfalse; // this can only be used when the connection is active!
  107. return false;
  108. }
  109. bool ChildProcessMaster::launchSlaveProcess (const File& executable, const String& commandLineUniqueID)
  110. {
  111. connection = nullptr;
  112. jassert (childProcess.kill());
  113. const String pipeName ("p" + String::toHexString (Random().nextInt64()));
  114. StringArray args;
  115. args.add (executable.getFullPathName());
  116. args.add (getCommandLinePrefix (commandLineUniqueID) + pipeName);
  117. if (childProcess.start (args))
  118. {
  119. connection = new Connection (*this, pipeName);
  120. if (connection->isConnected())
  121. {
  122. sendMessageToSlave (MemoryBlock (startMessage, specialMessageSize));
  123. return true;
  124. }
  125. connection = nullptr;
  126. }
  127. return false;
  128. }
  129. //==============================================================================
  130. struct ChildProcessSlave::Connection : public InterprocessConnection,
  131. private ChildProcessPingThread
  132. {
  133. Connection (ChildProcessSlave& p, const String& pipeName)
  134. : InterprocessConnection (false, magicMastSlaveConnectionHeader), owner (p)
  135. {
  136. connectToPipe (pipeName, timeoutMs);
  137. startThread (4);
  138. }
  139. ~Connection()
  140. {
  141. stopThread (10000);
  142. }
  143. private:
  144. ChildProcessSlave& owner;
  145. void connectionMade() override {}
  146. void connectionLost() override { owner.handleConnectionLost(); }
  147. bool sendPingMessage (const MemoryBlock& m) override { return owner.sendMessageToMaster (m); }
  148. void pingFailed() override { connectionLost(); }
  149. void messageReceived (const MemoryBlock& m) override
  150. {
  151. pingReceived();
  152. if (m.getSize() == specialMessageSize)
  153. {
  154. if (isPingMessage (m))
  155. return;
  156. if (memcmp (m.getData(), killMessage, specialMessageSize) == 0)
  157. {
  158. triggerConnectionLostMessage();
  159. return;
  160. }
  161. if (memcmp (m.getData(), startMessage, specialMessageSize) == 0)
  162. {
  163. owner.handleConnectionMade();
  164. return;
  165. }
  166. }
  167. owner.handleMessageFromMaster (m);
  168. }
  169. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Connection)
  170. };
  171. //==============================================================================
  172. ChildProcessSlave::ChildProcessSlave() {}
  173. ChildProcessSlave::~ChildProcessSlave() {}
  174. void ChildProcessSlave::handleConnectionMade() {}
  175. void ChildProcessSlave::handleConnectionLost() {}
  176. bool ChildProcessSlave::sendMessageToMaster (const MemoryBlock& mb)
  177. {
  178. if (connection != nullptr)
  179. return connection->sendMessage (mb);
  180. jassertfalse; // this can only be used when the connection is active!
  181. return false;
  182. }
  183. bool ChildProcessSlave::initialiseFromCommandLine (const String& commandLine,
  184. const String& commandLineUniqueID)
  185. {
  186. String prefix (getCommandLinePrefix (commandLineUniqueID));
  187. if (commandLine.trim().startsWith (prefix))
  188. {
  189. String pipeName (commandLine.fromFirstOccurrenceOf (prefix, false, false)
  190. .upToFirstOccurrenceOf (" ", false, false).trim());
  191. if (pipeName.isNotEmpty())
  192. {
  193. connection = new Connection (*this, pipeName);
  194. if (! connection->isConnected())
  195. connection = nullptr;
  196. }
  197. }
  198. return connection != nullptr;
  199. }