Basic multithreading support. The infinite loops test successfully maxes out the CPU.
This commit is contained in:
@@ -95,7 +95,7 @@ rust_start(uintptr_t main_fn, int argc, char **argv, void* crate_map) {
|
|||||||
|
|
||||||
dom->root_task->start(main_fn, (uintptr_t)args->args);
|
dom->root_task->start(main_fn, (uintptr_t)args->args);
|
||||||
|
|
||||||
int ret = dom->start_main_loop();
|
int ret = dom->start_main_loops(8);
|
||||||
delete args;
|
delete args;
|
||||||
kernel->destroy_domain(dom);
|
kernel->destroy_domain(dom);
|
||||||
kernel->join_all_domains();
|
kernel->join_all_domains();
|
||||||
|
|||||||
@@ -47,16 +47,14 @@ rust_dom::~rust_dom() {
|
|||||||
|
|
||||||
void
|
void
|
||||||
rust_dom::activate(rust_task *task) {
|
rust_dom::activate(rust_task *task) {
|
||||||
curr_task = task;
|
|
||||||
|
|
||||||
context ctx;
|
context ctx;
|
||||||
|
|
||||||
task->ctx.next = &ctx;
|
task->ctx.next = &ctx;
|
||||||
DLOG(this, task, "descheduling...");
|
DLOG(this, task, "descheduling...");
|
||||||
|
scheduler_lock.unlock();
|
||||||
task->ctx.swap(ctx);
|
task->ctx.swap(ctx);
|
||||||
|
scheduler_lock.lock();
|
||||||
DLOG(this, task, "task has returned");
|
DLOG(this, task, "task has returned");
|
||||||
|
|
||||||
curr_task = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -211,12 +209,15 @@ rust_dom::schedule_task() {
|
|||||||
// FIXME: in the face of failing tasks, this is not always right.
|
// FIXME: in the face of failing tasks, this is not always right.
|
||||||
// I(this, n_live_tasks() > 0);
|
// I(this, n_live_tasks() > 0);
|
||||||
if (running_tasks.length() > 0) {
|
if (running_tasks.length() > 0) {
|
||||||
size_t i = rand(&rctx);
|
size_t k = rand(&rctx);
|
||||||
i %= running_tasks.length();
|
// Look around for a runnable task, starting at k.
|
||||||
if (running_tasks[i]->yield_timer.has_timed_out()) {
|
for(size_t j = 0; j < running_tasks.length(); ++j) {
|
||||||
|
size_t i = (j + k) % running_tasks.length();
|
||||||
|
if (running_tasks[i]->can_schedule()) {
|
||||||
return (rust_task *)running_tasks[i];
|
return (rust_task *)running_tasks[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,15 +262,20 @@ rust_dom::log_state() {
|
|||||||
* drop to zero.
|
* drop to zero.
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
rust_dom::start_main_loop() {
|
rust_dom::start_main_loop(int id) {
|
||||||
|
scheduler_lock.lock();
|
||||||
|
|
||||||
// Make sure someone is watching, to pull us out of infinite loops.
|
// Make sure someone is watching, to pull us out of infinite loops.
|
||||||
rust_timer timer(this);
|
rust_timer timer(this);
|
||||||
|
|
||||||
DLOG(this, dom, "started domain loop");
|
DLOG(this, dom, "started domain loop %d", id);
|
||||||
|
|
||||||
while (number_of_live_tasks() > 0) {
|
while (number_of_live_tasks() > 0) {
|
||||||
A(this, kernel->is_deadlocked() == false, "deadlock");
|
A(this, kernel->is_deadlocked() == false, "deadlock");
|
||||||
|
|
||||||
|
DLOG(this, dom, "worker %d, number_of_live_tasks = %d",
|
||||||
|
id, number_of_live_tasks());
|
||||||
|
|
||||||
drain_incoming_message_queue(true);
|
drain_incoming_message_queue(true);
|
||||||
|
|
||||||
rust_task *scheduled_task = schedule_task();
|
rust_task *scheduled_task = schedule_task();
|
||||||
@@ -281,8 +287,11 @@ rust_dom::start_main_loop() {
|
|||||||
if (scheduled_task == NULL) {
|
if (scheduled_task == NULL) {
|
||||||
log_state();
|
log_state();
|
||||||
DLOG(this, task,
|
DLOG(this, task,
|
||||||
"all tasks are blocked, scheduler yielding ...");
|
"all tasks are blocked, scheduler id %d yielding ...",
|
||||||
|
id);
|
||||||
|
scheduler_lock.unlock();
|
||||||
sync::sleep(100);
|
sync::sleep(100);
|
||||||
|
scheduler_lock.lock();
|
||||||
DLOG(this, task,
|
DLOG(this, task,
|
||||||
"scheduler resuming ...");
|
"scheduler resuming ...");
|
||||||
continue;
|
continue;
|
||||||
@@ -303,15 +312,21 @@ rust_dom::start_main_loop() {
|
|||||||
|
|
||||||
interrupt_flag = 0;
|
interrupt_flag = 0;
|
||||||
|
|
||||||
|
DLOG(this, task,
|
||||||
|
"Running task %p on worker %d",
|
||||||
|
scheduled_task, id);
|
||||||
|
scheduled_task->active = true;
|
||||||
activate(scheduled_task);
|
activate(scheduled_task);
|
||||||
|
scheduled_task->active = false;
|
||||||
|
|
||||||
DLOG(this, task,
|
DLOG(this, task,
|
||||||
"returned from task %s @0x%" PRIxPTR
|
"returned from task %s @0x%" PRIxPTR
|
||||||
" in state '%s', sp=0x%" PRIxPTR,
|
" in state '%s', sp=0x%, worker id=%d" PRIxPTR,
|
||||||
scheduled_task->name,
|
scheduled_task->name,
|
||||||
(uintptr_t)scheduled_task,
|
(uintptr_t)scheduled_task,
|
||||||
scheduled_task->state->name,
|
scheduled_task->state->name,
|
||||||
scheduled_task->rust_sp);
|
scheduled_task->rust_sp,
|
||||||
|
id);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// These invariants are no longer valid, as rust_sp is not
|
// These invariants are no longer valid, as rust_sp is not
|
||||||
@@ -341,10 +356,32 @@ rust_dom::start_main_loop() {
|
|||||||
reap_dead_tasks();
|
reap_dead_tasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
DLOG(this, dom, "finished main-loop (dom.rval = %d)", rval);
|
DLOG(this, dom, "finished main-loop %d (dom.rval = %d)", id, rval);
|
||||||
|
|
||||||
|
scheduler_lock.unlock();
|
||||||
return rval;
|
return rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int rust_dom::start_main_loops(int num_threads)
|
||||||
|
{
|
||||||
|
dom_worker *worker = NULL;
|
||||||
|
|
||||||
|
// -1, because this thread will also be a worker.
|
||||||
|
for(int i = 0; i < num_threads - 1; ++i) {
|
||||||
|
worker = new dom_worker(i + 1, this);
|
||||||
|
worker->start();
|
||||||
|
threads.push(worker);
|
||||||
|
}
|
||||||
|
|
||||||
|
start_main_loop(0);
|
||||||
|
|
||||||
|
while(threads.pop(&worker)) {
|
||||||
|
worker->join();
|
||||||
|
delete worker;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
rust_crate_cache *
|
rust_crate_cache *
|
||||||
rust_dom::get_cache() {
|
rust_dom::get_cache() {
|
||||||
@@ -353,14 +390,26 @@ rust_dom::get_cache() {
|
|||||||
|
|
||||||
rust_task *
|
rust_task *
|
||||||
rust_dom::create_task(rust_task *spawner, const char *name) {
|
rust_dom::create_task(rust_task *spawner, const char *name) {
|
||||||
|
scheduler_lock.lock();
|
||||||
rust_task *task =
|
rust_task *task =
|
||||||
new (this) rust_task (this, &newborn_tasks, spawner, name);
|
new (this) rust_task (this, &newborn_tasks, spawner, name);
|
||||||
DLOG(this, task, "created task: " PTR ", spawner: %s, name: %s",
|
DLOG(this, task, "created task: " PTR ", spawner: %s, name: %s",
|
||||||
task, spawner ? spawner->name : "null", name);
|
task, spawner ? spawner->name : "null", name);
|
||||||
newborn_tasks.append(task);
|
newborn_tasks.append(task);
|
||||||
|
scheduler_lock.unlock();
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rust_dom::dom_worker::dom_worker(int id, rust_dom *owner)
|
||||||
|
: id(id), owner(owner)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void rust_dom::dom_worker::run()
|
||||||
|
{
|
||||||
|
owner->start_main_loop(id);
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Local Variables:
|
// Local Variables:
|
||||||
// mode: C++
|
// mode: C++
|
||||||
|
|||||||
@@ -96,11 +96,25 @@ struct rust_dom : public kernel_owned<rust_dom>, rc_base<rust_dom>
|
|||||||
void reap_dead_tasks();
|
void reap_dead_tasks();
|
||||||
rust_task *schedule_task();
|
rust_task *schedule_task();
|
||||||
|
|
||||||
int start_main_loop();
|
int start_main_loop(int id);
|
||||||
|
int start_main_loops(int num_threads);
|
||||||
|
|
||||||
void log_state();
|
void log_state();
|
||||||
|
|
||||||
rust_task *create_task(rust_task *spawner, const char *name);
|
rust_task *create_task(rust_task *spawner, const char *name);
|
||||||
|
|
||||||
|
class dom_worker : public rust_thread {
|
||||||
|
int id;
|
||||||
|
rust_dom *owner;
|
||||||
|
|
||||||
|
public:
|
||||||
|
dom_worker(int id, rust_dom *owner);
|
||||||
|
|
||||||
|
virtual void run();
|
||||||
|
};
|
||||||
|
|
||||||
|
lock_and_signal scheduler_lock;
|
||||||
|
array_list<dom_worker *> threads;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline rust_log &
|
inline rust_log &
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ rust_task::rust_task(rust_dom *dom, rust_task_list *state,
|
|||||||
list_index(-1),
|
list_index(-1),
|
||||||
rendezvous_ptr(0),
|
rendezvous_ptr(0),
|
||||||
alarm(this),
|
alarm(this),
|
||||||
handle(NULL)
|
handle(NULL),
|
||||||
|
active(false)
|
||||||
{
|
{
|
||||||
LOGPTR(dom, "new task", (uintptr_t)this);
|
LOGPTR(dom, "new task", (uintptr_t)this);
|
||||||
DLOG(dom, task, "sizeof(task) = %d (0x%x)", sizeof *this, sizeof *this);
|
DLOG(dom, task, "sizeof(task) = %d (0x%x)", sizeof *this, sizeof *this);
|
||||||
@@ -123,17 +124,12 @@ struct spawn_args {
|
|||||||
uintptr_t, uintptr_t);
|
uintptr_t, uintptr_t);
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: rewrite this in LLVM assembly so we can be sure the calling
|
|
||||||
// conventions will match.
|
|
||||||
extern "C" CDECL
|
extern "C" CDECL
|
||||||
void task_start_wrapper(spawn_args *a)
|
void task_start_wrapper(spawn_args *a)
|
||||||
{
|
{
|
||||||
rust_task *task = a->task;
|
rust_task *task = a->task;
|
||||||
int rval = 42;
|
int rval = 42;
|
||||||
|
|
||||||
// This is used by the context switching code. LLVM generates fastcall
|
|
||||||
// functions, but ucontext needs cdecl functions. This massages the
|
|
||||||
// calling conventions into the right form.
|
|
||||||
a->f(&rval, task, a->a3, a->a4);
|
a->f(&rval, task, a->a3, a->a4);
|
||||||
|
|
||||||
LOG(task, task, "task exited with value %d", rval);
|
LOG(task, task, "task exited with value %d", rval);
|
||||||
@@ -174,8 +170,11 @@ rust_task::start(uintptr_t spawnee_fn,
|
|||||||
ctx.call((void *)task_start_wrapper, a, sp);
|
ctx.call((void *)task_start_wrapper, a, sp);
|
||||||
|
|
||||||
yield_timer.reset(0);
|
yield_timer.reset(0);
|
||||||
|
{
|
||||||
|
scoped_lock sync(dom->scheduler_lock);
|
||||||
transition(&dom->newborn_tasks, &dom->running_tasks);
|
transition(&dom->newborn_tasks, &dom->running_tasks);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
rust_task::grow(size_t n_frame_bytes)
|
rust_task::grow(size_t n_frame_bytes)
|
||||||
@@ -425,7 +424,10 @@ rust_task::block(rust_cond *on, const char* name) {
|
|||||||
A(dom, cond == NULL, "Cannot block an already blocked task.");
|
A(dom, cond == NULL, "Cannot block an already blocked task.");
|
||||||
A(dom, on != NULL, "Cannot block on a NULL object.");
|
A(dom, on != NULL, "Cannot block on a NULL object.");
|
||||||
|
|
||||||
|
{
|
||||||
|
scoped_lock sync(dom->scheduler_lock);
|
||||||
transition(&dom->running_tasks, &dom->blocked_tasks);
|
transition(&dom->running_tasks, &dom->blocked_tasks);
|
||||||
|
}
|
||||||
cond = on;
|
cond = on;
|
||||||
cond_name = name;
|
cond_name = name;
|
||||||
}
|
}
|
||||||
@@ -437,7 +439,10 @@ rust_task::wakeup(rust_cond *from) {
|
|||||||
(uintptr_t) cond, (uintptr_t) from);
|
(uintptr_t) cond, (uintptr_t) from);
|
||||||
A(dom, cond == from, "Cannot wake up blocked task on wrong condition.");
|
A(dom, cond == from, "Cannot wake up blocked task on wrong condition.");
|
||||||
|
|
||||||
|
{
|
||||||
|
scoped_lock sync(dom->scheduler_lock);
|
||||||
transition(&dom->blocked_tasks, &dom->running_tasks);
|
transition(&dom->blocked_tasks, &dom->running_tasks);
|
||||||
|
}
|
||||||
I(dom, cond == from);
|
I(dom, cond == from);
|
||||||
cond = NULL;
|
cond = NULL;
|
||||||
cond_name = "none";
|
cond_name = "none";
|
||||||
@@ -445,6 +450,7 @@ rust_task::wakeup(rust_cond *from) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
rust_task::die() {
|
rust_task::die() {
|
||||||
|
scoped_lock sync(dom->scheduler_lock);
|
||||||
transition(&dom->running_tasks, &dom->dead_tasks);
|
transition(&dom->running_tasks, &dom->dead_tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,6 +488,11 @@ rust_task::get_handle() {
|
|||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool rust_task::can_schedule()
|
||||||
|
{
|
||||||
|
return yield_timer.has_timed_out() && !active;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Local Variables:
|
// Local Variables:
|
||||||
// mode: C++
|
// mode: C++
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ rust_task : public maybe_proxy<rust_task>,
|
|||||||
|
|
||||||
context ctx;
|
context ctx;
|
||||||
|
|
||||||
|
// This flag indicates that a worker is either currently running the task
|
||||||
|
// or is about to run this task.
|
||||||
|
bool active;
|
||||||
|
|
||||||
// Only a pointer to 'name' is kept, so it must live as long as this task.
|
// Only a pointer to 'name' is kept, so it must live as long as this task.
|
||||||
rust_task(rust_dom *dom,
|
rust_task(rust_dom *dom,
|
||||||
rust_task_list *state,
|
rust_task_list *state,
|
||||||
@@ -111,6 +115,8 @@ rust_task : public maybe_proxy<rust_task>,
|
|||||||
|
|
||||||
frame_glue_fns *get_frame_glue_fns(uintptr_t fp);
|
frame_glue_fns *get_frame_glue_fns(uintptr_t fp);
|
||||||
rust_crate_cache * get_crate_cache();
|
rust_crate_cache * get_crate_cache();
|
||||||
|
|
||||||
|
bool can_schedule();
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ void lock_and_signal::lock() {
|
|||||||
EnterCriticalSection(&_cs);
|
EnterCriticalSection(&_cs);
|
||||||
#else
|
#else
|
||||||
CHECKED(pthread_mutex_lock(&_mutex));
|
CHECKED(pthread_mutex_lock(&_mutex));
|
||||||
|
_holding_thread = pthread_self();
|
||||||
|
_locked = true;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,6 +50,7 @@ void lock_and_signal::unlock() {
|
|||||||
#if defined(__WIN32__)
|
#if defined(__WIN32__)
|
||||||
LeaveCriticalSection(&_cs);
|
LeaveCriticalSection(&_cs);
|
||||||
#else
|
#else
|
||||||
|
_locked = false;
|
||||||
CHECKED(pthread_mutex_unlock(&_mutex));
|
CHECKED(pthread_mutex_unlock(&_mutex));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -100,6 +103,26 @@ void lock_and_signal::signal_all() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool lock_and_signal::lock_held_by_current_thread()
|
||||||
|
{
|
||||||
|
#if defined(__WIN32__)
|
||||||
|
// TODO: implement this functionality for win32.
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
return _locked && _holding_thread == pthread_self();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
scoped_lock::scoped_lock(lock_and_signal &lock)
|
||||||
|
: lock(lock)
|
||||||
|
{
|
||||||
|
lock.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
scoped_lock::~scoped_lock()
|
||||||
|
{
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Local Variables:
|
// Local Variables:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// -*- c++ -*-
|
||||||
#ifndef LOCK_AND_SIGNAL_H
|
#ifndef LOCK_AND_SIGNAL_H
|
||||||
#define LOCK_AND_SIGNAL_H
|
#define LOCK_AND_SIGNAL_H
|
||||||
|
|
||||||
@@ -8,6 +9,9 @@ class lock_and_signal {
|
|||||||
#else
|
#else
|
||||||
pthread_cond_t _cond;
|
pthread_cond_t _cond;
|
||||||
pthread_mutex_t _mutex;
|
pthread_mutex_t _mutex;
|
||||||
|
|
||||||
|
pthread_t _holding_thread;
|
||||||
|
bool _locked;
|
||||||
#endif
|
#endif
|
||||||
public:
|
public:
|
||||||
lock_and_signal();
|
lock_and_signal();
|
||||||
@@ -19,6 +23,16 @@ public:
|
|||||||
void timed_wait(size_t timeout_in_ns);
|
void timed_wait(size_t timeout_in_ns);
|
||||||
void signal();
|
void signal();
|
||||||
void signal_all();
|
void signal_all();
|
||||||
|
|
||||||
|
bool lock_held_by_current_thread();
|
||||||
|
};
|
||||||
|
|
||||||
|
class scoped_lock {
|
||||||
|
lock_and_signal &lock;
|
||||||
|
|
||||||
|
public:
|
||||||
|
scoped_lock(lock_and_signal &lock);
|
||||||
|
~scoped_lock();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* LOCK_AND_SIGNAL_H */
|
#endif /* LOCK_AND_SIGNAL_H */
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ rust_task_test::worker::run() {
|
|||||||
kernel->create_domain("test");
|
kernel->create_domain("test");
|
||||||
rust_dom *domain = handle->referent();
|
rust_dom *domain = handle->referent();
|
||||||
domain->root_task->start((uintptr_t)&task_entry, (uintptr_t)NULL);
|
domain->root_task->start((uintptr_t)&task_entry, (uintptr_t)NULL);
|
||||||
domain->start_main_loop();
|
domain->start_main_loop(0);
|
||||||
kernel->destroy_domain(domain);
|
kernel->destroy_domain(domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// -*- c++ -*-
|
||||||
#ifndef ARRAY_LIST_H
|
#ifndef ARRAY_LIST_H
|
||||||
#define ARRAY_LIST_H
|
#define ARRAY_LIST_H
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// -*- c++ -*-
|
||||||
#ifndef INDEXED_LIST_H
|
#ifndef INDEXED_LIST_H
|
||||||
#define INDEXED_LIST_H
|
#define INDEXED_LIST_H
|
||||||
|
|
||||||
|
|||||||
29
src/test/run-pass/infinite-loops.rs
Normal file
29
src/test/run-pass/infinite-loops.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
A simple way to make sure threading works. This should use all the
|
||||||
|
CPU cycles an any machines that we're likely to see for a while.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// xfail-stage0
|
||||||
|
// xfail-stage1
|
||||||
|
// xfail-stage2
|
||||||
|
// xfail-stage3
|
||||||
|
|
||||||
|
use std;
|
||||||
|
import std::task::join;
|
||||||
|
|
||||||
|
fn loop(int n) {
|
||||||
|
let task t1;
|
||||||
|
let task t2;
|
||||||
|
|
||||||
|
if(n > 0) {
|
||||||
|
t1 = spawn loop(n - 1);
|
||||||
|
t2 = spawn loop(n - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
while(true) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let task t = spawn loop(5);
|
||||||
|
join(t);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user