auto merge of #7111 : brson/rust/stack, r=brson
... through yields This avoids the following pathological scenario that makes threadring OOM: 1) task calls C using fast_ffi, borrowing a big stack from the scheduler. 2) task returns from C and places the big stack on the task-local stack segment list 3) task calls further Rust functions that require growing the stack, and for this reuses the big stack 4) task yields, failing to return the big stack to the scheduler. 5) repeat 500+ times and OOM (reopening after incoming fallout. *do not r+*. broken)
This commit is contained in:
commit
eda5e40b79
@ -211,12 +211,13 @@ rust_sched_loop::return_c_stack(stk_seg *stack) {
|
|||||||
// NB: Runs on the Rust stack. Might return NULL!
|
// NB: Runs on the Rust stack. Might return NULL!
|
||||||
inline stk_seg *
|
inline stk_seg *
|
||||||
rust_sched_loop::borrow_big_stack() {
|
rust_sched_loop::borrow_big_stack() {
|
||||||
assert(cached_big_stack);
|
|
||||||
stk_seg *your_stack;
|
stk_seg *your_stack;
|
||||||
if (extra_big_stack) {
|
if (extra_big_stack) {
|
||||||
your_stack = extra_big_stack;
|
your_stack = extra_big_stack;
|
||||||
extra_big_stack = NULL;
|
extra_big_stack = NULL;
|
||||||
} else {
|
} else {
|
||||||
|
// NB: This may be null if we're asking for a *second*
|
||||||
|
// big stack, in which case the caller will fall back to a slow path
|
||||||
your_stack = cached_big_stack;
|
your_stack = cached_big_stack;
|
||||||
cached_big_stack = NULL;
|
cached_big_stack = NULL;
|
||||||
}
|
}
|
||||||
|
@ -54,8 +54,7 @@ rust_task::rust_task(rust_sched_loop *sched_loop, rust_task_state state,
|
|||||||
disallow_yield(0),
|
disallow_yield(0),
|
||||||
c_stack(NULL),
|
c_stack(NULL),
|
||||||
next_c_sp(0),
|
next_c_sp(0),
|
||||||
next_rust_sp(0),
|
next_rust_sp(0)
|
||||||
big_stack(NULL)
|
|
||||||
{
|
{
|
||||||
LOGPTR(sched_loop, "new task", (uintptr_t)this);
|
LOGPTR(sched_loop, "new task", (uintptr_t)this);
|
||||||
DLOG(sched_loop, task, "sizeof(task) = %d (0x%x)",
|
DLOG(sched_loop, task, "sizeof(task) = %d (0x%x)",
|
||||||
@ -566,14 +565,8 @@ rust_task::cleanup_after_turn() {
|
|||||||
|
|
||||||
while (stk->next) {
|
while (stk->next) {
|
||||||
stk_seg *new_next = stk->next->next;
|
stk_seg *new_next = stk->next->next;
|
||||||
|
assert (!stk->next->is_big);
|
||||||
if (stk->next->is_big) {
|
|
||||||
assert (big_stack == stk->next);
|
|
||||||
sched_loop->return_big_stack(big_stack);
|
|
||||||
big_stack = NULL;
|
|
||||||
} else {
|
|
||||||
free_stack(stk->next);
|
free_stack(stk->next);
|
||||||
}
|
|
||||||
|
|
||||||
stk->next = new_next;
|
stk->next = new_next;
|
||||||
}
|
}
|
||||||
@ -584,38 +577,20 @@ rust_task::cleanup_after_turn() {
|
|||||||
bool
|
bool
|
||||||
rust_task::new_big_stack() {
|
rust_task::new_big_stack() {
|
||||||
assert(stk);
|
assert(stk);
|
||||||
// If we have a cached big stack segment, use it.
|
|
||||||
if (big_stack) {
|
|
||||||
// Check to see if we're already on the big stack.
|
|
||||||
stk_seg *ss = stk;
|
|
||||||
while (ss != NULL) {
|
|
||||||
if (ss == big_stack)
|
|
||||||
return false;
|
|
||||||
ss = ss->prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unlink the big stack.
|
|
||||||
if (big_stack->next)
|
|
||||||
big_stack->next->prev = big_stack->prev;
|
|
||||||
if (big_stack->prev)
|
|
||||||
big_stack->prev->next = big_stack->next;
|
|
||||||
} else {
|
|
||||||
stk_seg *borrowed_big_stack = sched_loop->borrow_big_stack();
|
stk_seg *borrowed_big_stack = sched_loop->borrow_big_stack();
|
||||||
if (!borrowed_big_stack) {
|
if (!borrowed_big_stack) {
|
||||||
abort();
|
return false;
|
||||||
} else {
|
|
||||||
big_stack = borrowed_big_stack;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
big_stack->task = this;
|
borrowed_big_stack->task = this;
|
||||||
big_stack->next = stk->next;
|
borrowed_big_stack->next = stk->next;
|
||||||
if (big_stack->next)
|
if (borrowed_big_stack->next)
|
||||||
big_stack->next->prev = big_stack;
|
borrowed_big_stack->next->prev = borrowed_big_stack;
|
||||||
big_stack->prev = stk;
|
borrowed_big_stack->prev = stk;
|
||||||
stk->next = big_stack;
|
stk->next = borrowed_big_stack;
|
||||||
|
|
||||||
stk = big_stack;
|
stk = borrowed_big_stack;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -640,10 +615,9 @@ void
|
|||||||
rust_task::reset_stack_limit() {
|
rust_task::reset_stack_limit() {
|
||||||
uintptr_t sp = get_sp();
|
uintptr_t sp = get_sp();
|
||||||
while (!sp_in_stk_seg(sp, stk)) {
|
while (!sp_in_stk_seg(sp, stk)) {
|
||||||
stk = stk->prev;
|
prev_stack();
|
||||||
assert(stk != NULL && "Failed to find the current stack");
|
assert(stk != NULL && "Failed to find the current stack");
|
||||||
}
|
}
|
||||||
record_stack_limit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -667,8 +641,6 @@ rust_task::delete_all_stacks() {
|
|||||||
|
|
||||||
stk = prev;
|
stk = prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
big_stack = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -278,9 +278,6 @@ private:
|
|||||||
uintptr_t next_c_sp;
|
uintptr_t next_c_sp;
|
||||||
uintptr_t next_rust_sp;
|
uintptr_t next_rust_sp;
|
||||||
|
|
||||||
// The big stack.
|
|
||||||
stk_seg *big_stack;
|
|
||||||
|
|
||||||
// Called when the atomic refcount reaches zero
|
// Called when the atomic refcount reaches zero
|
||||||
void delete_this();
|
void delete_this();
|
||||||
|
|
||||||
@ -607,7 +604,21 @@ rust_task::prev_stack() {
|
|||||||
// require switching to the C stack and be costly. Instead we'll just move
|
// require switching to the C stack and be costly. Instead we'll just move
|
||||||
// up the link list and clean up later, either in new_stack or after our
|
// up the link list and clean up later, either in new_stack or after our
|
||||||
// turn ends on the scheduler.
|
// turn ends on the scheduler.
|
||||||
|
if (stk->is_big) {
|
||||||
|
stk_seg *ss = stk;
|
||||||
stk = stk->prev;
|
stk = stk->prev;
|
||||||
|
|
||||||
|
// Unlink the big stack.
|
||||||
|
if (ss->next)
|
||||||
|
ss->next->prev = ss->prev;
|
||||||
|
if (ss->prev)
|
||||||
|
ss->prev->next = ss->next;
|
||||||
|
|
||||||
|
sched_loop->return_big_stack(ss);
|
||||||
|
} else {
|
||||||
|
stk = stk->prev;
|
||||||
|
}
|
||||||
|
|
||||||
record_stack_limit();
|
record_stack_limit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,18 +10,18 @@
|
|||||||
|
|
||||||
// Based on threadring.erlang by Jira Isa
|
// Based on threadring.erlang by Jira Isa
|
||||||
|
|
||||||
// xfail-test FIXME #5985 OOM's on the mac bot
|
use std::os;
|
||||||
|
|
||||||
fn start(n_tasks: int, token: int) {
|
fn start(n_tasks: int, token: int) {
|
||||||
let mut (p, ch1) = comm::stream();
|
let mut (p, ch1) = stream();
|
||||||
ch1.send(token);
|
ch1.send(token);
|
||||||
// XXX could not get this to work with a range closure
|
// XXX could not get this to work with a range closure
|
||||||
let mut i = 2;
|
let mut i = 2;
|
||||||
while i <= n_tasks {
|
while i <= n_tasks {
|
||||||
let (next_p, ch) = comm::stream();
|
let (next_p, ch) = stream();
|
||||||
let imm_i = i;
|
let imm_i = i;
|
||||||
let imm_p = p;
|
let imm_p = p;
|
||||||
do task::spawn {
|
do spawn {
|
||||||
roundtrip(imm_i, n_tasks, &imm_p, &ch);
|
roundtrip(imm_i, n_tasks, &imm_p, &ch);
|
||||||
};
|
};
|
||||||
p = next_p;
|
p = next_p;
|
||||||
@ -29,16 +29,16 @@ fn start(n_tasks: int, token: int) {
|
|||||||
}
|
}
|
||||||
let imm_p = p;
|
let imm_p = p;
|
||||||
let imm_ch = ch1;
|
let imm_ch = ch1;
|
||||||
do task::spawn {
|
do spawn {
|
||||||
roundtrip(1, n_tasks, &imm_p, &imm_ch);
|
roundtrip(1, n_tasks, &imm_p, &imm_ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn roundtrip(id: int, n_tasks: int, p: &comm::Port<int>, ch: &comm::Chan<int>) {
|
fn roundtrip(id: int, n_tasks: int, p: &Port<int>, ch: &Chan<int>) {
|
||||||
while (true) {
|
while (true) {
|
||||||
match p.recv() {
|
match p.recv() {
|
||||||
1 => {
|
1 => {
|
||||||
io::println(fmt!("%d\n", id));
|
println(fmt!("%d\n", id));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
token => {
|
token => {
|
||||||
@ -60,13 +60,13 @@ fn main() {
|
|||||||
os::args()
|
os::args()
|
||||||
};
|
};
|
||||||
let token = if args.len() > 1u {
|
let token = if args.len() > 1u {
|
||||||
int::from_str(args[1]).get()
|
FromStr::from_str(args[1]).get()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
1000
|
1000
|
||||||
};
|
};
|
||||||
let n_tasks = if args.len() > 2u {
|
let n_tasks = if args.len() > 2u {
|
||||||
int::from_str(args[2]).get()
|
FromStr::from_str(args[2]).get()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
503
|
503
|
||||||
|
Loading…
x
Reference in New Issue
Block a user