Skip to content

Commit

Permalink
Introduce SleepPhase.
Browse files Browse the repository at this point in the history
Thread semantics are ad hoc in Ruby so we make a best effort to mimic them
where possible. The behavior of process exit when one or more threads are
suspended in a sleep state is not well-defined. We assume that no sleeping
thread should block the process from exiting. We introduce a specific "sleep
phase" as one state a thread may be in. We then ignore sleeping threads when
executing a process exit.

Unfortunately, this is more complex than it appears on its face. While
executing a process exit, we are essentially racing any sleeping threads that
may wake up and attempt to access resources that are being destroyed (something
like one of those scenes from Inception with the buildings crumbling around the
participants). We cannot waking thread proceed once process exit has started,
so we permanently lock a mutex that every waking thread must acquire before
progressing.

Ultimately, these lock resources will need to be in the program's data segment,
so they are static memory, not dynamically allocated, as they are now.
  • Loading branch information
brixen committed Jan 24, 2016
1 parent 03f8943 commit 903aae1
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 7 deletions.
4 changes: 2 additions & 2 deletions vm/park.cpp
Expand Up @@ -20,7 +20,7 @@ namespace rubinius {
Object* result = cNil;
while(!wake_) {
{
UnmanagedPhase unmanaged(state);
SleepPhase sleeping(state);

cond_.wait(mutex_);
}
Expand Down Expand Up @@ -50,7 +50,7 @@ namespace rubinius {

while(!wake_) {
{
UnmanagedPhase unmanaged(state);
SleepPhase sleeping(state);

utilities::thread::Code status = cond_.wait_until(mutex_, ts);
if(status == utilities::thread::cTimedOut) {
Expand Down
39 changes: 34 additions & 5 deletions vm/thread_nexus.cpp
Expand Up @@ -21,6 +21,10 @@ namespace rubinius {
return (vm->thread_phase() & cBlocking) == cBlocking;
}

bool ThreadNexus::sleeping_p(VM* vm) {
return (vm->thread_phase() & cSleeping) == cSleeping;
}

bool ThreadNexus::yielding_p(VM* vm) {
return (vm->thread_phase() & cYielding) == cYielding;
}
Expand Down Expand Up @@ -65,6 +69,7 @@ namespace rubinius {
stop_ = false;
threads_lock_.init();
lock_.init(true);
sleep_lock_.init();
wait_mutex_.init();
wait_condition_.init();

Expand Down Expand Up @@ -103,6 +108,8 @@ namespace rubinius {
return "cUnmanaged";
case ThreadNexus::cWaiting:
return "cWaiting";
case ThreadNexus::cSleeping:
return "cSleeping";
case ThreadNexus::cYielding:
return "cYielding";
case ThreadNexus::cBlocking:
Expand Down Expand Up @@ -221,9 +228,14 @@ namespace rubinius {
}

void ThreadNexus::waiting_lock(VM* vm) {
vm->set_thread_phase(ThreadNexus::cWaiting);
vm->set_thread_phase(cWaiting);
lock_.lock();
vm->set_thread_phase(ThreadNexus::cManaged);
vm->set_thread_phase(cManaged);
}

void ThreadNexus::sleep_lock(VM* vm) {
utilities::thread::Mutex::LockGuard guard(sleep_lock_);
vm->become_managed();
}

void ThreadNexus::lock(VM* vm) {
Expand Down Expand Up @@ -254,22 +266,39 @@ namespace rubinius {
}

void ThreadNexus::wait_till_alone(VM* vm) {
/* We block acquiring the sleep lock so that any waking thread that raced
* us to it will finish waking up. Any sleeping thread that wakes up after
* we get the lock will block until the process exits because once we
* acquire the sleep lock, we never unlock it.
*/
sleep_lock_.lock();

vm->set_thread_phase(cWaiting);

uint64_t ns = 0;

// TODO: add thread_stop signalling
while(true) {
bool blocking = false;

{
utilities::thread::SpinLock::LockGuard guard(threads_lock_);

if(threads_.size() == 1) {
if(threads_.front()->as_vm() == vm) {
return;
for(ThreadList::iterator i = threads_.begin();
i != threads_.end();
++i)
{
if(VM* other_vm = (*i)->as_vm()) {
if(vm == other_vm || sleeping_p(other_vm)) continue;

blocking = true;
break;
}
}
}

if(!blocking) return;

ns += delay();

detect_halt_deadlock(ns, vm);
Expand Down
5 changes: 5 additions & 0 deletions vm/thread_nexus.hpp
Expand Up @@ -23,6 +23,7 @@ namespace rubinius {
bool stop_;
utilities::thread::SpinLock threads_lock_;
utilities::thread::Mutex lock_;
utilities::thread::Mutex sleep_lock_;
utilities::thread::Mutex wait_mutex_;
utilities::thread::Condition wait_condition_;
ThreadList threads_;
Expand All @@ -35,13 +36,15 @@ namespace rubinius {
cBlocking = 0x04,
cUnmanaged = 0x81,
cWaiting = 0x82,
cSleeping = 0x84,
cYielding = 0x80
};

ThreadNexus()
: stop_(false)
, threads_lock_()
, lock_(true)
, sleep_lock_()
, wait_mutex_()
, wait_condition_()
, threads_()
Expand Down Expand Up @@ -77,6 +80,7 @@ namespace rubinius {

bool blocking_p(VM* vm);
void blocking(VM* vm);
bool sleeping_p(VM* vm);
bool yielding_p(VM* vm);
void yielding(VM* vm);
bool serialized_p(VM* vm);
Expand All @@ -96,6 +100,7 @@ namespace rubinius {
void lock(VM* vm);
void waiting_lock(VM* vm);
void managed_lock(VM* vm);
void sleep_lock(VM* vm);
bool stop_lock(VM* vm);

void wait_till_alone(VM* vm);
Expand Down
15 changes: 15 additions & 0 deletions vm/thread_phase.hpp
Expand Up @@ -27,6 +27,21 @@ namespace rubinius {
}
};

class SleepPhase {
State* state_;

public:
SleepPhase(STATE)
: state_(state)
{
state->vm()->set_thread_phase(ThreadNexus::cSleeping);
}

~SleepPhase() {
state_->shared().thread_nexus()->sleep_lock(state_->vm());
}
};

class ManagedPhase {
State* state_;

Expand Down

0 comments on commit 903aae1

Please sign in to comment.