Tracking termination of processes
Finding information on how to monitor when processes are terminated was hard to come by and if my memory serves, I believe that it was only with the assistance of Quinn @ Apple who led me to the rather simple method that I present below.
Grand Central Dispatch (GCD) is an Apple technology that allows performing multi-threaded code, without having to think too carefully about the intricacies usually required, which can cause various classic problems.
If you’re a developer in the Apple ecosystem and don’t know about GCD, I strongly urge you to explore it by first reading Ray Wenderlich’s blog on the subject.
When it comes to developing applications, I’m a fan of Qt, especially due to its multi-platform capability, so I don’t often require Cocoa, Objective-C and Swift. Therefore, my limited GCD usage mostly comes down to calling dispatch_once and dispatch_async.
What many people don’t seem to know, or forgot as soon as they read about it, is that GCD provides dispatch source events. As the man pages state:
Dispatch event sources may be used to monitor a variety of system objects and events including file descriptors, mach ports, processes, virtual filesystem nodes, signal delivery and timers.
When a state change occurs, the dispatch source will submit its event handler block to its target queue.
So, how do we use dispatch source?
Since we want to know when processes are terminated, we can create a dispatch source with the processor type and monitor a process exiting, as follows
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t dsp = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid, DISPATCH_PROC_EXIT, queue);
We set event handlers to call our own functions when events occur
dispatch_source_set_event_handler_f(dsp, (dispatch_function_t)process_termination_event);
dispatch_source_set_cancel_handler_f(dsp, (dispatch_function_t)process_termination_finalize);
dispatch_source_set_event_handler notifies us when a process terminates by calling our function process_termination_event
void MyClass::process_termination_event(struct ProcessInfo* procinfo)
{
dispatch_source_cancel(procinfo->source); // terminate monitoring
}
procinfo is a class member variable structure used to maintain state. In this case, we need a handle to the source, in order to cancel it when we’re finished.
Being a generic system, the source event would keep monitoring for the termination of the process, even after it has finished, so calling dispatch_source_cancel notifies the GCD that we’re finished with the source, which in-turn, notifies us back by calling our second event handler function process_termination_finalize, allowing us to clean up
void MyClass ::process_termination_finalize(struct ProcessInfo* procinfo)
{
dispatch_release(procinfo->source);
}
All that’s left is to start monitoring
dispatch_resume(dsp); // let’s go!
So, to summarise
- Create the source with dispatch_source_create
- Setup the event handlers
- Start monitoring
Finally, here it is all together
class ProcessMonitor
{
public:
// Call this to add newly created process for monitoring termination
void TrackProcessTermination(int pid, const QString& path);
private:
struct ProcessInfo
{
int pid;
dispatch_source_t source;
ProcessMonitor* context;
QString path;
};
// callback handlers for monitoring process termination
static void process_termination_event(struct ProcessInfo* procinfo); // notified here when process terminates
static void process_termination_finalize(struct ProcessInfo* procinfo); // cleanup the notification
// a list of pids of currently running (monitored) processes
QSet<int> m_trackingProcessSet;
};