diff --git a/src/libcore/private.rs b/src/libcore/private.rs index d3002ba9316..48489dab488 100644 --- a/src/libcore/private.rs +++ b/src/libcore/private.rs @@ -28,6 +28,9 @@ use task; use task::{TaskBuilder, atomically}; use uint; +#[path = "private/at_exit.rs"] +pub mod at_exit; + extern mod rustrt { #[legacy_exports]; unsafe fn rust_task_weaken(ch: rust_port_id); diff --git a/src/libcore/private/at_exit.rs b/src/libcore/private/at_exit.rs new file mode 100644 index 00000000000..7ac252ea102 --- /dev/null +++ b/src/libcore/private/at_exit.rs @@ -0,0 +1,86 @@ +use sys; +use cast; +use ptr; +use task; +use uint; +use vec; +use rand; +use libc::{c_void, size_t}; + +/** +Register a function to be run during runtime shutdown. + +After all non-weak tasks have exited, registered exit functions will +execute, in random order, on the primary scheduler. Each function runs +in its own unsupervised task. +*/ +pub fn at_exit(f: ~fn()) unsafe { + let runner: &fn(*ExitFunctions) = exit_runner; + let runner_pair: sys::Closure = cast::transmute(runner); + let runner_ptr = runner_pair.code; + let runner_ptr = cast::transmute(runner_ptr); + rustrt::rust_register_exit_function(runner_ptr, ~f); +} + +// NB: The double pointer indirection here is because ~fn() is a fat +// pointer and due to FFI problems I am more comfortable making the +// interface use a normal pointer +extern mod rustrt { + fn rust_register_exit_function(runner: *c_void, f: ~~fn()); +} + +struct ExitFunctions { + // The number of exit functions + count: size_t, + // The buffer of exit functions + start: *~~fn() +} + +fn exit_runner(exit_fns: *ExitFunctions) unsafe { + let exit_fns = &*exit_fns; + let count = (*exit_fns).count; + let start = (*exit_fns).start; + + // NB: from_buf memcpys from the source, which will + // give us ownership of the array of functions + let mut exit_fns_vec = vec::from_buf(start, count as uint); + // Let's not make any promises about execution order + rand::Rng().shuffle_mut(exit_fns_vec); + + debug!("running %u exit functions", exit_fns_vec.len()); + + while exit_fns_vec.is_not_empty() { + match exit_fns_vec.pop() { + ~f => { + task::task().supervised().spawn(f); + } + } + } +} + +#[abi = "rust-intrinsic"] +pub extern mod rusti { + fn move_val_init(dst: &mut T, -src: T); + fn init() -> T; +} + +#[test] +fn test_at_exit() { + let i = 10; + do at_exit { + debug!("at_exit1"); + assert i == 10; + } +} + +#[test] +fn test_at_exit_many() { + let i = 10; + for uint::range(20, 100) |j| { + do at_exit { + debug!("at_exit2"); + assert i == 10; + assert j > i; + } + } +} \ No newline at end of file diff --git a/src/rt/rust_builtin.cpp b/src/rt/rust_builtin.cpp index cbc58e85db6..a37aea13e40 100644 --- a/src/rt/rust_builtin.cpp +++ b/src/rt/rust_builtin.cpp @@ -1026,6 +1026,11 @@ rust_raw_thread_join_delete(raw_thread *thread) { delete thread; } +extern "C" void +rust_register_exit_function(spawn_fn runner, fn_env_pair *f) { + rust_task *task = rust_get_current_task(); + task->kernel->register_exit_function(runner, f); +} // // Local Variables: diff --git a/src/rt/rust_kernel.cpp b/src/rt/rust_kernel.cpp index cc98b474ee3..3042b006a92 100644 --- a/src/rt/rust_kernel.cpp +++ b/src/rt/rust_kernel.cpp @@ -36,6 +36,8 @@ rust_kernel::rust_kernel(rust_env *env) : non_weak_tasks(0), global_loop_chan(0), global_env_chan(0), + at_exit_runner(NULL), + at_exit_started(false), env(env) { @@ -427,6 +429,7 @@ rust_kernel::begin_shutdown() { } } + run_exit_functions(); allow_scheduler_exit(); end_weak_tasks(); } @@ -446,6 +449,47 @@ rust_kernel::send_to_port(rust_port_id chan, void *sptr) { } } +void +rust_kernel::register_exit_function(spawn_fn runner, fn_env_pair *f) { + scoped_lock with(at_exit_lock); + + assert(!at_exit_started && "registering at_exit function after exit"); + + if (at_exit_runner) { + assert(runner == at_exit_runner + && "there can be only one at_exit_runner"); + } + + at_exit_runner = runner; + at_exit_fns.push_back(f); +} + +void +rust_kernel::run_exit_functions() { + rust_task *task; + + { + scoped_lock with(at_exit_lock); + + assert(!at_exit_started && "running exit functions twice?"); + + at_exit_started = true; + + if (at_exit_runner == NULL) { + return; + } + + rust_scheduler *sched = get_scheduler_by_id(main_sched_id()); + assert(sched); + task = sched->create_task(NULL, "at_exit"); + + final_exit_fns.count = at_exit_fns.size(); + final_exit_fns.start = at_exit_fns.data(); + } + + task->start(at_exit_runner, NULL, &final_exit_fns); +} + // // Local Variables: // mode: C++ diff --git a/src/rt/rust_kernel.h b/src/rt/rust_kernel.h index 13fd8934172..b1548e92cdb 100644 --- a/src/rt/rust_kernel.h +++ b/src/rt/rust_kernel.h @@ -50,6 +50,7 @@ #include "memory_region.h" #include "rust_log.h" #include "rust_sched_reaper.h" +#include "rust_type.h" #include "util/hash_map.h" class rust_scheduler; @@ -66,6 +67,13 @@ typedef intptr_t rust_port_id; typedef std::map sched_map; +// This is defined as a struct only because we need a single pointer to pass +// to the Rust function that runs the at_exit functions +struct exit_functions { + size_t count; + fn_env_pair **start; +}; + class rust_kernel { memory_region _region; rust_log _log; @@ -126,6 +134,14 @@ class rust_kernel { // Used to serialize access to getenv/setenv uintptr_t global_env_chan; + lock_and_signal at_exit_lock; + spawn_fn at_exit_runner; + bool at_exit_started; + std::vector at_exit_fns; + exit_functions final_exit_fns; + + void run_exit_functions(); + public: struct rust_env *env; @@ -175,6 +191,8 @@ public: uintptr_t* get_global_loop() { return &global_loop_chan; } uintptr_t* get_global_env_chan() { return &global_env_chan; } + + void register_exit_function(spawn_fn runner, fn_env_pair *f); }; template struct kernel_owned { diff --git a/src/rt/rustrt.def.in b/src/rt/rustrt.def.in index cce4e411e02..719505079e6 100644 --- a/src/rt/rustrt.def.in +++ b/src/rt/rustrt.def.in @@ -210,3 +210,4 @@ linenoiseHistorySave linenoiseHistoryLoad rust_raw_thread_start rust_raw_thread_join_delete +rust_register_exit_function