void wakeBlockingQueue(Capability *cap, StgBlockingQueue *bq) { MessageBlackHole *msg; const StgInfoTable *i; ASSERT(bq->header.info == &stg_BLOCKING_QUEUE_DIRTY_info || bq->header.info == &stg_BLOCKING_QUEUE_CLEAN_info ); for (msg = bq->queue; msg != (MessageBlackHole*)END_TSO_QUEUE; msg = msg->link) { i = msg->header.info; if (i != &stg_IND_info) { ASSERT(i == &stg_MSG_BLACKHOLE_info); tryWakeupThread(cap,msg->tso); } } // overwrite the BQ with an indirection so it will be // collected at the next GC. #if defined(DEBUG) && !defined(THREADED_RTS) // XXX FILL_SLOP, but not if THREADED_RTS because in that case // another thread might be looking at this BLOCKING_QUEUE and // checking the owner field at the same time. bq->bh = 0; bq->queue = 0; bq->owner = 0; #endif OVERWRITE_INFO(bq, &stg_IND_info); }
nat throwToMsg (Capability *cap, MessageThrowTo *msg) { StgWord status; StgTSO *target = msg->target; Capability *target_cap; goto check_target; retry: write_barrier(); debugTrace(DEBUG_sched, "throwTo: retrying..."); check_target: ASSERT(target != END_TSO_QUEUE); // Thread already dead? if (target->what_next == ThreadComplete || target->what_next == ThreadKilled) { return THROWTO_SUCCESS; } debugTraceCap(DEBUG_sched, cap, "throwTo: from thread %lu to thread %lu", (unsigned long)msg->source->id, (unsigned long)msg->target->id); #ifdef DEBUG traceThreadStatus(DEBUG_sched, target); #endif target_cap = target->cap; if (target->cap != cap) { throwToSendMsg(cap, target_cap, msg); return THROWTO_BLOCKED; } status = target->why_blocked; switch (status) { case NotBlocked: { if ((target->flags & TSO_BLOCKEX) == 0) { // It's on our run queue and not blocking exceptions raiseAsync(cap, target, msg->exception, rtsFalse, NULL); return THROWTO_SUCCESS; } else { blockedThrowTo(cap,target,msg); return THROWTO_BLOCKED; } } case BlockedOnMsgThrowTo: { const StgInfoTable *i; MessageThrowTo *m; m = target->block_info.throwto; // target is local to this cap, but has sent a throwto // message to another cap. // // The source message is locked. We need to revoke the // target's message so that we can raise the exception, so // we attempt to lock it. // There's a possibility of a deadlock if two threads are both // trying to throwTo each other (or more generally, a cycle of // threads). To break the symmetry we compare the addresses // of the MessageThrowTo objects, and the one for which m < // msg gets to spin, while the other can only try to lock // once, but must then back off and unlock both before trying // again. if (m < msg) { i = lockClosure((StgClosure *)m); } else { i = tryLockClosure((StgClosure *)m); if (i == NULL) { // debugBelch("collision\n"); throwToSendMsg(cap, target->cap, msg); return THROWTO_BLOCKED; } } if (i == &stg_MSG_NULL_info) { // we know there's a MSG_TRY_WAKEUP on the way, so we // might as well just do it now. The message will // be a no-op when it arrives. unlockClosure((StgClosure*)m, i); tryWakeupThread(cap, target); goto retry; } if (i != &stg_MSG_THROWTO_info) { // if it's a MSG_NULL, this TSO has been woken up by another Cap unlockClosure((StgClosure*)m, i); goto retry; } if ((target->flags & TSO_BLOCKEX) && ((target->flags & TSO_INTERRUPTIBLE) == 0)) { unlockClosure((StgClosure*)m, i); blockedThrowTo(cap,target,msg); return THROWTO_BLOCKED; } // nobody else can wake up this TSO after we claim the message doneWithMsgThrowTo(m); raiseAsync(cap, target, msg->exception, rtsFalse, NULL); return THROWTO_SUCCESS; } case BlockedOnMVar: case BlockedOnMVarRead: { /* To establish ownership of this TSO, we need to acquire a lock on the MVar that it is blocked on. */ StgMVar *mvar; StgInfoTable *info USED_IF_THREADS; mvar = (StgMVar *)target->block_info.closure; // ASSUMPTION: tso->block_info must always point to a // closure. In the threaded RTS it does. switch (get_itbl((StgClosure *)mvar)->type) { case MVAR_CLEAN: case MVAR_DIRTY: break; default: goto retry; } info = lockClosure((StgClosure *)mvar); // we have the MVar, let's check whether the thread // is still blocked on the same MVar. if ((target->why_blocked != BlockedOnMVar && target->why_blocked != BlockedOnMVarRead) || (StgMVar *)target->block_info.closure != mvar) { unlockClosure((StgClosure *)mvar, info); goto retry; } if (target->_link == END_TSO_QUEUE) { // the MVar operation has already completed. There is a // MSG_TRY_WAKEUP on the way, but we can just wake up the // thread now anyway and ignore the message when it // arrives. unlockClosure((StgClosure *)mvar, info); tryWakeupThread(cap, target); goto retry; } if ((target->flags & TSO_BLOCKEX) && ((target->flags & TSO_INTERRUPTIBLE) == 0)) { blockedThrowTo(cap,target,msg); unlockClosure((StgClosure *)mvar, info); return THROWTO_BLOCKED; } else { // revoke the MVar operation removeFromMVarBlockedQueue(target); raiseAsync(cap, target, msg->exception, rtsFalse, NULL); unlockClosure((StgClosure *)mvar, info); return THROWTO_SUCCESS; } } case BlockedOnBlackHole: { if (target->flags & TSO_BLOCKEX) { // BlockedOnBlackHole is not interruptible. blockedThrowTo(cap,target,msg); return THROWTO_BLOCKED; } else { // Revoke the message by replacing it with IND. We're not // locking anything here, so we might still get a TRY_WAKEUP // message from the owner of the blackhole some time in the // future, but that doesn't matter. ASSERT(target->block_info.bh->header.info == &stg_MSG_BLACKHOLE_info); OVERWRITE_INFO(target->block_info.bh, &stg_IND_info); raiseAsync(cap, target, msg->exception, rtsFalse, NULL); return THROWTO_SUCCESS; } } case BlockedOnSTM: lockTSO(target); // Unblocking BlockedOnSTM threads requires the TSO to be // locked; see STM.c:unpark_tso(). if (target->why_blocked != BlockedOnSTM) { unlockTSO(target); goto retry; } if ((target->flags & TSO_BLOCKEX) && ((target->flags & TSO_INTERRUPTIBLE) == 0)) { blockedThrowTo(cap,target,msg); unlockTSO(target); return THROWTO_BLOCKED; } else { raiseAsync(cap, target, msg->exception, rtsFalse, NULL); unlockTSO(target); return THROWTO_SUCCESS; } case BlockedOnCCall_Interruptible: #ifdef THREADED_RTS { Task *task = NULL; // walk suspended_ccalls to find the correct worker thread InCall *incall; for (incall = cap->suspended_ccalls; incall != NULL; incall = incall->next) { if (incall->suspended_tso == target) { task = incall->task; break; } } if (task != NULL) { blockedThrowTo(cap, target, msg); if (!((target->flags & TSO_BLOCKEX) && ((target->flags & TSO_INTERRUPTIBLE) == 0))) { interruptWorkerTask(task); } return THROWTO_BLOCKED; } else { debugTraceCap(DEBUG_sched, cap, "throwTo: could not find worker thread to kill"); } // fall to next } #endif case BlockedOnCCall: blockedThrowTo(cap,target,msg); return THROWTO_BLOCKED; #ifndef THREADEDED_RTS case BlockedOnRead: case BlockedOnWrite: case BlockedOnDelay: #if defined(mingw32_HOST_OS) case BlockedOnDoProc: #endif if ((target->flags & TSO_BLOCKEX) && ((target->flags & TSO_INTERRUPTIBLE) == 0)) { blockedThrowTo(cap,target,msg); return THROWTO_BLOCKED; } else { removeFromQueues(cap,target); raiseAsync(cap, target, msg->exception, rtsFalse, NULL); return THROWTO_SUCCESS; } #endif case ThreadMigrating: // if is is ThreadMigrating and tso->cap is ours, then it // *must* be migrating *to* this capability. If it were // migrating away from the capability, then tso->cap would // point to the destination. // // There is a MSG_WAKEUP in the message queue for this thread, // but we can just do it preemptively: tryWakeupThread(cap, target); // and now retry, the thread should be runnable. goto retry; default: barf("throwTo: unrecognised why_blocked (%d)", target->why_blocked); } barf("throwTo"); }