Vuo 2.4.4
Loading...
Searching...
No Matches
VuoCompositionLoader.cc
Go to the documentation of this file.
1
10extern "C" {
11
12#include <dlfcn.h>
13#include <getopt.h>
14#include <stdio.h>
15#include <unistd.h>
16#include <dispatch/dispatch.h>
17#include <CoreFoundation/CoreFoundation.h>
18#include <CoreServices/CoreServices.h>
19#include <objc/runtime.h>
20#include <objc/message.h>
21#include <pthread.h>
22#include "VuoTelemetry.hh"
23#include "VuoEventLoop.h"
24#include "VuoRuntime.h"
25#include "VuoCompositionState.h"
26
28void *ZMQLoaderControl = NULL;
29static void *ZMQLoaderSelfReceive = 0;
30static void *ZMQLoaderSelfSend = 0;
31
32void *ZMQControlContext = NULL;
33void *ZMQControl = NULL;
34char *controlURL = NULL;
35char *telemetryURL = NULL;
36
37bool isReplacing = false;
38bool replacementFailed = false;
39void *dylibHandle = NULL;
40map<string, void *> resourceDylibHandles;
41vector<string> resourceDylibsToUnload;
42vector<string> resourceDylibsToLoad;
43pid_t runnerPid = 0;
44int runnerPipe = -1;
46
47bool replaceComposition(const char *dylibPath, char *compositionDiff);
48void stopComposition(void);
49bool loadResourceDylib(const string &dylibPath);
50bool unloadResourceDylib(const string &dylibPath);
51
52void *VuoApp_mainThread = NULL;
53char *VuoApp_dylibPath = NULL;
54
58static bool isStoppedInitially(void)
59{
60 return false;
61}
62
64
65} // extern "C"
66
70static void __attribute__((constructor)) VuoCompositionLoader_init(void)
71{
72 VuoApp_mainThread = (void *)pthread_self();
73
74#pragma clang diagnostic push
75#pragma clang diagnostic ignored "-Wdeprecated-declarations"
76 // Calls _TSGetMainThread().
77 // https://b33p.net/kosada/node/12944
78 YieldToAnyThread();
79#pragma clang diagnostic pop
80}
81
85void vuoLoaderControlReplySend(enum VuoLoaderControlReply reply, zmq_msg_t *messages, unsigned int messageCount)
86{
87 vuoSend("VuoLoaderControl",ZMQLoaderControl,reply,messages,messageCount,false,NULL);
88}
89
93void vuoControlRequestSend(enum VuoControlRequest request, zmq_msg_t *messages, unsigned int messageCount)
94{
95 vuoSend("VuoControl",ZMQControl,request,messages,messageCount,false,NULL);
96}
97
102{
103 int reply = vuoReceiveInt(ZMQControl, NULL);
104 if (reply != expectedReply)
105 VUserLog("The composition loader received the wrong message from the composition (expected %d, received %d)", expectedReply, reply);
106}
107
111int main(int argc, char **argv)
112{
113 char *loaderControlURL = NULL;
114
115 // Parse commandline arguments.
116 {
117 static struct option options[] = {
118 {"vuo-control", required_argument, NULL, 0},
119 {"vuo-telemetry", required_argument, NULL, 0},
120 {"vuo-loader", required_argument, NULL, 0},
121 {"vuo-runner-pipe", required_argument, NULL, 0},
122 {"vuo-continue-if-runner-dies", no_argument, NULL, 0},
123 {"vuo-runner-pid", required_argument, NULL, 0},
124 {NULL, no_argument, NULL, 0}
125 };
126 int optionIndex=-1;
127 int ret;
128 while ((ret = getopt_long(argc, argv, "", options, &optionIndex)) != -1)
129 {
130 if (ret == '?')
131 continue;
132
133 switch(optionIndex)
134 {
135 case 0: // "vuo-control"
136 controlURL = strdup(optarg);
137 break;
138 case 1: // "vuo-telemetry"
139 telemetryURL = strdup(optarg);
140 break;
141 case 2: // "vuo-loader"
142 if (loaderControlURL)
143 free(loaderControlURL);
144 loaderControlURL = strdup(optarg);
145 break;
146 case 3: // --vuo-runner-pipe
147 runnerPipe = atoi(optarg);
148 break;
149 case 4: // --vuo-continue-if-runner-dies
151 break;
152 case 5: // --vuo-runner-pid
153 runnerPid = atoi(optarg);
154 break;
155 }
156 }
157 }
158
159 if (!loaderControlURL)
160 {
161 VUserLog("Error: Please specify a --vuo-loader URL.");
162 return -1;
163 }
164
165 VuoDefer(^{ free(loaderControlURL); });
166
167 // Set up ZMQ connections.
168 {
169 ZMQLoaderControlContext = zmq_init(1);
170
171 ZMQLoaderControl = zmq_socket(ZMQLoaderControlContext,ZMQ_REP);
172 if(zmq_bind(ZMQLoaderControl,loaderControlURL))
173 {
174 VUserLog("The composition couldn't start because the composition loader couldn't establish communication to control the composition : %s", zmq_strerror(errno));
175 return -1;
176 }
177
178 ZMQLoaderSelfReceive = zmq_socket(ZMQLoaderControlContext, ZMQ_PAIR);
179 if (zmq_bind(ZMQLoaderSelfReceive, "inproc://vuo-loader-self") != 0)
180 {
181 VUserLog("Couldn't bind self-receive socket: %s (%d)", zmq_strerror(errno), errno);
182 return -1;
183 }
184
185 ZMQLoaderSelfSend = zmq_socket(ZMQLoaderControlContext, ZMQ_PAIR);
186 if (zmq_connect(ZMQLoaderSelfSend, "inproc://vuo-loader-self") != 0)
187 {
188 VUserLog("Couldn't connect self-send socket: %s (%d)", zmq_strerror(errno), errno);
189 return -1;
190 }
191 }
192
193 // Launch control responder.
194 dispatch_queue_t loaderControlQueue;
195 dispatch_source_t loaderControlTimer;
196 dispatch_semaphore_t loaderControlCanceledSemaphore;
197 {
198 loaderControlCanceledSemaphore = dispatch_semaphore_create(0);
199 loaderControlQueue = dispatch_queue_create("org.vuo.runtime.loader", NULL);
200 loaderControlTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, loaderControlQueue);
201 dispatch_source_set_timer(loaderControlTimer, dispatch_walltime(NULL,0), NSEC_PER_SEC/1000, NSEC_PER_SEC/1000);
202 dispatch_source_set_event_handler(loaderControlTimer, ^{
203
205
206 zmq_pollitem_t items[]=
207 {
208 {ZMQLoaderControl,0,ZMQ_POLLIN,0},
209 {ZMQLoaderSelfReceive,0,ZMQ_POLLIN,0},
210 };
211 int itemCount = 2;
212 long timeout = -1; // Wait forever (we'll get a message on ZMQLoaderSelfReceive when it's time to stop).
213 zmq_poll(items,itemCount,timeout);
214 if(!(items[0].revents & ZMQ_POLLIN))
215 return;
216
218
219 switch (control)
220 {
222 {
223 char *dylibPath = vuoReceiveAndCopyString(ZMQLoaderControl, NULL);
224
225 int numResourceDylibPathsAdded = vuoReceiveInt(ZMQLoaderControl, NULL);
226 for (int i = 0; i < numResourceDylibPathsAdded; ++i)
227 {
229 resourceDylibsToLoad.push_back(s);
230 free(s);
231 }
232
233 int numResourceDylibPathsRemoved = vuoReceiveInt(ZMQLoaderControl, NULL);
234 for (int i = 0; i < numResourceDylibPathsRemoved; ++i)
235 {
237 resourceDylibsToUnload.push_back(s);
238 free(s);
239 }
240
241 char *compositionDiff = vuoReceiveAndCopyString(ZMQLoaderControl, NULL);
242
243 bool ok = replaceComposition(dylibPath, compositionDiff);
244
245 zmq_msg_t messages[1];
246 vuoInitMessageWithBool(messages, ok);
248
249 if (! ok)
250 {
251 replacementFailed = true;
253 }
254
255 break;
256 }
257 }
258 });
259 dispatch_source_set_cancel_handler(loaderControlTimer, ^{
260 dispatch_semaphore_signal(loaderControlCanceledSemaphore);
261 });
262 dispatch_resume(loaderControlTimer);
263 }
264
265 // Wait until the composition is permanently stopped (not just temporarily stopped for replacing).
266 {
267 // Before the composition has started for the first time, isStopped is an alias for isStoppedInitially, which returns false.
268 // While the composition is being replaced, isReplaced is true and isStopped is invalid for part of the time.
269 // While the composition is running (started and not yet stopped), isStopped returns false.
270 while ((isReplacing || ! isStopped()) && ! replacementFailed)
271 VuoEventLoop_processEvent(VuoEventLoop_WaitIndefinitely);
272 }
273
274 // Clean up ZMQ connections.
275 {
277
278 zmq_close(ZMQControl);
279
280 dispatch_source_cancel(loaderControlTimer);
281
282 // Break out of zmq_poll().
283 {
284 char z = 0;
285 zmq_msg_t message;
286 zmq_msg_init_size(&message, sizeof z);
287 memcpy(zmq_msg_data(&message), &z, sizeof z);
288 if (zmq_msg_send(&message, static_cast<zmq_msg_t *>(ZMQLoaderSelfSend), 0) == -1)
289 VUserLog("Couldn't break: %s (%d)", zmq_strerror(errno), errno);
290 zmq_msg_close(&message);
291 }
292
293 dispatch_semaphore_wait(loaderControlCanceledSemaphore, DISPATCH_TIME_FOREVER);
294 dispatch_release(loaderControlCanceledSemaphore);
295 dispatch_release(loaderControlTimer);
296 dispatch_sync(loaderControlQueue, ^{
297 zmq_close(ZMQLoaderControl);
298 zmq_close(ZMQLoaderSelfSend);
299 zmq_close(ZMQLoaderSelfReceive);
300 });
301 dispatch_release(loaderControlQueue);
302
303 if (! replacementFailed)
304 {
305 VuoFiniType *vuoFini = (VuoFiniType *) dlsym(dylibHandle, "vuoFini");
306 if (! vuoFini)
307 {
308 VUserLog("The composition couldn't stop because vuoFini() couldn't be found in the composition library : %s", dlerror());
309 return -1;
310 }
311 vuoFini();
312 }
313
314 // Before terminating the process, wait for any last messages to be sent.
315 zmq_ctx_term(ZMQLoaderControlContext);
316 zmq_ctx_term(ZMQControlContext);
317 }
318
319 return 0;
320}
321
327bool replaceComposition(const char *dylibPath, char *compositionDiff)
328{
329 // Store dylibPath for VuoApp_getName().
331 free(VuoApp_dylibPath);
332 VuoApp_dylibPath = strdup(dylibPath);
333 // Provide the composition name to the crash reporter.
334 {
335 const char *filename = strrchr(dylibPath, '/');
336 if (filename)
337 {
338 char *name = strdup(filename + 1); // Trim leading slash.
339 name[strlen(name) - strlen("-XXXXXX.dylib")] = 0;
340 VuoLog_status("Running Vuo composition \"%s\"", name);
341 free(name);
342 }
343 }
344
345 isReplacing = true;
346
347 void *runtimePersistentState = NULL;
348
349 // Stop the old composition (if any).
350 if (dylibHandle)
351 {
352 isStopped = NULL;
353
354 VuoCompositionState compositionState = { NULL, "" };
355 void **vuoRuntimeState = (void **)dlsym(dylibHandle, "vuoRuntimeState");
356 if (! vuoRuntimeState)
357 {
358 VUserLog("The composition couldn't be replaced because vuoRuntimeState couldn't be found in '%s' : %s", dylibPath, dlerror());
359 return false;
360 }
361 compositionState.runtimeState = *vuoRuntimeState;
362
363 typedef void (*vuoSetCompositionDiffType)(VuoCompositionState *, char *);
364 vuoSetCompositionDiffType vuoSetCompositionDiff = (vuoSetCompositionDiffType) dlsym(dylibHandle, "vuoSetCompositionDiff");
366 {
367 VUserLog("The composition couldn't be replaced because vuoSetCompositionDiff() couldn't be found in the composition library : %s", dlerror());
368 return false;
369 }
370 vuoSetCompositionDiff(&compositionState, compositionDiff);
371
373
374 zmq_close(ZMQControl);
375 ZMQControl = NULL;
376
377 VuoFiniType *vuoFini = (VuoFiniType *)dlsym(dylibHandle, "vuoFini");
378 if (! vuoFini)
379 {
380 VUserLog("The composition couldn't be replaced because vuoFini() couldn't be found in the composition library : %s", dlerror());
381 return false;
382 }
383 runtimePersistentState = vuoFini();
384
385 dlclose(dylibHandle);
386 dylibHandle = NULL;
387
388 for (vector<string>::iterator i = resourceDylibsToUnload.begin(); i != resourceDylibsToUnload.end(); ++i)
391 }
392
393 // Start the new composition paused.
394 {
395 for (vector<string>::iterator i = resourceDylibsToLoad.begin(); i != resourceDylibsToLoad.end(); ++i)
397 resourceDylibsToLoad.clear();
398
399 ZMQControlContext = zmq_init(1);
400
401 ZMQControl = zmq_socket(ZMQControlContext,ZMQ_REQ);
402 if (zmq_connect(ZMQControl,controlURL))
403 {
404 VUserLog("The composition couldn't be replaced because the composition loader couldn't establish communication to control the composition : %s", zmq_strerror(errno));
405 return false;
406 }
407
409
410 dylibHandle = dlopen(dylibPath, RTLD_NOW);
411 if (! dylibHandle)
412 {
413 VUserLog("The composition couldn't be replaced because the library '%s' couldn't be loaded : %s", dylibPath, dlerror());
414 return false;
415 }
416
417 vuoInitInProcess = (VuoInitInProcessType *)dlsym(dylibHandle, "vuoInitInProcess");
418 if (! vuoInitInProcess)
419 {
420 VUserLog("The composition couldn't be replaced because vuoInitInProcess() couldn't be found in '%s' : %s", dylibPath, dlerror());
421 return false;
422 }
423
424 isStopped = (VuoIsCurrentCompositionStoppedType *)dlsym(dylibHandle, "vuoIsCurrentCompositionStopped");
425 if (! isStopped)
426 {
427 VUserLog("The composition couldn't be replaced because vuoIsCurrentCompositionStopped() couldn't be found in '%s' : %s", dylibPath, dlerror());
428 return false;
429 }
430
432 "", dylibHandle, runtimePersistentState, false);
433 }
434
435 isReplacing = false;
436
437 return true;
438}
439
444{
446
447 const int timeoutInSeconds = -1;
448 zmq_msg_t messages[2];
449 vuoInitMessageWithInt(&messages[0], timeoutInSeconds);
450 vuoInitMessageWithBool(&messages[1], true ); // isBeingReplaced
453}
454
458bool loadResourceDylib(const string &dylibPath)
459{
460 void *dylibHandle = dlopen(dylibPath.c_str(), RTLD_NOW);
461 if (! dylibHandle)
462 {
463 VUserLog("The composition couldn't be replaced because the library '%s' couldn't be loaded : %s", dylibPath.c_str(), dlerror());
464 return false;
465 }
466
468 return true;
469}
470
474bool unloadResourceDylib(const string &dylibPath)
475{
476 void *dylibHandle = resourceDylibHandles[dylibPath];
477 if (! dylibHandle)
478 {
479 VUserLog("The library '%s' couldn't be unloaded because its handle was not found.", dylibPath.c_str());
480 return false;
481 }
482
483 int ret = dlclose(dylibHandle);
484 if (ret != 0)
485 {
486 VUserLog("The library '%s' couldn't be unloaded : %s", dylibPath.c_str(), dlerror());
487 return false;
488 }
489
490 resourceDylibHandles.erase(dylibPath);
491 return true;
492}