Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Healthy living through better thread management

* A whole bunch of stuff to manage and checkpoint the LLVM background
  compiling thread. This keeps us from getting bizarre error output
  from LLVM and from deadlocking it's internal mutexes.
  • Loading branch information...
commit 0c8eb66455257d3e3eded68c99d0040901abbb0d 1 parent dfc6a94
Evan Phoenix authored
View
50 vm/builtin/system.cpp
@@ -46,6 +46,8 @@
#include "configuration.hpp"
+#include "llvm/jit.hpp"
+
namespace rubinius {
@@ -85,8 +87,20 @@ namespace rubinius {
}
/* @todo Improve error messages */
- Object* System::vm_exec(VM* state, String* path, Array* args)
- {
+ Object* System::vm_exec(STATE, String* path, Array* args) {
+
+ // Some system (darwin) don't let execvp work if there is more
+ // than one thread running. So we kill off any background LLVM
+ // thread here.
+
+#ifdef ENABLE_LLVM
+ LLVMState::shutdown(state);
+#endif
+
+ // TODO Need to stop and kill off any ruby threads!
+ // We haven't run into this because exec is almost always called
+ // after fork(), which pulls over just one thread anyway.
+
std::size_t argc = args->size();
/* execvp() requires a NULL as last element */
@@ -100,9 +114,8 @@ namespace rubinius {
(void) ::execvp(path->c_str(), &argv[0]); /* std::vector is contiguous. --rue */
/* execvp() returning means it failed. */
- Exception::errno_error(state, "execvp() failed!");
-
- return Qnil;
+ Exception::errno_error(state, "execvp(2) failed");
+ return NULL;
}
Object* System::vm_wait_pid(STATE, Fixnum* pid_obj, Object* no_hang) {
@@ -157,6 +170,10 @@ namespace rubinius {
{
int result = 0;
+#ifdef ENABLE_LLVM
+ LLVMState::pause(state);
+#endif
+
// Unlock the lock and relock, that way both the parent and
// child relock independently, and correctly hold the lock.
{
@@ -164,13 +181,26 @@ namespace rubinius {
result = ::fork();
}
- if ( -1 == result ) {
- Exception::errno_error(state, "fork() failed!");
+ // We're in the child...
+ if(result == 0) {
+ /* @todo any other re-initialisation needed? */
+
+ state->shared.reinit();
+
+ // Re-initialize LLVM
+#ifdef ENABLE_LLVM
+ LLVMState::on_fork(state);
+#endif
+ } else {
+#ifdef ENABLE_LLVM
+ LLVMState::unpause(state);
+#endif
}
- /* @todo EVFLAG_FORKCHECK should remove need for any maintenance
- * here, but is there any other re-initialisation needed?
- */
+ if(result == -1) {
+ Exception::errno_error(state, "fork(2) failed");
+ return NULL;
+ }
return Fixnum::from(result);
}
View
4 vm/drivers/cli.cpp
@@ -91,7 +91,6 @@ int main(int argc, char** argv) {
env.enable_preemption();
env.start_signals();
env.run_file(loader);
- return env.exit_code();
} catch(Assertion *e) {
std::cout << "VM Assertion:" << std::endl;
@@ -143,4 +142,7 @@ int main(int argc, char** argv) {
} catch(...) {
std::cout << "Unknown exception detected." << std::endl;
}
+
+ env.halt();
+ return env.exit_code();
}
View
11 vm/environment.cpp
@@ -14,6 +14,7 @@
#include "builtin/symbol.hpp"
#include "builtin/module.hpp"
#include "builtin/taskprobe.hpp"
+#include "llvm/jit.hpp"
#include "signal.hpp"
#include "object_utils.hpp"
@@ -158,13 +159,19 @@ namespace rubinius {
std::cout << msg.str() << "\n";
exc->print_locations(state);
Assertion::raise(msg.str().c_str());
- } else if(state->thread_state()->raise_reason() == cExit) {
- exit(as<Fixnum>(state->thread_state()->raise_value())->to_native());
}
delete cf;
}
+ void Environment::halt() {
+#ifdef ENABLE_LLVM
+ LLVMState::shutdown(state);
+#endif
+
+ state->shared.stop_the_world();
+ }
+
int Environment::exit_code() {
if(state->thread_state()->raise_reason() == cExit) {
if(Fixnum* fix = try_as<Fixnum>(state->thread_state()->raise_value())) {
View
1  vm/environment.hpp
@@ -30,6 +30,7 @@ namespace rubinius {
void run_file(std::string path);
void enable_preemption();
void boot_vm();
+ void halt();
int exit_code();
void start_signals();
};
View
134 vm/llvm/jit.cpp
@@ -35,6 +35,26 @@ namespace rubinius {
return state->shared.llvm_state;
}
+ void LLVMState::shutdown(STATE) {
+ if(!state->shared.llvm_state) return;
+ state->shared.llvm_state->shutdown_i();
+ }
+
+ void LLVMState::on_fork(STATE) {
+ if(!state->shared.llvm_state) return;
+ state->shared.llvm_state->on_fork_i();
+ }
+
+ void LLVMState::pause(STATE) {
+ if(!state->shared.llvm_state) return;
+ state->shared.llvm_state->pause_i();
+ }
+
+ void LLVMState::unpause(STATE) {
+ if(!state->shared.llvm_state) return;
+ state->shared.llvm_state->unpause_i();
+ }
+
const llvm::Type* LLVMState::ptr_type(std::string name) {
std::string full_name = std::string("struct.rubinius::") + name;
return PointerType::getUnqual(
@@ -42,26 +62,93 @@ namespace rubinius {
}
class BackgroundCompilerThread : public thread::Thread {
- thread::Mutex list_mutex_;
+ enum State {
+ cUnknown,
+ cRunning,
+ cPaused,
+ cIdle
+ };
+
+ thread::Mutex mutex_;
std::list<BackgroundCompileRequest*> pending_requests_;
thread::Condition condition_;
+ thread::Condition pause_condition_;
LLVMState* ls_;
bool show_machine_code_;
+ State state;
+ bool stop_;
+ bool pause_;
+ bool paused_;
+
public:
BackgroundCompilerThread(LLVMState* ls)
: ls_(ls)
+ , state(cUnknown)
+ , stop_(false)
+ , pause_(false)
+ , paused_(false)
{
show_machine_code_ = ls->jit_dump_code() & cMachineCode;
}
void add(BackgroundCompileRequest* req) {
- thread::Mutex::LockGuard guard(list_mutex_);
+ thread::Mutex::LockGuard guard(mutex_);
pending_requests_.push_back(req);
condition_.signal();
}
+ void stop() {
+ {
+ thread::Mutex::LockGuard guard(mutex_);
+ stop_ = true;
+
+ if(state == cIdle) {
+ condition_.signal();
+ }
+ }
+
+ join();
+ }
+
+ void pause() {
+ thread::Mutex::LockGuard guard(mutex_);
+
+ // it's idle, ie paused.
+ if(state == cIdle || state == cPaused) return;
+
+ pause_ = true;
+
+ while(!paused_) {
+ pause_condition_.wait(mutex_);
+ }
+ }
+
+ void unpause() {
+ thread::Mutex::LockGuard guard(mutex_);
+
+ // idle, just waiting for more work, ok, thats fine.
+ if(state != cPaused) return;
+
+ pause_ = false;
+
+ condition_.signal();
+ }
+
+ void restart() {
+ mutex_.init();
+ condition_.init();
+ pause_condition_.init();
+
+ state = cUnknown;
+ stop_ = false;
+ pause_ = false;
+ paused_ = false;
+
+ run();
+ }
+
virtual void perform() {
for(;;) { // forever
@@ -69,16 +156,39 @@ namespace rubinius {
// Lock, wait, get a request, unlock
{
- thread::Mutex::LockGuard guard(list_mutex_);
+ thread::Mutex::LockGuard guard(mutex_);
+
+ // If we've been asked to stop, do so now.
+ if(stop_) return;
+
+ if(pause_) {
+ state = cPaused;
+
+ paused_ = true;
+ pause_condition_.signal();
+
+ while(pause_) {
+ condition_.wait(mutex_);
+ }
+
+ state = cUnknown;
+ paused_ = false;
+ }
while(pending_requests_.size() == 0) {
+ state = cIdle;
+
// unlock and wait...
- condition_.wait(list_mutex_);
+ condition_.wait(mutex_);
+
+ if(stop_) return;
}
// now locked again, shift a request
req = pending_requests_.front();
pending_requests_.pop_front();
+
+ state = cRunning;
}
// mutex now unlock, allowing others to push more requests
@@ -199,6 +309,22 @@ namespace rubinius {
background_thread_->run();
}
+ void LLVMState::shutdown_i() {
+ background_thread_->stop();
+ }
+
+ void LLVMState::on_fork_i() {
+ background_thread_->restart();
+ }
+
+ void LLVMState::pause_i() {
+ background_thread_->pause();
+ }
+
+ void LLVMState::unpause_i() {
+ background_thread_->unpause();
+ }
+
Symbol* LLVMState::symbol(const char* sym) {
return symbols_.lookup(sym);
}
View
9 vm/llvm/jit.hpp
@@ -52,6 +52,10 @@ namespace rubinius {
public:
static LLVMState* get(STATE);
+ static void shutdown(STATE);
+ static void on_fork(STATE);
+ static void pause(STATE);
+ static void unpause(STATE);
LLVMState(STATE);
@@ -91,6 +95,11 @@ namespace rubinius {
void compile_soon(STATE, VMMethod* vmm);
Symbol* symbol(const char* sym);
+
+ void shutdown_i();
+ void on_fork_i();
+ void pause_i();
+ void unpause_i();
};
class LLVMCompiler {
View
14 vm/shared_state.cpp
@@ -23,6 +23,16 @@ namespace rubinius {
, should_stop_(false)
{}
+ // Called after a fork(), when we know we're alone again, to get
+ // everything back in the proper order.
+ void reinit() {
+ mutex_.init();
+ waiting_to_stop_.init();
+ waiting_to_run_.init();
+ pending_threads_ = 1;
+ should_stop_ = false;
+ }
+
// If called when the GC is waiting to run,
// wait until the GC tells us it's ok to continue.
// always increments pending_threads_ at the end.
@@ -161,6 +171,10 @@ namespace rubinius {
}
}
+ void SharedState::reinit() {
+ world_.reinit();
+ }
+
void SharedState::stop_the_world() {
world_.wait_til_alone();
}
View
1  vm/shared_state.hpp
@@ -129,6 +129,7 @@ namespace rubinius {
void add_profiler(VM* vm, profiler::Profiler* profiler);
void remove_profiler(VM* vm, profiler::Profiler* profiler);
+ void reinit();
void stop_the_world();
void restart_world();
void checkpoint();
View
16 vm/util/thread.hpp
@@ -135,9 +135,11 @@ namespace thread {
if(err != 0) {
if(err == EDEADLK) {
std::cout << "Thread deadlock!\n";
+ assert(0);
}
- assert(0);
+ // Ignore the other errors, since they mean there is no thread
+ // so we can consider us already joined to it.
}
}
@@ -270,13 +272,17 @@ namespace thread {
pthread_mutex_t native_;
public:
- Mutex() {
+ void init() {
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
assert(pthread_mutex_init(&native_, &attr) == 0);
}
+ Mutex() {
+ init();
+ }
+
~Mutex() {
int err = pthread_mutex_destroy(&native_);
if(err != 0) {
@@ -342,10 +348,14 @@ namespace thread {
pthread_cond_t native_;
public:
- Condition() {
+ void init() {
assert(pthread_cond_init(&native_, NULL) == 0);
}
+ Condition() {
+ init();
+ }
+
~Condition() {
assert(pthread_cond_destroy(&native_) == 0);
}
View
3  vm/vm.cpp
@@ -243,6 +243,9 @@ namespace rubinius {
void VM::collect(CallFrame* call_frame) {
this->set_call_frame(call_frame);
+ // Stops all other threads, so we're only here by ourselves.
+ StopTheWorld guard(this);
+
GCData gc_data(this);
om->collect_young(gc_data);
Please sign in to comment.
Something went wrong with that request. Please try again.