|
@@ -27,112 +27,113 @@
|
|
|
#include <pal.h>
|
|
|
#include <list.h>
|
|
|
|
|
|
+#define IDLE_SLEEP_TIME 1000
|
|
|
+#define MAX_IDLE_CYCLES 100
|
|
|
+
|
|
|
DEFINE_LIST(async_event);
|
|
|
struct async_event {
|
|
|
- IDTYPE caller;
|
|
|
+ IDTYPE caller; /* thread installing this event */
|
|
|
LIST_TYPE(async_event) list;
|
|
|
void (*callback) (IDTYPE caller, void * arg);
|
|
|
void * arg;
|
|
|
- PAL_HANDLE object;
|
|
|
- unsigned long install_time;
|
|
|
- unsigned long expire_time;
|
|
|
+ PAL_HANDLE object; /* handle (async IO) to wait on */
|
|
|
+ uint64_t expire_time; /* alarm/timer to wait on */
|
|
|
};
|
|
|
-
|
|
|
DEFINE_LISTP(async_event);
|
|
|
static LISTP_TYPE(async_event) async_list;
|
|
|
|
|
|
-/* This variable can be read without the async_helper_lock held, but is always
|
|
|
- * modified with it held. */
|
|
|
+/* can be read without async_helper_lock but always written with lock held */
|
|
|
static enum { HELPER_NOTALIVE, HELPER_ALIVE } async_helper_state;
|
|
|
|
|
|
-static struct shim_thread * async_helper_thread;
|
|
|
-static AEVENTTYPE async_helper_event;
|
|
|
-
|
|
|
+static struct shim_thread* async_helper_thread;
|
|
|
static struct shim_lock async_helper_lock;
|
|
|
|
|
|
-/* Returns remaining usecs */
|
|
|
-int64_t install_async_event (PAL_HANDLE object, unsigned long time,
|
|
|
- void (*callback) (IDTYPE caller, void * arg),
|
|
|
- void * arg)
|
|
|
-{
|
|
|
- struct async_event * event =
|
|
|
- malloc(sizeof(struct async_event));
|
|
|
+static AEVENTTYPE install_new_event;
|
|
|
|
|
|
- unsigned long install_time = DkSystemTimeQuery();
|
|
|
- int64_t rv = 0;
|
|
|
+static int create_async_helper(void);
|
|
|
|
|
|
- debug("install async event at %lu\n", install_time);
|
|
|
+/* Threads register async events like alarm(), setitimer(), ioctl(FIOASYNC)
|
|
|
+ * using this function. These events are enqueued in async_list and delivered
|
|
|
+ * to Async Helper thread by triggering install_new_event. When event is
|
|
|
+ * triggered in Async Helper thread, the corresponding event's callback with
|
|
|
+ * arguments `arg` is called. This callback typically sends a signal to the
|
|
|
+ * thread who registered the event (saved in `event->caller`).
|
|
|
+ *
|
|
|
+ * We distinguish between alarm/timer events and async IO events:
|
|
|
+ * - alarm/timer events set object = NULL and time = seconds
|
|
|
+ * (time = 0 cancels all pending alarms/timers).
|
|
|
+ * - async IO events set object = handle and time = 0.
|
|
|
+ *
|
|
|
+ * Function returns remaining usecs for alarm/timer events (same as alarm())
|
|
|
+ * or 0 for async IO events. On error, it returns -1.
|
|
|
+ */
|
|
|
+int64_t install_async_event(PAL_HANDLE object, uint64_t time,
|
|
|
+ void (*callback) (IDTYPE caller, void * arg),
|
|
|
+ void * arg) {
|
|
|
+ /* if event happens on object, time must be zero */
|
|
|
+ assert(!object || (object && !time));
|
|
|
|
|
|
+ uint64_t now = DkSystemTimeQuery();
|
|
|
+ uint64_t max_prev_expire_time = now;
|
|
|
+
|
|
|
+ struct async_event* event = malloc(sizeof(struct async_event));
|
|
|
event->callback = callback;
|
|
|
event->arg = arg;
|
|
|
event->caller = get_cur_tid();
|
|
|
event->object = object;
|
|
|
- event->install_time = time ? install_time : 0;
|
|
|
- event->expire_time = time ? install_time + time : 0;
|
|
|
+ event->expire_time = time ? now + time : 0;
|
|
|
|
|
|
lock(&async_helper_lock);
|
|
|
|
|
|
- struct async_event * tmp;
|
|
|
+ if (!object) {
|
|
|
+ /* This is alarm() or setitimer() emulation, treat both according to
|
|
|
+ * alarm() syscall semantics: cancel any pending alarm/timer. */
|
|
|
+ struct async_event * tmp, * n;
|
|
|
+ LISTP_FOR_EACH_ENTRY_SAFE(tmp, n, &async_list, list) {
|
|
|
+ if (tmp->expire_time) {
|
|
|
+ /* this is a pending alarm/timer, cancel it and save its expiration time */
|
|
|
+ if (max_prev_expire_time < tmp->expire_time)
|
|
|
+ max_prev_expire_time = tmp->expire_time;
|
|
|
|
|
|
- LISTP_FOR_EACH_ENTRY(tmp, &async_list, list) {
|
|
|
- if (event->expire_time && tmp->expire_time > event->expire_time)
|
|
|
- break;
|
|
|
- }
|
|
|
+ LISTP_DEL(tmp, &async_list, list);
|
|
|
+ free(tmp);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- /*
|
|
|
- * man page of alarm system call :
|
|
|
- * DESCRIPTION
|
|
|
- * alarm() arranges for a SIGALRM signal to be delivered to the
|
|
|
- * calling process in seconds seconds.
|
|
|
- * If seconds is zero, any pending alarm is canceled.
|
|
|
- * In any event any previously set alarm() is canceled.
|
|
|
- */
|
|
|
- if (!LISTP_EMPTY(&async_list)) {
|
|
|
- tmp = LISTP_FIRST_ENTRY(&async_list, struct async_event, list);
|
|
|
- tmp = tmp->list.prev;
|
|
|
-
|
|
|
- rv = tmp->expire_time - install_time;
|
|
|
-
|
|
|
- /*
|
|
|
- * any previously set alarm() is canceled.
|
|
|
- * There should be exactly only one timer pending
|
|
|
- */
|
|
|
- LISTP_DEL(tmp, &async_list, list);
|
|
|
- free(tmp);
|
|
|
- } else {
|
|
|
- tmp = NULL;
|
|
|
+ if (!time) {
|
|
|
+ /* This is alarm(0), we cancelled all pending alarms/timers
|
|
|
+ * and user doesn't want to set a new alarm: we are done. */
|
|
|
+ free(event);
|
|
|
+ unlock(&async_helper_lock);
|
|
|
+ return max_prev_expire_time - now;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
INIT_LIST_HEAD(event, list);
|
|
|
- if (!time) // If seconds is zero, any pending alarm is canceled.
|
|
|
- free(event);
|
|
|
- else
|
|
|
- LISTP_ADD_TAIL(event, &async_list, list);
|
|
|
+ LISTP_ADD_TAIL(event, &async_list, list);
|
|
|
|
|
|
- if (async_helper_state == HELPER_NOTALIVE)
|
|
|
- create_async_helper();
|
|
|
+ if (async_helper_state == HELPER_NOTALIVE) {
|
|
|
+ int ret = create_async_helper();
|
|
|
+ if (ret < 0)
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
|
|
|
unlock(&async_helper_lock);
|
|
|
|
|
|
- set_event(&async_helper_event, 1);
|
|
|
- return rv;
|
|
|
+ debug("Installed async event at %lu\n", now);
|
|
|
+ set_event(&install_new_event, 1);
|
|
|
+ return max_prev_expire_time - now;
|
|
|
}
|
|
|
|
|
|
-int init_async (void)
|
|
|
-{
|
|
|
- /* This is early enough in init that we can write this variable without
|
|
|
- * the lock. */
|
|
|
+int init_async(void) {
|
|
|
+ /* early enough in init, can write global vars without the lock */
|
|
|
async_helper_state = HELPER_NOTALIVE;
|
|
|
create_lock(&async_helper_lock);
|
|
|
- create_event(&async_helper_event);
|
|
|
+ create_event(&install_new_event);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-#define IDLE_SLEEP_TIME 1000
|
|
|
-#define MAX_IDLE_CYCLES 100
|
|
|
-
|
|
|
-static void shim_async_helper (void * arg)
|
|
|
-{
|
|
|
+static void shim_async_helper(void * arg) {
|
|
|
struct shim_thread * self = (struct shim_thread *) arg;
|
|
|
if (!arg)
|
|
|
return;
|
|
@@ -140,7 +141,7 @@ static void shim_async_helper (void * arg)
|
|
|
__libc_tcb_t tcb;
|
|
|
allocate_tls(&tcb, false, self);
|
|
|
debug_setbuf(&tcb.shim_tcb, true);
|
|
|
- debug("set tcb to %p\n", &tcb);
|
|
|
+ debug("Set tcb to %p\n", &tcb);
|
|
|
|
|
|
lock(&async_helper_lock);
|
|
|
bool notme = (self != async_helper_thread);
|
|
@@ -152,127 +153,110 @@ static void shim_async_helper (void * arg)
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- debug("async helper thread started\n");
|
|
|
+ /* Assume async helper thread will not drain the stack that PAL provides,
|
|
|
+ * so for efficiency we don't swap the stack. */
|
|
|
+ debug("Async helper thread started\n");
|
|
|
|
|
|
- /* TSAI: we assume async helper thread will not drain the
|
|
|
- stack that PAL provides, so for efficiency, we don't
|
|
|
- swap any stack */
|
|
|
- unsigned long idle_cycles = 0;
|
|
|
- unsigned long latest_time;
|
|
|
- struct async_event * next_event = NULL;
|
|
|
- PAL_HANDLE async_event_handle = event_handle(&async_helper_event);
|
|
|
+ /* Simple heuristic to not burn cycles when no async events are installed:
|
|
|
+ * async helper thread sleeps IDLE_SLEEP_TIME for MAX_IDLE_CYCLES and
|
|
|
+ * if nothing happens, dies. It will be re-spawned if some thread wants
|
|
|
+ * to install a new event. */
|
|
|
+ uint64_t idle_cycles = 0;
|
|
|
|
|
|
- int object_list_size = 32, object_num;
|
|
|
- PAL_HANDLE polled;
|
|
|
- PAL_HANDLE * local_objects =
|
|
|
+ PAL_HANDLE polled = NULL;
|
|
|
+
|
|
|
+ /* init object_list so that it always contains at least install_new_event */
|
|
|
+ size_t object_list_size = 32;
|
|
|
+ PAL_HANDLE * object_list =
|
|
|
malloc(sizeof(PAL_HANDLE) * (1 + object_list_size));
|
|
|
- local_objects[0] = async_event_handle;
|
|
|
|
|
|
- goto update_status;
|
|
|
+ PAL_HANDLE install_new_event_hdl = event_handle(&install_new_event);
|
|
|
+ object_list[0] = install_new_event_hdl;
|
|
|
|
|
|
- /* This loop should be careful to use a barrier after sleeping
|
|
|
- * to ensure that the while breaks once async_helper_state changes.
|
|
|
- */
|
|
|
while (async_helper_state == HELPER_ALIVE) {
|
|
|
- unsigned long sleep_time;
|
|
|
- if (next_event) {
|
|
|
- sleep_time = next_event->expire_time - latest_time;
|
|
|
- idle_cycles = 0;
|
|
|
- } else if (object_num) {
|
|
|
- sleep_time = NO_TIMEOUT;
|
|
|
- idle_cycles = 0;
|
|
|
- } else {
|
|
|
- sleep_time = IDLE_SLEEP_TIME;
|
|
|
- idle_cycles++;
|
|
|
- }
|
|
|
-
|
|
|
- polled = DkObjectsWaitAny(object_num + 1, local_objects, sleep_time);
|
|
|
- COMPILER_BARRIER();
|
|
|
-
|
|
|
- if (!polled) {
|
|
|
- if (next_event) {
|
|
|
- debug("async event trigger at %lu\n",
|
|
|
- next_event->expire_time);
|
|
|
+ uint64_t now = DkSystemTimeQuery();
|
|
|
|
|
|
- next_event->callback(next_event->caller, next_event->arg);
|
|
|
-
|
|
|
- lock(&async_helper_lock);
|
|
|
- /* DEP: Events can only be on the async list */
|
|
|
- LISTP_DEL(next_event, &async_list, list);
|
|
|
- free(next_event);
|
|
|
- goto update_list;
|
|
|
- }
|
|
|
- continue;
|
|
|
+ if (polled == install_new_event_hdl) {
|
|
|
+ /* Some thread wants to install new event; this event is found
|
|
|
+ * in async_list below, so just re-init install_new_event. */
|
|
|
+ clear_event(&install_new_event);
|
|
|
}
|
|
|
|
|
|
- if (polled == async_event_handle) {
|
|
|
- clear_event(&async_helper_event);
|
|
|
-update_status:
|
|
|
- latest_time = DkSystemTimeQuery();
|
|
|
- if (async_helper_state == HELPER_NOTALIVE) {
|
|
|
- break;
|
|
|
- } else {
|
|
|
- lock(&async_helper_lock);
|
|
|
- goto update_list;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- struct async_event * tmp, * n;
|
|
|
-
|
|
|
lock(&async_helper_lock);
|
|
|
|
|
|
+ /* Iterate through all async IO events and alarm/timer events to:
|
|
|
+ * - call callbacks for all triggered events, and
|
|
|
+ * - repopulate object_list with async IO events (if any), and
|
|
|
+ * - find the next expiring alarm/timer (if any) */
|
|
|
+ uint64_t next_expire_time = 0;
|
|
|
+ size_t object_num = 0;
|
|
|
+
|
|
|
+ struct async_event * tmp, * n;
|
|
|
LISTP_FOR_EACH_ENTRY_SAFE(tmp, n, &async_list, list) {
|
|
|
- if (tmp->object == polled) {
|
|
|
- debug("async event trigger at %lu\n",
|
|
|
- latest_time);
|
|
|
+ /* First check if this event was triggered; note that IO events
|
|
|
+ * stay in the list whereas alarms/timers are fired only once. */
|
|
|
+ if (polled && tmp->object == polled) {
|
|
|
+ debug("Async IO event triggered at %lu\n", now);
|
|
|
unlock(&async_helper_lock);
|
|
|
tmp->callback(tmp->caller, tmp->arg);
|
|
|
lock(&async_helper_lock);
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-update_list:
|
|
|
- next_event = NULL;
|
|
|
- object_num = 0;
|
|
|
-
|
|
|
- if (!LISTP_EMPTY(&async_list)) {
|
|
|
- struct async_event * tmp, * n;
|
|
|
-
|
|
|
- LISTP_FOR_EACH_ENTRY_SAFE(tmp, n, &async_list, list) {
|
|
|
- if (tmp->object) {
|
|
|
- local_objects[object_num + 1] = tmp->object;
|
|
|
- object_num++;
|
|
|
- }
|
|
|
-
|
|
|
- if (!tmp->install_time)
|
|
|
- continue;
|
|
|
-
|
|
|
- if (tmp->expire_time > latest_time) {
|
|
|
- next_event = tmp;
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- debug("async event trigger at %lu (expire at %lu)\n",
|
|
|
- latest_time, tmp->expire_time);
|
|
|
+ } else if (tmp->expire_time && tmp->expire_time <= now) {
|
|
|
+ debug("Async alarm/timer triggered at %lu (expired at %lu)\n",
|
|
|
+ now, tmp->expire_time);
|
|
|
LISTP_DEL(tmp, &async_list, list);
|
|
|
unlock(&async_helper_lock);
|
|
|
tmp->callback(tmp->caller, tmp->arg);
|
|
|
free(tmp);
|
|
|
lock(&async_helper_lock);
|
|
|
+ continue;
|
|
|
}
|
|
|
|
|
|
- idle_cycles = 0;
|
|
|
+ /* Now re-add this IO event to the list or re-add this timer */
|
|
|
+ if (tmp->object) {
|
|
|
+ if (object_num == object_list_size) {
|
|
|
+ /* grow object_list to accomodate more objects */
|
|
|
+ PAL_HANDLE * tmp_array = malloc(
|
|
|
+ sizeof(PAL_HANDLE) * (1 + object_list_size * 2));
|
|
|
+ memcpy(tmp_array, object_list,
|
|
|
+ sizeof(PAL_HANDLE) * (1 + object_list_size));
|
|
|
+ object_list_size *= 2;
|
|
|
+ free(object_list);
|
|
|
+ object_list = tmp_array;
|
|
|
+ }
|
|
|
+ object_list[object_num + 1] = tmp->object;
|
|
|
+ object_num++;
|
|
|
+ } else if (tmp->expire_time && tmp->expire_time > now) {
|
|
|
+ if (!next_expire_time || next_expire_time > tmp->expire_time) {
|
|
|
+ /* use time of the next expiring alarm/timer */
|
|
|
+ next_expire_time = tmp->expire_time;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
unlock(&async_helper_lock);
|
|
|
|
|
|
- if (idle_cycles++ == MAX_IDLE_CYCLES) {
|
|
|
- debug("async helper thread reach helper cycle\n");
|
|
|
- /* walking away, if someone is issueing an event,
|
|
|
- they have to create another thread */
|
|
|
+ uint64_t sleep_time;
|
|
|
+ if (next_expire_time) {
|
|
|
+ sleep_time = next_expire_time - now;
|
|
|
+ idle_cycles = 0;
|
|
|
+ } else if (object_num) {
|
|
|
+ sleep_time = NO_TIMEOUT;
|
|
|
+ idle_cycles = 0;
|
|
|
+ } else {
|
|
|
+ /* no async IO events and no timers/alarms: thread is idling */
|
|
|
+ sleep_time = IDLE_SLEEP_TIME;
|
|
|
+ idle_cycles++;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (idle_cycles == MAX_IDLE_CYCLES) {
|
|
|
+ debug("Async helper thread has been idle for some time; stopping it\n");
|
|
|
break;
|
|
|
}
|
|
|
+
|
|
|
+ /* wait on async IO events + install_new_event + next expiring alarm/timer */
|
|
|
+ polled = DkObjectsWaitAny(object_num + 1, object_list, sleep_time);
|
|
|
+ /* ensure that while loop breaks on async_helper_state change */
|
|
|
+ COMPILER_BARRIER();
|
|
|
}
|
|
|
|
|
|
lock(&async_helper_lock);
|
|
@@ -280,66 +264,55 @@ update_list:
|
|
|
async_helper_thread = NULL;
|
|
|
unlock(&async_helper_lock);
|
|
|
put_thread(self);
|
|
|
- debug("async helper thread terminated\n");
|
|
|
- free(local_objects);
|
|
|
+ debug("Async helper thread terminated\n");
|
|
|
+ free(object_list);
|
|
|
|
|
|
DkThreadExit();
|
|
|
}
|
|
|
|
|
|
-/* This should be called with the async_helper_lock held */
|
|
|
-int create_async_helper (void)
|
|
|
-{
|
|
|
- int ret = 0;
|
|
|
-
|
|
|
+/* this should be called with the async_helper_lock held */
|
|
|
+static int create_async_helper(void) {
|
|
|
if (async_helper_state == HELPER_ALIVE)
|
|
|
return 0;
|
|
|
|
|
|
enable_locking();
|
|
|
|
|
|
- struct shim_thread * new = get_new_internal_thread();
|
|
|
+ struct shim_thread* new = get_new_internal_thread();
|
|
|
if (!new)
|
|
|
return -ENOMEM;
|
|
|
|
|
|
PAL_HANDLE handle = thread_create(shim_async_helper, new, 0);
|
|
|
|
|
|
if (!handle) {
|
|
|
- ret = -PAL_ERRNO;
|
|
|
async_helper_thread = NULL;
|
|
|
async_helper_state = HELPER_NOTALIVE;
|
|
|
put_thread(new);
|
|
|
- return ret;
|
|
|
+ return -PAL_ERRNO;
|
|
|
}
|
|
|
|
|
|
new->pal_handle = handle;
|
|
|
-
|
|
|
- /* Publish new and update the state once fully initialized */
|
|
|
async_helper_thread = new;
|
|
|
async_helper_state = HELPER_ALIVE;
|
|
|
-
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-/*
|
|
|
- * On success, the reference to the thread of async helper is returned with
|
|
|
- * reference count incremented.
|
|
|
- * It's caller the responsibility to wait for its exit and release the
|
|
|
- * final reference to free related resources.
|
|
|
- * It's problematic for the thread itself to release its resources which it's
|
|
|
- * using. For example stack.
|
|
|
- * So defer releasing it after its exit and make the releasing the caller
|
|
|
- * responsibility.
|
|
|
+/* On success, the reference to async helper thread is returned with refcount
|
|
|
+ * incremented. It is the responsibility of caller to wait for async helper's
|
|
|
+ * exit and then release the final reference to free related resources (it is
|
|
|
+ * problematic for the thread itself to release its own resources e.g. stack).
|
|
|
*/
|
|
|
-struct shim_thread * terminate_async_helper (void)
|
|
|
-{
|
|
|
+struct shim_thread* terminate_async_helper(void) {
|
|
|
if (async_helper_state != HELPER_ALIVE)
|
|
|
return NULL;
|
|
|
|
|
|
lock(&async_helper_lock);
|
|
|
- struct shim_thread * ret = async_helper_thread;
|
|
|
+ struct shim_thread* ret = async_helper_thread;
|
|
|
if (ret)
|
|
|
get_thread(ret);
|
|
|
async_helper_state = HELPER_NOTALIVE;
|
|
|
unlock(&async_helper_lock);
|
|
|
- set_event(&async_helper_event, 1);
|
|
|
+
|
|
|
+ /* force wake up of async helper thread so that it exits */
|
|
|
+ set_event(&install_new_event, 1);
|
|
|
return ret;
|
|
|
}
|