Vuo  2.3.2
VuoCompositionLoader.cc
Go to the documentation of this file.
1 
10 extern "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.h"
23 #include "VuoEventLoop.h"
24 #include "VuoRuntime.h"
25 #include "VuoCompositionState.h"
26 
28 void *ZMQLoaderControl = NULL;
29 static void *ZMQLoaderSelfReceive = 0;
30 static void *ZMQLoaderSelfSend = 0;
31 
32 void *ZMQControlContext = NULL;
33 void *ZMQControl = NULL;
34 char *controlURL = NULL;
35 char *telemetryURL = NULL;
36 
37 bool isReplacing = false;
38 void *dylibHandle = NULL;
39 map<string, void *> resourceDylibHandles;
40 vector<string> resourceDylibsToUnload;
41 vector<string> resourceDylibsToLoad;
42 pid_t runnerPid = 0;
43 int runnerPipe = -1;
44 bool continueIfRunnerDies = false;
45 
46 bool replaceComposition(const char *dylibPath, char *compositionDiff);
47 void stopComposition(void);
48 bool loadResourceDylib(const string &dylibPath);
49 bool unloadResourceDylib(const string &dylibPath);
50 
51 void *VuoApp_mainThread = NULL;
52 char *VuoApp_dylibPath = NULL;
53 
57 static bool isStoppedInitially(void)
58 {
59  return false;
60 }
61 
63 
64 } // extern "C"
65 
69 static 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 
84 void vuoLoaderControlReplySend(enum VuoLoaderControlReply reply, zmq_msg_t *messages, unsigned int messageCount)
85 {
86  vuoSend("VuoLoaderControl",ZMQLoaderControl,reply,messages,messageCount,false,NULL);
87 }
88 
92 void vuoControlRequestSend(enum VuoControlRequest request, zmq_msg_t *messages, unsigned int messageCount)
93 {
94  vuoSend("VuoControl",ZMQControl,request,messages,messageCount,false,NULL);
95 }
96 
100 void vuoControlReplyReceive(enum VuoControlReply expectedReply)
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 
110 int 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
149  continueIfRunnerDies = true;
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  {
227  char *s = vuoReceiveAndCopyString(ZMQLoaderControl, NULL);
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  {
235  char *s = vuoReceiveAndCopyString(ZMQLoaderControl, NULL);
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 
315 bool replaceComposition(const char *dylibPath, char *compositionDiff)
316 {
317  // Store dylibPath for VuoApp_getName().
318  if (VuoApp_dylibPath)
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");
353  if (! 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 
360  stopComposition();
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)
378  resourceDylibsToUnload.clear();
379  }
380 
381  // Start the new composition paused.
382  {
383  for (vector<string>::iterator i = resourceDylibsToLoad.begin(); i != resourceDylibsToLoad.end(); ++i)
384  loadResourceDylib(*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 
431 void stopComposition(void)
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 
447 bool 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 
456  resourceDylibHandles[dylibPath] = dylibHandle;
457  return true;
458 }
459 
463 bool 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 }