RAM2118 commited on
Commit
2871daa
·
verified ·
1 Parent(s): 8957da4

Out-of-process rebuild: Add Plugin/HelperConnection.cpp — full implementation

Browse files
Files changed (1) hide show
  1. Source/Plugin/HelperConnection.cpp +324 -0
Source/Plugin/HelperConnection.cpp ADDED
@@ -0,0 +1,324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include "HelperConnection.h"
2
+ #include "../Shared/Constants.h"
3
+
4
+ #include <sys/socket.h>
5
+ #include <sys/un.h>
6
+ #include <sys/wait.h>
7
+ #include <unistd.h>
8
+ #include <signal.h>
9
+ #include <poll.h>
10
+ #include <spawn.h>
11
+
12
+ extern char** environ;
13
+
14
+ #include "json.hpp"
15
+ using json = nlohmann::json;
16
+
17
+ HelperConnection::HelperConnection()
18
+ {
19
+ myPid = getpid();
20
+ }
21
+
22
+ HelperConnection::~HelperConnection()
23
+ {
24
+ killHelper();
25
+ }
26
+
27
+ bool HelperConnection::spawnHelper()
28
+ {
29
+ killHelper(); // Clean up any previous instance
30
+
31
+ // 1. Create shared memory
32
+ auto shmName = IPC::getShmName(myPid);
33
+ if (!sharedAudio.create(shmName))
34
+ {
35
+ DBG("HelperConnection: Failed to create shared memory");
36
+ return false;
37
+ }
38
+
39
+ // 2. Create semaphores
40
+ if (!inputSem.create(IPC::getInputSemName(myPid)) ||
41
+ !outputSem.create(IPC::getOutputSemName(myPid)))
42
+ {
43
+ DBG("HelperConnection: Failed to create semaphores");
44
+ return false;
45
+ }
46
+
47
+ // 3. Create Unix domain socket (server side)
48
+ auto socketPath = IPC::getSocketPath(myPid);
49
+ unlink(socketPath.c_str()); // Remove stale socket
50
+
51
+ serverSocketFd = socket(AF_UNIX, SOCK_STREAM, 0);
52
+ if (serverSocketFd < 0)
53
+ {
54
+ DBG("HelperConnection: Failed to create socket");
55
+ return false;
56
+ }
57
+
58
+ struct sockaddr_un addr;
59
+ memset(&addr, 0, sizeof(addr));
60
+ addr.sun_family = AF_UNIX;
61
+ strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
62
+
63
+ if (bind(serverSocketFd, (struct sockaddr*)&addr, sizeof(addr)) < 0 ||
64
+ listen(serverSocketFd, 1) < 0)
65
+ {
66
+ DBG("HelperConnection: Failed to bind/listen socket");
67
+ close(serverSocketFd);
68
+ serverSocketFd = -1;
69
+ return false;
70
+ }
71
+
72
+ // 4. Find helper binary (bundled inside .vst3/Contents/Resources/)
73
+ auto helperPath = juce::File::getSpecialLocation(juce::File::currentExecutableFile)
74
+ .getParentDirectory() // MacOS/
75
+ .getParentDirectory() // Contents/
76
+ .getChildFile("Resources")
77
+ .getChildFile(PluginBridgeConstants::kHelperBinaryName);
78
+
79
+ if (!helperPath.exists())
80
+ {
81
+ // Fallback: look next to the plugin binary (for development)
82
+ helperPath = juce::File::getSpecialLocation(juce::File::currentExecutableFile)
83
+ .getParentDirectory()
84
+ .getChildFile(PluginBridgeConstants::kHelperBinaryName);
85
+ }
86
+
87
+ if (!helperPath.exists())
88
+ {
89
+ DBG("HelperConnection: Helper binary not found at: " + helperPath.getFullPathName());
90
+ return false;
91
+ }
92
+
93
+ // 5. Spawn helper process
94
+ state.store(State::Starting);
95
+
96
+ auto pidStr = juce::String(myPid).toStdString();
97
+ auto helperPathStr = helperPath.getFullPathName().toStdString();
98
+
99
+ pid_t pid;
100
+ char* argv[] = {
101
+ (char*)helperPathStr.c_str(),
102
+ (char*)"--pid",
103
+ (char*)pidStr.c_str(),
104
+ nullptr
105
+ };
106
+
107
+ int spawnResult = posix_spawn(&pid, helperPathStr.c_str(), nullptr, nullptr, argv, environ);
108
+ if (spawnResult != 0)
109
+ {
110
+ DBG("HelperConnection: Failed to spawn helper: " + juce::String(strerror(spawnResult)));
111
+ state.store(State::Disconnected);
112
+ return false;
113
+ }
114
+
115
+ helperPid = pid;
116
+ helperRunning.store(true);
117
+
118
+ // 6. Accept connection from helper (with timeout)
119
+ struct pollfd pfd;
120
+ pfd.fd = serverSocketFd;
121
+ pfd.events = POLLIN;
122
+
123
+ if (poll(&pfd, 1, IPC::kHelperTimeoutMs) <= 0)
124
+ {
125
+ DBG("HelperConnection: Helper didn't connect in time");
126
+ killHelper();
127
+ return false;
128
+ }
129
+
130
+ clientSocketFd = accept(serverSocketFd, nullptr, nullptr);
131
+ if (clientSocketFd < 0)
132
+ {
133
+ DBG("HelperConnection: Failed to accept helper connection");
134
+ killHelper();
135
+ return false;
136
+ }
137
+
138
+ // 7. Start IPC listen thread
139
+ ipcRunning.store(true);
140
+ ipcThread = std::thread([this]() { ipcListenLoop(); });
141
+
142
+ // 8. Start crash detection timer
143
+ startTimerHz(4);
144
+
145
+ // Wait for "ready" notification (up to 5s)
146
+ int waitMs = 0;
147
+ while (state.load() == State::Starting && waitMs < IPC::kHelperTimeoutMs)
148
+ {
149
+ juce::Thread::sleep(50);
150
+ waitMs += 50;
151
+ }
152
+
153
+ return state.load() == State::Connected;
154
+ }
155
+
156
+ void HelperConnection::killHelper()
157
+ {
158
+ stopTimer();
159
+
160
+ // Stop IPC
161
+ ipcRunning.store(false);
162
+ if (clientSocketFd >= 0) { close(clientSocketFd); clientSocketFd = -1; }
163
+ if (serverSocketFd >= 0) { close(serverSocketFd); serverSocketFd = -1; }
164
+ if (ipcThread.joinable()) ipcThread.join();
165
+
166
+ // Kill helper process
167
+ if (helperPid > 0 && helperRunning.load())
168
+ {
169
+ kill(helperPid, SIGTERM);
170
+ int status;
171
+ waitpid(helperPid, &status, WNOHANG);
172
+ usleep(100000); // 100ms grace
173
+ kill(helperPid, SIGKILL); // Force if still alive
174
+ waitpid(helperPid, &status, 0);
175
+ }
176
+ helperPid = 0;
177
+ helperRunning.store(false);
178
+
179
+ // Clean up shared resources
180
+ sharedAudio.destroy();
181
+ inputSem.destroy();
182
+ outputSem.destroy();
183
+
184
+ // Remove socket file
185
+ unlink(IPC::getSocketPath(myPid).c_str());
186
+
187
+ state.store(State::Disconnected);
188
+ }
189
+
190
+ std::string HelperConnection::sendCommand(const std::string& jsonCmd)
191
+ {
192
+ std::lock_guard<std::mutex> lock(commandMutex);
193
+
194
+ if (clientSocketFd < 0 || !helperRunning.load())
195
+ return "{\"ok\":false,\"error\":\"not connected\"}";
196
+
197
+ // Send
198
+ auto data = jsonCmd + "\n";
199
+ if (::send(clientSocketFd, data.c_str(), data.size(), MSG_NOSIGNAL) < 0)
200
+ return "{\"ok\":false,\"error\":\"send failed\"}";
201
+
202
+ // Wait for response (up to 5s)
203
+ responseReady.store(false);
204
+ int waitMs = 0;
205
+ while (!responseReady.load() && waitMs < 5000)
206
+ {
207
+ juce::Thread::sleep(10);
208
+ waitMs += 10;
209
+ }
210
+
211
+ if (!responseReady.load())
212
+ return "{\"ok\":false,\"error\":\"timeout\"}";
213
+
214
+ return lastResponse;
215
+ }
216
+
217
+ void HelperConnection::timerCallback()
218
+ {
219
+ if (helperPid <= 0) return;
220
+
221
+ // Check if helper is still alive
222
+ int status;
223
+ pid_t result = waitpid(helperPid, &status, WNOHANG);
224
+
225
+ if (result == helperPid)
226
+ {
227
+ // Helper has exited
228
+ helperRunning.store(false);
229
+ state.store(State::Crashed);
230
+ stopTimer();
231
+
232
+ DBG("HelperConnection: Helper process died");
233
+ if (onHelperCrashed)
234
+ onHelperCrashed();
235
+ }
236
+ }
237
+
238
+ void HelperConnection::ipcListenLoop()
239
+ {
240
+ std::string buffer;
241
+ char chunk[4096];
242
+
243
+ while (ipcRunning.load())
244
+ {
245
+ struct pollfd pfd;
246
+ pfd.fd = clientSocketFd;
247
+ pfd.events = POLLIN;
248
+
249
+ int ret = poll(&pfd, 1, 200);
250
+ if (ret <= 0) continue;
251
+
252
+ ssize_t n = recv(clientSocketFd, chunk, sizeof(chunk) - 1, 0);
253
+ if (n <= 0)
254
+ {
255
+ if (ipcRunning.load())
256
+ {
257
+ helperRunning.store(false);
258
+ state.store(State::Crashed);
259
+ if (onHelperCrashed)
260
+ juce::MessageManager::callAsync([this]() { if (onHelperCrashed) onHelperCrashed(); });
261
+ }
262
+ return;
263
+ }
264
+
265
+ chunk[n] = '\0';
266
+ buffer += chunk;
267
+
268
+ size_t pos;
269
+ while ((pos = buffer.find('\n')) != std::string::npos)
270
+ {
271
+ auto msg = buffer.substr(0, pos);
272
+ buffer.erase(0, pos + 1);
273
+
274
+ if (msg.empty()) continue;
275
+
276
+ // Check if it's a notification or a command response
277
+ try
278
+ {
279
+ auto j = json::parse(msg);
280
+ if (j.contains("notify"))
281
+ {
282
+ handleNotification(msg);
283
+ }
284
+ else
285
+ {
286
+ // It's a response to our command
287
+ lastResponse = msg;
288
+ responseReady.store(true);
289
+ }
290
+ }
291
+ catch (...) {}
292
+ }
293
+ }
294
+ }
295
+
296
+ void HelperConnection::handleNotification(const std::string& jsonStr)
297
+ {
298
+ try
299
+ {
300
+ auto j = json::parse(jsonStr);
301
+ auto notify = j.value("notify", "");
302
+
303
+ if (notify == "ready")
304
+ {
305
+ state.store(State::Connected);
306
+ if (onHelperReady)
307
+ juce::MessageManager::callAsync([this]() { if (onHelperReady) onHelperReady(); });
308
+ }
309
+ else if (notify == "param_changed")
310
+ {
311
+ int index = j.value("index", -1);
312
+ float value = j.value("value", 0.0f);
313
+ if (onParamChanged)
314
+ onParamChanged(index, value);
315
+ }
316
+ else if (notify == "crashed")
317
+ {
318
+ state.store(State::Crashed);
319
+ if (onHelperCrashed)
320
+ juce::MessageManager::callAsync([this]() { if (onHelperCrashed) onHelperCrashed(); });
321
+ }
322
+ }
323
+ catch (...) {}
324
+ }