Vuo  2.0.0
VuoRuntimeState.cc
Go to the documentation of this file.
1 
10 #include "VuoRuntimeState.hh"
11 
12 #include <dlfcn.h>
13 #include <signal.h>
14 #include <sstream>
15 #include "VuoCompositionDiff.hh"
16 #include "VuoEventLoop.h"
17 #include "VuoException.hh"
18 #include "VuoHeap.h"
19 #include "VuoNodeRegistry.hh"
22 #include "VuoThreadManager.hh"
23 
28 {
29  persistentState = NULL;
30 }
31 
36 {
37  delete persistentState;
38 }
39 
46 void VuoRuntimeState::init(void *zmqContext, const char *controlUrl, const char *telemetryUrl, bool isPaused,
47  pid_t runnerPid, int runnerPipe, bool continueIfRunnerDies, const char *workingDirectory,
48  void *compositionBinaryHandle)
49 {
50  if (! persistentState)
51  {
52  persistentState = new VuoRuntimePersistentState(workingDirectory);
54  }
55 
56  this->_isPaused = isPaused;
57  hasBeenUnpaused = false;
58  _isStopped = false;
59  wasStopCompositionCalled = false;
60  this->continueIfRunnerDies = continueIfRunnerDies;
61 
62  this->runnerPid = runnerPid;
63 
64  terminationDisabledCount = 0;
65  terminationDisabledQueue = dispatch_queue_create("org.vuo.runtime.terminationDisabled", NULL);
66 
67  stopQueue = dispatch_queue_create("org.vuo.runtime.stop", NULL);
68 
69  waitForStopTimer = NULL;
70  waitForStopCanceledSemaphore = NULL;
71 
72  vuoSetup = NULL;
73  vuoCleanup = NULL;
74  vuoInstanceInit = NULL;
75  vuoInstanceFini = NULL;
76  vuoInstanceTriggerStart = NULL;
77  vuoInstanceTriggerStop = NULL;
78 
79  updateCompositionSymbols(compositionBinaryHandle);
80  persistentState->communicator->openConnection(zmqContext, controlUrl, telemetryUrl, runnerPipe);
81 }
82 
87 {
91 
92  if (waitForStopTimer)
93  {
94  dispatch_source_cancel(waitForStopTimer);
95  dispatch_semaphore_wait(waitForStopCanceledSemaphore, DISPATCH_TIME_FOREVER);
96  dispatch_release(waitForStopTimer);
97  dispatch_release(waitForStopCanceledSemaphore);
98  }
99 
101 
102  dispatch_release(stopQueue);
103 }
104 
110 void VuoRuntimeState::updateCompositionSymbols(void *compositionBinaryHandle)
111 {
112  ostringstream errorMessage;
113 
114  vuoSetup = (vuoSetupType) dlsym(compositionBinaryHandle, "vuoSetup");
115  if (! vuoSetup)
116  {
117  errorMessage << "The composition couldn't be started because its vuoSetup() function couldn't be found : " << dlerror();
118  throw VuoException(errorMessage.str());
119  }
120 
121  vuoCleanup = (vuoCleanupType) dlsym(compositionBinaryHandle, "vuoCleanup");
122  if (! vuoCleanup)
123  {
124  errorMessage << "The composition couldn't be started because its vuoCleanup() function couldn't be found : " << dlerror();
125  throw VuoException(errorMessage.str());
126  }
127 
128  vuoInstanceInit = (vuoInstanceInitType) dlsym(compositionBinaryHandle, "vuoInstanceInit");
129  if (! vuoInstanceInit)
130  {
131  errorMessage << "The composition couldn't be started because its vuoInstanceInit() function couldn't be found : " << dlerror();
132  throw VuoException(errorMessage.str());
133  }
134 
135  vuoInstanceFini = (vuoInstanceFiniType) dlsym(compositionBinaryHandle, "vuoInstanceFini");
136  if (! vuoInstanceFini)
137  {
138  errorMessage << "The composition couldn't be started because its vuoInstanceFini() function couldn't be found : " << dlerror();
139  throw VuoException(errorMessage.str());
140  }
141 
142  vuoInstanceTriggerStart = (vuoInstanceTriggerStartType) dlsym(compositionBinaryHandle, "vuoInstanceTriggerStart");
143  if (! vuoInstanceTriggerStart)
144  {
145  errorMessage << "The composition couldn't be started because its vuoInstanceTriggerStart() function couldn't be found : " << dlerror();
146  throw VuoException(errorMessage.str());
147  }
148 
149  vuoInstanceTriggerStop = (vuoInstanceTriggerStopType) dlsym(compositionBinaryHandle, "vuoInstanceTriggerStop");
150  if (! vuoInstanceTriggerStop)
151  {
152  errorMessage << "The composition couldn't be started because its vuoInstanceTriggerStop() function couldn't be found : " << dlerror();
153  throw VuoException(errorMessage.str());
154  }
155 
156  persistentState->communicator->updateCompositionSymbols(compositionBinaryHandle);
157  persistentState->nodeRegistry->updateCompositionSymbols(compositionBinaryHandle);
158 }
159 
164 {
165  return _isPaused;
166 }
167 
172 {
173  return _isStopped;
174 }
175 
180 {
181  return runnerPid;
182 }
183 
187 bool VuoRuntimeState::mayBeTerminated(void)
188 {
189  __block bool ret;
190  dispatch_sync(terminationDisabledQueue, ^{
191  ret = (terminationDisabledCount == 0);
192  });
193 
194  return ret;
195 }
196 
204 {
205  dispatch_sync(terminationDisabledQueue, ^{
206  ++terminationDisabledCount;
207  });
208 }
209 
214 {
215  dispatch_sync(terminationDisabledQueue, ^{
216  --terminationDisabledCount;
217  if (terminationDisabledCount < 0)
218  terminationDisabledCount = 0;
219  });
220 }
221 
227 {
229 
230  vuoSetup();
231 
232  if (! _isPaused)
233  {
234  // Wait to call vuoInstanceInit() until the first time the composition enters an unpaused state.
235  // If vuoInstanceInit() were called immediately when the composition is started in a paused state(),
236  // then the vuo.event.fireOnStart node's fired event would be ignored.
237 
238  hasBeenUnpaused = true;
239  __block bool initDone = false;
240  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
241  vuoInstanceInit();
242  vuoInstanceTriggerStart();
243  initDone = true;
245  });
246  while (! initDone)
247  VuoEventLoop_processEvent(VuoEventLoop_WaitIndefinitely);
248  }
249 
252 
253  if (! continueIfRunnerDies)
255 }
256 
261 {
262  _isPaused = true;
263  vuoInstanceTriggerStop();
264 }
265 
270 {
271  _isPaused = false;
272 
273  if (! hasBeenUnpaused)
274  {
275  hasBeenUnpaused = true;
276  vuoInstanceInit();
277  }
278  vuoInstanceTriggerStart();
279 }
280 
285 void VuoRuntimeState::stopCompositionAsOrderedByRunner(bool isBeingReplaced, int timeoutInSeconds, bool isLastEverInProcess)
286 {
287  if (! isBeingReplaced)
288  {
290  }
291 
292  stopComposition(isBeingReplaced, timeoutInSeconds);
293 
294  if (timeoutInSeconds >= 0)
295  {
296  VUOLOG_PROFILE_BEGIN(mainQueue);
297  dispatch_sync(dispatch_get_main_queue(), ^{
298  VUOLOG_PROFILE_END(mainQueue);
299  VuoEventLoop_processEvent(VuoEventLoop_RunOnce);
300  });
301 
302  VuoCompositionState compositionState = { (void *)this, "" };
304 
305  // https://b33p.net/kosada/node/12912
306  if (isLastEverInProcess)
307  {
308  typedef void (*VuoImageTextCacheFiniType)(void);
309  VuoImageTextCacheFiniType vuoImageTextCacheFini = (VuoImageTextCacheFiniType) dlsym(RTLD_DEFAULT, "VuoImageTextCache_fini");
310  if (vuoImageTextCacheFini)
311  vuoImageTextCacheFini();
312  }
313 
314  VuoHeap_report();
315 
317  }
318 }
319 
328 {
330 
331  dispatch_async(persistentState->communicator->getControlQueue(), ^{
332  if (persistentState->communicator->hasZmqConnection())
333  {
334  persistentState->communicator->sendStopRequested();
335 
336  // If we haven't received a response to VuoTelemetryStopRequested within 2 seconds, stop anyway.
337  waitForStopTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, persistentState->communicator->getControlQueue());
338  dispatch_source_set_timer(waitForStopTimer, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2.), NSEC_PER_SEC * 2, NSEC_PER_SEC/10);
339  waitForStopCanceledSemaphore = dispatch_semaphore_create(0);
340 
341  dispatch_source_set_event_handler(waitForStopTimer, ^{
342  stopComposition(false, 5);
343  dispatch_source_cancel(waitForStopTimer);
344  });
345 
346  dispatch_source_set_cancel_handler(waitForStopTimer, ^{
347  dispatch_semaphore_signal(waitForStopCanceledSemaphore);
348  });
349 
350  dispatch_resume(waitForStopTimer);
351  }
352  else
353  {
354  stopComposition(false, 5);
355  }
356  });
357 
358  persistentState->communicator->interruptListeningForControl();
359 }
360 
370 void VuoRuntimeState::stopComposition(bool isBeingReplaced, int timeoutInSeconds)
371 {
372  dispatch_retain(stopQueue); // Prevent "Release of a locked queue" error if fini() releases stopQueue while this function is on it (https://b33p.net/kosada/node/15438)
373 
374  dispatch_sync(stopQueue, ^{
375 
376  // If we're stopping due to a user request, and we've already stopped, don't try to stop again.
377  // (The 2-second timer in stopCompositionAsOrderedByComposition() might ding while the previous call is still in progress.)
378  if (wasStopCompositionCalled)
379  return;
380  wasStopCompositionCalled = true;
381 
382  killProcessAfterTimeout(timeoutInSeconds);
383 
384  dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
385  if (hasBeenUnpaused)
386  {
387  if (!_isPaused)
388  {
389  _isPaused = true;
390  vuoInstanceTriggerStop();
391  }
392 
393  vuoInstanceFini(); // Called on a non-main thread to avoid deadlock with VuoRuntimeCommunicator::sendTelemetry().
394  }
395  });
396 
397  if (!isBeingReplaced)
399 
400  vuoCleanup();
401 
403 
406  else
408  });
409 
410  dispatch_release(stopQueue);
411 }
412 
419 {
420  _isStopped = true;
422 }
423 
427 void VuoRuntimeState::killProcessAfterTimeout(int timeoutInSeconds)
428 {
429  if (timeoutInSeconds < 0)
430  return;
431 
432  if (runnerPid == getpid())
433  // If the runner is in the same process as the composition (and possibly other current-process compositions),
434  // it would be overkill to kill all of it.
435  return;
436 
437  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeoutInSeconds * NSEC_PER_SEC),
438  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
439 
440  // Since below we need to use the main thread to check whether the composition can be terminated,
441  // set a backup timer that fires if the main thread isn't responding.
442  dispatch_source_t backupTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
443  dispatch_source_set_timer(backupTimer, dispatch_time(DISPATCH_TIME_NOW, timeoutInSeconds * NSEC_PER_SEC), timeoutInSeconds * NSEC_PER_SEC, NSEC_PER_SEC/10);
444  dispatch_source_set_event_handler(backupTimer, ^{
446  VUserLog("Warning: Waited %d seconds for the composition to cleanly shut down, but it's still running. Now I'm force-quitting it.", timeoutInSeconds * 2);
447  kill(getpid(), SIGKILL);
448  });
449  dispatch_resume(backupTimer);
450 
451  __block bool eventLoopMayBeTerminated;
452  dispatch_sync(dispatch_get_main_queue(), ^{
453  eventLoopMayBeTerminated = VuoEventLoop_mayBeTerminated();
454  });
455  if (mayBeTerminated() && eventLoopMayBeTerminated)
456  {
458  VUserLog("Warning: Waited %d seconds for the composition to cleanly shut down, but it's still running. Now I'm force-quitting it.", timeoutInSeconds);
459  kill(getpid(), SIGKILL);
460  }
461 
462  dispatch_source_cancel(backupTimer);
463  dispatch_release(backupTimer);
464  });
465 }
466 
467 extern "C"
468 {
469 
473 bool vuoIsPaused(VuoCompositionState *compositionState)
474 {
476  return runtimeState->isPaused();
477 }
478 
479 } // extern "C"