Vuo  2.4.1
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 <CoreFoundation/CoreFoundation.h>
16#include "VuoCompositionDiff.hh"
17#include "VuoEventLoop.h"
18#include "VuoException.hh"
19#include "VuoHeap.h"
20#include "VuoNodeRegistry.hh"
23#include "VuoThreadManager.hh"
24
29{
30 persistentState = NULL;
31}
32
37{
38 delete persistentState;
39}
40
47void VuoRuntimeState::init(void *zmqContext, const char *controlUrl, const char *telemetryUrl, bool isPaused,
48 pid_t runnerPid, int runnerPipe, bool continueIfRunnerDies, const char *workingDirectory,
49 void *compositionBinaryHandle)
50{
51 if (! persistentState)
52 {
53 persistentState = new VuoRuntimePersistentState(workingDirectory);
55 }
56
57 this->_isPaused = isPaused;
58 hasBeenUnpaused = false;
59 _isStopped = false;
60 wasStopCompositionCalled = false;
61 this->continueIfRunnerDies = continueIfRunnerDies;
62
63 this->runnerPid = runnerPid;
64
65 terminationDisabledCount = 0;
66 terminationDisabledQueue = dispatch_queue_create("org.vuo.runtime.terminationDisabled", NULL);
67
68 stopQueue = dispatch_queue_create("org.vuo.runtime.stop", NULL);
69
70 waitForStopTimer = NULL;
71 waitForStopCanceledSemaphore = NULL;
72
73 vuoSetup = NULL;
74 vuoCleanup = NULL;
75 vuoInstanceInit = NULL;
76 vuoInstanceFini = NULL;
77 vuoInstanceTriggerStart = NULL;
78 vuoInstanceTriggerStop = NULL;
79
80 updateCompositionSymbols(compositionBinaryHandle);
81 persistentState->communicator->openConnection(zmqContext, controlUrl, telemetryUrl, runnerPipe);
82}
83
88{
92
93 if (waitForStopTimer)
94 {
95 dispatch_source_cancel(waitForStopTimer);
96 dispatch_semaphore_wait(waitForStopCanceledSemaphore, DISPATCH_TIME_FOREVER);
97 dispatch_release(waitForStopTimer);
98 dispatch_release(waitForStopCanceledSemaphore);
99 }
100
102
103 dispatch_release(stopQueue);
104}
105
111void VuoRuntimeState::updateCompositionSymbols(void *compositionBinaryHandle)
112{
113 ostringstream errorMessage;
114
115 vuoSetup = (vuoSetupType) dlsym(compositionBinaryHandle, "vuoSetup");
116 if (! vuoSetup)
117 {
118 errorMessage << "The composition couldn't be started because its vuoSetup() function couldn't be found : " << dlerror();
119 throw VuoException(errorMessage.str());
120 }
121
122 vuoCleanup = (vuoCleanupType) dlsym(compositionBinaryHandle, "vuoCleanup");
123 if (! vuoCleanup)
124 {
125 errorMessage << "The composition couldn't be started because its vuoCleanup() function couldn't be found : " << dlerror();
126 throw VuoException(errorMessage.str());
127 }
128
129 vuoInstanceInit = (vuoInstanceInitType) dlsym(compositionBinaryHandle, "vuoInstanceInit");
130 if (! vuoInstanceInit)
131 {
132 errorMessage << "The composition couldn't be started because its vuoInstanceInit() function couldn't be found : " << dlerror();
133 throw VuoException(errorMessage.str());
134 }
135
136 vuoInstanceFini = (vuoInstanceFiniType) dlsym(compositionBinaryHandle, "vuoInstanceFini");
137 if (! vuoInstanceFini)
138 {
139 errorMessage << "The composition couldn't be started because its vuoInstanceFini() function couldn't be found : " << dlerror();
140 throw VuoException(errorMessage.str());
141 }
142
143 vuoInstanceTriggerStart = (vuoInstanceTriggerStartType) dlsym(compositionBinaryHandle, "vuoInstanceTriggerStart");
144 if (! vuoInstanceTriggerStart)
145 {
146 errorMessage << "The composition couldn't be started because its vuoInstanceTriggerStart() function couldn't be found : " << dlerror();
147 throw VuoException(errorMessage.str());
148 }
149
150 vuoInstanceTriggerStop = (vuoInstanceTriggerStopType) dlsym(compositionBinaryHandle, "vuoInstanceTriggerStop");
151 if (! vuoInstanceTriggerStop)
152 {
153 errorMessage << "The composition couldn't be started because its vuoInstanceTriggerStop() function couldn't be found : " << dlerror();
154 throw VuoException(errorMessage.str());
155 }
156
157 persistentState->communicator->updateCompositionSymbols(compositionBinaryHandle);
158 persistentState->nodeRegistry->updateCompositionSymbols(compositionBinaryHandle);
159}
160
165{
166 return _isPaused;
167}
168
173{
174 return _isStopped;
175}
176
181{
182 return runnerPid;
183}
184
188bool VuoRuntimeState::isRunnerInCurrentProcess(void)
189{
190 return runnerPid == getpid();
191}
192
196bool VuoRuntimeState::mayBeTerminated(void)
197{
198 __block bool ret;
199 dispatch_sync(terminationDisabledQueue, ^{
200 ret = (terminationDisabledCount == 0);
201 });
202
203 return ret;
204}
205
213{
214 dispatch_sync(terminationDisabledQueue, ^{
215 ++terminationDisabledCount;
216 });
217}
218
223{
224 dispatch_sync(terminationDisabledQueue, ^{
225 --terminationDisabledCount;
226 if (terminationDisabledCount < 0)
227 terminationDisabledCount = 0;
228 });
229}
230
236{
238
239 vuoSetup();
240
241 if (! _isPaused)
242 {
243 // Wait to call vuoInstanceInit() until the first time the composition enters an unpaused state.
244 // If vuoInstanceInit() were called immediately when the composition is started in a paused state(),
245 // then the vuo.event.fireOnStart node's fired event would be ignored.
246
247 hasBeenUnpaused = true;
248 __block bool initDone = false;
249 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
250 vuoInstanceInit();
251 vuoInstanceTriggerStart();
252 initDone = true;
254 });
255 while (! initDone)
256 VuoEventLoop_processEvent(VuoEventLoop_WaitIndefinitely);
257 }
258
261
262 if (! continueIfRunnerDies)
264}
265
270{
271 _isPaused = true;
272 vuoInstanceTriggerStop();
273}
274
279{
280 _isPaused = false;
281
282 if (! hasBeenUnpaused)
283 {
284 hasBeenUnpaused = true;
285 vuoInstanceInit();
286 }
287 vuoInstanceTriggerStart();
288}
289
294void VuoRuntimeState::stopCompositionAsOrderedByRunner(bool isBeingReplaced, int timeoutInSeconds)
295{
296 if (! isBeingReplaced)
297 {
299 }
300
301 stopComposition(isBeingReplaced, timeoutInSeconds);
302
303 if (timeoutInSeconds >= 0)
304 {
305 VUOLOG_PROFILE_BEGIN(mainQueue);
306 dispatch_sync(dispatch_get_main_queue(), ^{
307 VUOLOG_PROFILE_END(mainQueue);
308 VuoEventLoop_processEvent(VuoEventLoop_RunOnce);
309 });
310
311 VuoCompositionState compositionState = { (void *)this, "" };
313
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
370void 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 && ! isRunnerInCurrentProcess())
398 {
399 VuoCompositionState compositionState = { (void *)this, "" };
401
402 // Call VuoImageRenderer_fini() to clean up its graphics data before the VuoGlPool goes away.
403 typedef void (*VuoImageRendererFiniType)(void);
404 VuoImageRendererFiniType vuoImageRendererFini = (VuoImageRendererFiniType) dlsym(RTLD_DEFAULT, "VuoImageRenderer_fini");
405 if (vuoImageRendererFini)
406 vuoImageRendererFini();
407
408 // Call VuoImageTextCache_fini() to clear the cache so that the subsequent VuoHeap_report() doesn't report memory leaks.
409 typedef void (*VuoImageTextCacheFiniType)(void);
410 VuoImageTextCacheFiniType vuoImageTextCacheFini = (VuoImageTextCacheFiniType) dlsym(RTLD_DEFAULT, "VuoImageTextCache_fini");
411 if (vuoImageTextCacheFini)
412 vuoImageTextCacheFini();
413
414 // Call VuoApp_fini() to shut down the NSApplication gracefully. (This must be done *before* breaking out of the event loop.)
415 typedef void (*VuoAppFiniType)(void);
416 VuoAppFiniType vuoAppFini = (VuoAppFiniType) dlsym(RTLD_DEFAULT, "VuoApp_fini");
417 if (vuoAppFini)
418 vuoAppFini();
419
421 }
422
423 vuoCleanup();
424
426
429 else
431 });
432
433 dispatch_release(stopQueue);
434}
435
442{
443 _isStopped = true;
445}
446
450void VuoRuntimeState::killProcessAfterTimeout(int timeoutInSeconds)
451{
452 if (timeoutInSeconds < 0)
453 return;
454
455 if (isRunnerInCurrentProcess())
456 // If the runner is in the same process as the composition (and possibly other current-process compositions),
457 // it would be overkill to kill all of it.
458 return;
459
460 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeoutInSeconds * NSEC_PER_SEC),
461 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
462
463 // Since below we need to use the main thread to check whether the composition can be terminated,
464 // set a backup timer that fires if the main thread isn't responding.
465 dispatch_source_t backupTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
466 dispatch_source_set_timer(backupTimer, dispatch_time(DISPATCH_TIME_NOW, timeoutInSeconds * NSEC_PER_SEC), timeoutInSeconds * NSEC_PER_SEC, NSEC_PER_SEC/10);
467 dispatch_source_set_event_handler(backupTimer, ^{
468 if (mayBeTerminated())
469 {
471 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);
472 kill(getpid(), SIGKILL);
473 }
474 });
475 dispatch_resume(backupTimer);
476
477 __block bool eventLoopMayBeTerminated;
478 std::mutex *mainMutex = new std::mutex;
479 mainMutex->lock();
480 CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
481 eventLoopMayBeTerminated = VuoEventLoop_mayBeTerminated();
482 mainMutex->unlock();
483 });
484 CFRunLoopWakeUp(CFRunLoopGetMain());
485
486 // Wait for the above block to execute on the main thread.
487 mainMutex->lock();
488 delete mainMutex;
489
490 if (mayBeTerminated() && eventLoopMayBeTerminated)
491 {
493 VUserLog("Warning: Waited %d seconds for the composition to cleanly shut down, but it's still running. Now I'm force-quitting it.", timeoutInSeconds);
494 kill(getpid(), SIGKILL);
495 }
496
497 dispatch_source_cancel(backupTimer);
498 dispatch_release(backupTimer);
499 });
500}
501
502extern "C"
503{
504
508bool vuoIsPaused(VuoCompositionState *compositionState)
509{
511 return runtimeState->isPaused();
512}
513
514} // extern "C"