/* This will be called from vcore context, after the current thread has yielded * and is trying to block on sysc. Need to put it somewhere were we can wake it * up when the sysc is done. For now, we'll have the kernel send us an event * when the syscall is done. */ void pth_blockon_sysc(struct syscall *sysc) { int old_flags; bool need_to_restart = FALSE; uint32_t vcoreid = vcore_id(); assert(current_uthread->state == UT_BLOCKED); /* rip from the active queue */ struct mcs_lock_qnode local_qn = {0}; struct pthread_tcb *pthread = (struct pthread_tcb*)current_uthread; mcs_lock_notifsafe(&queue_lock, &local_qn); threads_active--; TAILQ_REMOVE(&active_queue, pthread, next); mcs_unlock_notifsafe(&queue_lock, &local_qn); /* Set things up so we can wake this thread up later */ sysc->u_data = current_uthread; /* Put the uthread on the pending list. Note the ordering. We must be on * the list before we register the ev_q. All sysc's must be tracked before * we tell the kernel to signal us. */ TAILQ_INSERT_TAIL(&sysc_mgmt[vcoreid].pending_syscs, pthread, next); /* Safety: later we'll make sure we restart on the core we slept on */ pthread->vcoreid = vcoreid; /* Register our vcore's syscall ev_q to hear about this syscall. */ if (!register_evq(sysc, &sysc_mgmt[vcoreid].ev_q)) { /* Lost the race with the call being done. The kernel won't send the * event. Just restart him. */ restart_thread(sysc); } /* GIANT WARNING: do not touch the thread after this point. */ }
/* This handler is usually run in vcore context, though I can imagine it being * called by a uthread in some other threading library. */ static void pth_handle_syscall(struct event_msg *ev_msg, unsigned int ev_type, bool overflow) { struct syscall *sysc; assert(in_vcore_context()); if (overflow) { handle_sysc_overflow(); } if (!ev_msg) { /* Probably a bug somewhere if we had no ev_msg and no overflow */ if (!overflow) printf("[pthread] crap, no ev_msg!!\n"); return; } sysc = ev_msg->ev_arg3; assert(sysc); restart_thread(sysc); }
/* This handler is usually run in vcore context, though I can imagine it being * called by a uthread in some other threading library. */ static void pth_handle_syscall(struct event_msg *ev_msg, unsigned int ev_type, void *data) { struct syscall *sysc; assert(in_vcore_context()); /* if we just got a bit (not a msg), it should be because the process is * still an SCP and hasn't started using the MCP ev_q yet (using the simple * ev_q and glibc's blockon) or because the bit is still set from an old * ev_q (blocking syscalls from before we could enter vcore ctx). Either * way, just return. Note that if you screwed up the pth ev_q and made it * NO_MSG, you'll never notice (we used to assert(ev_msg)). */ if (!ev_msg) return; /* It's a bug if we don't have a msg (we're handling a syscall bit-event) */ assert(ev_msg); /* Get the sysc from the message and just restart it */ sysc = ev_msg->ev_arg3; assert(sysc); restart_thread(sysc); }
/* This will be called from vcore context, after the current thread has yielded * and is trying to block on sysc. Need to put it somewhere were we can wake it * up when the sysc is done. For now, we'll have the kernel send us an event * when the syscall is done. */ static void pth_thread_blockon_sysc(struct uthread *uthread, void *syscall) { struct syscall *sysc = (struct syscall*)syscall; int old_flags; uint32_t vcoreid = vcore_id(); /* rip from the active queue */ struct pthread_tcb *pthread = (struct pthread_tcb*)uthread; pthread->state = PTH_BLK_SYSC; mcs_pdr_lock(&queue_lock); threads_active--; TAILQ_REMOVE(&active_queue, pthread, tq_next); mcs_pdr_unlock(&queue_lock); /* Set things up so we can wake this thread up later */ sysc->u_data = uthread; /* Register our vcore's syscall ev_q to hear about this syscall. */ if (!register_evq(sysc, sysc_mgmt[vcoreid].ev_q)) { /* Lost the race with the call being done. The kernel won't send the * event. Just restart him. */ restart_thread(sysc); } /* GIANT WARNING: do not touch the thread after this point. */ }
static void handle_page_fault(struct uthread *uthread, unsigned int err, unsigned long aux) { struct pthread_tcb *pthread = (struct pthread_tcb*)uthread; if (!(err & PF_VMR_BACKED)) { __pthread_signal_and_restart(pthread, SIGSEGV, SEGV_MAPERR, (void*)aux); } else { /* stitching for the event handler. sysc -> uth, uth -> sysc */ uthread->local_sysc.u_data = uthread; uthread->sysc = &uthread->local_sysc; pthread->state = PTH_BLK_SYSC; /* one downside is that we'll never check the return val of the syscall. if * we errored out, we wouldn't know til we PF'd again, and inspected the old * retval/err and other sysc fields (make sure the PF is on the same addr, * etc). could run into this issue on truncated files too. */ syscall_async(&uthread->local_sysc, SYS_populate_va, aux, 1); if (!register_evq(&uthread->local_sysc, sysc_mgmt[vcore_id()].ev_q)) { /* Lost the race with the call being done. The kernel won't send the * event. Just restart him. */ restart_thread(&uthread->local_sysc); } } }
void cancel_test(void) { pthread_t waiter; void *result; int status; /* Test 1: Normal Cancel *********************************************/ /* Start the waiter thread */ printf("cancel_test: Test 1: Normal Cancelation\n"); printf("cancel_test: Starting thread\n"); start_thread(&waiter, 1); /* Then cancel it. It should be in the pthread_cond_wait now */ printf("cancel_test: Canceling thread\n"); status = pthread_cancel(waiter); if (status != 0) { printf("cancel_test: ERROR pthread_cancel failed, status=%d\n", status); } /* Then join to the thread to pick up the result (if we don't do * we will have a memory leak!) */ printf("cancel_test: Joining\n"); status = pthread_join(waiter, &result); if (status != 0) { printf("cancel_test: ERROR pthread_join failed, status=%d\n", status); } else { printf("cancel_test: waiter exited with result=%p\n", result); if (result != PTHREAD_CANCELED) { printf("cancel_test: ERROR expected result=%p\n", PTHREAD_CANCELED); } else { printf("cancel_test: PASS thread terminated with PTHREAD_CANCELED\n"); } } /* Test 2: Cancel Detached Thread ************************************/ printf("cancel_test: Test 2: Cancelation of detached thread\n"); printf("cancel_test: Re-starting thread\n"); restart_thread(&waiter, 1); /* Detach the thread */ status = pthread_detach(waiter); if (status != 0) { printf("cancel_test: ERROR pthread_detach, status=%d\n", status); } /* Then cancel it. It should be in the pthread_cond_wait now */ printf("cancel_test: Canceling thread\n"); status = pthread_cancel(waiter); if (status != 0) { printf("cancel_test: ERROR pthread_cancel failed, status=%d\n", status); } /* Join should now fail */ printf("cancel_test: Joining\n"); status = pthread_join(waiter, &result); if (status == 0) { printf("cancel_test: ERROR pthread_join succeeded\n"); } else if (status != ESRCH) { printf("cancel_test: ERROR pthread_join failed but with wrong status=%d\n", status); } else { printf("cancel_test: PASS pthread_join failed with status=ESRCH\n"); } /* Test 3: Non-cancelable threads ************************************/ printf("cancel_test: Test 3: Non-cancelable threads\n"); printf("cancel_test: Re-starting thread (non-cancelable)\n"); restart_thread(&waiter, 0); /* Then cancel it. It should be in the pthread_cond_wait now. The * behavior here is non-standard: when the thread is at a cancelation * point, it should be cancelable, even when cancelation is disable. * * The cancelation should succeed, because the cancelation is pending. */ printf("cancel_test: Canceling thread\n"); status = pthread_cancel(waiter); if (status != 0) { printf("cancel_test: ERROR pthread_cancel failed, status=%d\n", status); } /* Signal the thread. It should wake up and restore the cancelable state. * When the cancelable state is re-enabled, the thread should be canceled. */ status = pthread_mutex_lock(&mutex); if (status != 0) { printf("cancel_test: ERROR pthread_mutex_lock failed, status=%d\n", status); } status = pthread_cond_signal(&cond); if (status != 0) { printf("cancel_test: ERROR pthread_cond_signal failed, status=%d\n", status); } status = pthread_mutex_unlock(&mutex); if (status != 0) { printf("cancel_test: ERROR pthread_mutex_unlock failed, status=%d\n", status); } /* Then join to the thread to pick up the result (if we don't do * we will have a memory leak!) */ printf("cancel_test: Joining\n"); status = pthread_join(waiter, &result); if (status != 0) { printf("cancel_test: ERROR pthread_join failed, status=%d\n", status); } else { printf("cancel_test: waiter exited with result=%p\n", result); if (result != PTHREAD_CANCELED) { printf("cancel_test: ERROR expected result=%p\n", PTHREAD_CANCELED); } else { printf("cancel_test: PASS thread terminated with PTHREAD_CANCELED\n"); } } }