Introduce process observer
Add a tool to easily observe process termination. This allows to move this complexity out of the server code.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#include "process.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <libgen.h>
|
||||
#include "log.h"
|
||||
|
||||
@@ -41,3 +42,80 @@ sc_pipe_read_all(sc_pipe pipe, char *data, size_t len) {
|
||||
}
|
||||
return copied;
|
||||
}
|
||||
|
||||
static int
|
||||
run_observer(void *data) {
|
||||
struct sc_process_observer *observer = data;
|
||||
sc_process_wait(observer->pid, false); // ignore exit code
|
||||
|
||||
sc_mutex_lock(&observer->mutex);
|
||||
observer->terminated = true;
|
||||
sc_cond_signal(&observer->cond_terminated);
|
||||
sc_mutex_unlock(&observer->mutex);
|
||||
|
||||
if (observer->listener) {
|
||||
observer->listener->on_terminated(observer->listener_userdata);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_process_observer_init(struct sc_process_observer *observer, sc_pid pid,
|
||||
const struct sc_process_listener *listener,
|
||||
void *listener_userdata) {
|
||||
// Either no listener, or on_terminated() is defined
|
||||
assert(!listener || listener->on_terminated);
|
||||
|
||||
bool ok = sc_mutex_init(&observer->mutex);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ok = sc_cond_init(&observer->cond_terminated);
|
||||
if (!ok) {
|
||||
sc_mutex_destroy(&observer->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
observer->pid = pid;
|
||||
observer->listener = listener;
|
||||
observer->listener_userdata = listener_userdata;
|
||||
observer->terminated = false;
|
||||
|
||||
ok = sc_thread_create(&observer->thread, run_observer, "process_observer",
|
||||
observer);
|
||||
if (!ok) {
|
||||
sc_cond_destroy(&observer->cond_terminated);
|
||||
sc_mutex_destroy(&observer->mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_process_observer_timedwait(struct sc_process_observer *observer,
|
||||
sc_tick deadline) {
|
||||
sc_mutex_lock(&observer->mutex);
|
||||
bool timed_out = false;
|
||||
while (!observer->terminated && !timed_out) {
|
||||
timed_out = !sc_cond_timedwait(&observer->cond_terminated,
|
||||
&observer->mutex, deadline);
|
||||
}
|
||||
bool terminated = observer->terminated;
|
||||
sc_mutex_unlock(&observer->mutex);
|
||||
|
||||
return terminated;
|
||||
}
|
||||
|
||||
void
|
||||
sc_process_observer_join(struct sc_process_observer *observer) {
|
||||
sc_thread_join(&observer->thread, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
sc_process_observer_destroy(struct sc_process_observer *observer) {
|
||||
sc_cond_destroy(&observer->cond_terminated);
|
||||
sc_mutex_destroy(&observer->mutex);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "util/thread.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
@@ -32,6 +33,34 @@
|
||||
|
||||
#endif
|
||||
|
||||
struct sc_process_listener {
|
||||
void (*on_terminated)(void *userdata);
|
||||
};
|
||||
|
||||
/**
|
||||
* Tool to observe process termination
|
||||
*
|
||||
* To keep things simple and multiplatform, it runs a separate thread to wait
|
||||
* for process termination (without closing the process to avoid race
|
||||
* conditions).
|
||||
*
|
||||
* It allows a caller to block until the process is terminated (with a
|
||||
* timeout), and to be notified asynchronously from the observer thread.
|
||||
*
|
||||
* The process is not owned by the observer (the observer will never close it).
|
||||
*/
|
||||
struct sc_process_observer {
|
||||
sc_pid pid;
|
||||
|
||||
sc_mutex mutex;
|
||||
sc_cond cond_terminated;
|
||||
bool terminated;
|
||||
|
||||
sc_thread thread;
|
||||
const struct sc_process_listener *listener;
|
||||
void *listener_userdata;
|
||||
};
|
||||
|
||||
enum sc_process_result {
|
||||
SC_PROCESS_SUCCESS,
|
||||
SC_PROCESS_ERROR_GENERIC,
|
||||
@@ -106,4 +135,42 @@ sc_pipe_read_all(sc_pipe pipe, char *data, size_t len);
|
||||
void
|
||||
sc_pipe_close(sc_pipe pipe);
|
||||
|
||||
/**
|
||||
* Start observing process
|
||||
*
|
||||
* The listener is optional. If set, its callback will be called from the
|
||||
* observer thread once the process is terminated.
|
||||
*/
|
||||
bool
|
||||
sc_process_observer_init(struct sc_process_observer *observer, sc_pid pid,
|
||||
const struct sc_process_listener *listener,
|
||||
void *listener_userdata);
|
||||
|
||||
/**
|
||||
* Wait for process termination until a deadline
|
||||
*
|
||||
* Return true if the process is already terminated. Return false if the
|
||||
* process terminatation has not been detected yet (however, it may have
|
||||
* terminated in the meantime).
|
||||
*
|
||||
* To wait without timeout/deadline, just use sc_process_wait() instead.
|
||||
*/
|
||||
bool
|
||||
sc_process_observer_timedwait(struct sc_process_observer *observer,
|
||||
sc_tick deadline);
|
||||
|
||||
/**
|
||||
* Join the observer thread
|
||||
*/
|
||||
void
|
||||
sc_process_observer_join(struct sc_process_observer *observer);
|
||||
|
||||
/**
|
||||
* Destroy the observer
|
||||
*
|
||||
* This does not close the associated process.
|
||||
*/
|
||||
void
|
||||
sc_process_observer_destroy(struct sc_process_observer *observer);
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user