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