| // Copyright 2012 The Rust Project Developers. See the COPYRIGHT |
| // file at the top-level directory of this distribution and at |
| // http://rust-lang.org/COPYRIGHT. |
| // |
| // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| // option. This file may not be copied, modified, or distributed |
| // except according to those terms. |
| |
| |
| |
| #include "rust_kernel.h" |
| #include "rust_util.h" |
| #include "rust_scheduler.h" |
| #include "rust_sched_launcher.h" |
| #include <algorithm> |
| |
| #define KLOG_(...) \ |
| KLOG(this, kern, __VA_ARGS__) |
| #define KLOG_ERR_(field, ...) \ |
| KLOG_LVL(this, field, log_err, __VA_ARGS__) |
| |
| rust_kernel::rust_kernel(rust_env *env) : |
| _log(NULL), |
| max_task_id(INIT_TASK_ID-1), // sync_add_and_fetch increments first |
| rval(0), |
| max_sched_id(1), |
| killed(false), |
| already_exiting(false), |
| sched_reaper(this), |
| osmain_driver(NULL), |
| non_weak_tasks(0), |
| at_exit_runner(NULL), |
| at_exit_started(false), |
| env(env), |
| global_data(0) |
| { |
| // Create the single threaded scheduler that will run on the platform's |
| // main thread |
| rust_manual_sched_launcher_factory *osmain_launchfac = |
| new rust_manual_sched_launcher_factory(); |
| osmain_scheduler = create_scheduler(osmain_launchfac, 1, false); |
| osmain_driver = osmain_launchfac->get_driver(); |
| |
| // Create the primary scheduler |
| rust_thread_sched_launcher_factory *main_launchfac = |
| new rust_thread_sched_launcher_factory(); |
| main_scheduler = create_scheduler(main_launchfac, |
| env->num_sched_threads, |
| false); |
| |
| sched_reaper.start(); |
| } |
| |
| void |
| rust_kernel::log(uint32_t level, char const *fmt, ...) { |
| char buf[BUF_BYTES]; |
| va_list args; |
| va_start(args, fmt); |
| vsnprintf(buf, sizeof(buf), fmt, args); |
| _log.trace_ln(NULL, level, buf); |
| va_end(args); |
| } |
| |
| void |
| rust_kernel::fatal(char const *fmt, ...) { |
| char buf[BUF_BYTES]; |
| va_list args; |
| va_start(args, fmt); |
| vsnprintf(buf, sizeof(buf), fmt, args); |
| _log.trace_ln(NULL, (uint32_t)0, buf); |
| exit(1); |
| va_end(args); |
| } |
| |
| void * |
| rust_kernel::malloc(size_t size, const char *tag) { |
| return exchange_alloc.malloc(size); |
| } |
| |
| void * |
| rust_kernel::realloc(void *mem, size_t size) { |
| return exchange_alloc.realloc(mem, size); |
| } |
| |
| void rust_kernel::free(void *mem) { |
| exchange_alloc.free(mem); |
| } |
| |
| rust_sched_id |
| rust_kernel::create_scheduler(size_t num_threads) { |
| rust_thread_sched_launcher_factory *launchfac = |
| new rust_thread_sched_launcher_factory(); |
| return create_scheduler(launchfac, num_threads, true); |
| } |
| |
| rust_sched_id |
| rust_kernel::create_scheduler(rust_sched_launcher_factory *launchfac, |
| size_t num_threads, bool allow_exit) { |
| rust_sched_id id; |
| rust_scheduler *sched; |
| { |
| scoped_lock with(sched_lock); |
| |
| /*if (sched_table.size() == 2) { |
| // The main and OS main schedulers may not exit while there are |
| // other schedulers |
| KLOG_("Disallowing main scheduler to exit"); |
| rust_scheduler *main_sched = |
| get_scheduler_by_id_nolock(main_scheduler); |
| assert(main_sched != NULL); |
| main_sched->disallow_exit(); |
| } |
| if (sched_table.size() == 1) { |
| KLOG_("Disallowing osmain scheduler to exit"); |
| rust_scheduler *osmain_sched = |
| get_scheduler_by_id_nolock(osmain_scheduler); |
| assert(osmain_sched != NULL); |
| osmain_sched->disallow_exit(); |
| }*/ |
| |
| id = max_sched_id++; |
| assert(id != INTPTR_MAX && "Hit the maximum scheduler id"); |
| sched = new (this, "rust_scheduler") |
| rust_scheduler(this, num_threads, id, allow_exit, killed, |
| launchfac); |
| bool is_new = sched_table |
| .insert(std::pair<rust_sched_id, |
| rust_scheduler*>(id, sched)).second; |
| assert(is_new && "Reusing a sched id?"); |
| } |
| sched->start_task_threads(); |
| return id; |
| } |
| |
| rust_scheduler * |
| rust_kernel::get_scheduler_by_id(rust_sched_id id) { |
| scoped_lock with(sched_lock); |
| return get_scheduler_by_id_nolock(id); |
| } |
| |
| rust_scheduler * |
| rust_kernel::get_scheduler_by_id_nolock(rust_sched_id id) { |
| if (id == 0) { |
| return NULL; |
| } |
| sched_lock.must_have_lock(); |
| sched_map::iterator iter = sched_table.find(id); |
| if (iter != sched_table.end()) { |
| return iter->second; |
| } else { |
| return NULL; |
| } |
| } |
| |
| void |
| rust_kernel::release_scheduler_id(rust_sched_id id) { |
| scoped_lock with(sched_lock); |
| join_list.push_back(id); |
| sched_lock.signal(); |
| } |
| |
| /* |
| Called by rust_sched_reaper to join every every terminating scheduler thread, |
| so that we can be sure they have completely exited before the process exits. |
| If we don't join them then we can see valgrind errors due to un-freed pthread |
| memory. |
| */ |
| void |
| rust_kernel::wait_for_schedulers() |
| { |
| scoped_lock with(sched_lock); |
| while (!sched_table.empty()) { |
| while (!join_list.empty()) { |
| rust_sched_id id = join_list.back(); |
| KLOG_("Deleting scheduler %d", id); |
| join_list.pop_back(); |
| sched_map::iterator iter = sched_table.find(id); |
| assert(iter != sched_table.end()); |
| rust_scheduler *sched = iter->second; |
| sched_table.erase(iter); |
| sched->join_task_threads(); |
| sched->deref(); |
| /*if (sched_table.size() == 2) { |
| KLOG_("Allowing main scheduler to exit"); |
| // It's only the main schedulers left. Tell them to exit |
| rust_scheduler *main_sched = |
| get_scheduler_by_id_nolock(main_scheduler); |
| assert(main_sched != NULL); |
| main_sched->allow_exit(); |
| } |
| if (sched_table.size() == 1) { |
| KLOG_("Allowing osmain scheduler to exit"); |
| rust_scheduler *osmain_sched = |
| get_scheduler_by_id_nolock(osmain_scheduler); |
| assert(osmain_sched != NULL); |
| osmain_sched->allow_exit(); |
| }*/ |
| } |
| if (!sched_table.empty()) { |
| sched_lock.wait(); |
| } |
| } |
| } |
| |
| /* Called on the main thread to run the osmain scheduler to completion, |
| then wait for schedulers to exit */ |
| int |
| rust_kernel::run() { |
| assert(osmain_driver != NULL); |
| osmain_driver->start_main_loop(); |
| sched_reaper.join(); |
| rust_check_exchange_count_on_exit(); |
| return rval; |
| } |
| |
| void |
| rust_kernel::fail() { |
| // FIXME (#908): On windows we're getting "Application has |
| // requested the Runtime to terminate it in an unusual way" when |
| // trying to shutdown cleanly. |
| set_exit_status(PROC_FAIL_CODE); |
| #if defined(__WIN32__) |
| exit(rval); |
| #endif |
| // I think this only needs to be done by one task ever; as it is, |
| // multiple tasks invoking kill_all might get here. Currently libcore |
| // ensures only one task will ever invoke it, but this would really be |
| // fine either way, so I'm leaving it as it is. -- bblum |
| |
| // Copy the list of schedulers so that we don't hold the lock while |
| // running kill_all_tasks. Refcount to ensure they stay alive. |
| std::vector<rust_scheduler*> scheds; |
| { |
| scoped_lock with(sched_lock); |
| // All schedulers created after this flag is set will be doomed. |
| killed = true; |
| for (sched_map::iterator iter = sched_table.begin(); |
| iter != sched_table.end(); iter++) { |
| iter->second->ref(); |
| scheds.push_back(iter->second); |
| } |
| } |
| |
| for (std::vector<rust_scheduler*>::iterator iter = scheds.begin(); |
| iter != scheds.end(); iter++) { |
| (*iter)->kill_all_tasks(); |
| (*iter)->deref(); |
| } |
| } |
| |
| rust_task_id |
| rust_kernel::generate_task_id() { |
| rust_task_id id = sync::increment(max_task_id); |
| assert(id != INTPTR_MAX && "Hit the maximum task id"); |
| return id; |
| } |
| |
| #ifdef __WIN32__ |
| void |
| rust_kernel::win32_require(LPCTSTR fn, BOOL ok) { |
| if (!ok) { |
| LPTSTR buf; |
| DWORD err = GetLastError(); |
| FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | |
| FORMAT_MESSAGE_FROM_SYSTEM | |
| FORMAT_MESSAGE_IGNORE_INSERTS, |
| NULL, err, |
| MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
| (LPTSTR) &buf, 0, NULL ); |
| KLOG_ERR_(dom, "%s failed with error %ld: %s", fn, err, buf); |
| LocalFree((HLOCAL)buf); |
| assert(ok); |
| } |
| } |
| #endif |
| |
| void |
| rust_kernel::set_exit_status(int code) { |
| scoped_lock with(rval_lock); |
| // If we've already failed then that's the code we're going to use |
| if (rval != PROC_FAIL_CODE) { |
| rval = code; |
| } |
| } |
| |
| void |
| rust_kernel::inc_live_count() { |
| uintptr_t new_non_weak_tasks = sync::increment(non_weak_tasks); |
| KLOG_("New non-weak tasks %" PRIdPTR, new_non_weak_tasks); |
| } |
| |
| void |
| rust_kernel::dec_live_count() { |
| uintptr_t new_non_weak_tasks = sync::decrement(non_weak_tasks); |
| KLOG_("New non-weak tasks %" PRIdPTR, new_non_weak_tasks); |
| if (new_non_weak_tasks == 0) { |
| begin_shutdown(); |
| } |
| } |
| |
| void |
| rust_kernel::allow_scheduler_exit() { |
| scoped_lock with(sched_lock); |
| |
| KLOG_("Allowing main scheduler to exit"); |
| // It's only the main schedulers left. Tell them to exit |
| rust_scheduler *main_sched = |
| get_scheduler_by_id_nolock(main_scheduler); |
| assert(main_sched != NULL); |
| main_sched->allow_exit(); |
| |
| KLOG_("Allowing osmain scheduler to exit"); |
| rust_scheduler *osmain_sched = |
| get_scheduler_by_id_nolock(osmain_scheduler); |
| assert(osmain_sched != NULL); |
| osmain_sched->allow_exit(); |
| } |
| |
| void |
| rust_kernel::begin_shutdown() { |
| { |
| scoped_lock with(sched_lock); |
| // FIXME #4410: This shouldn't be necessary, but because of |
| // unweaken_task this may end up getting called multiple times. |
| if (already_exiting) { |
| return; |
| } else { |
| already_exiting = true; |
| } |
| } |
| |
| run_exit_functions(); |
| allow_scheduler_exit(); |
| } |
| |
| 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) { |
| // FIXME #2912 Would be very nice to assert this but we can't because |
| // of the way coretest works (the test case ends up using its own |
| // function) |
| //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++ |
| // fill-column: 78; |
| // indent-tabs-mode: nil |
| // c-basic-offset: 4 |
| // buffer-file-coding-system: utf-8-unix |
| // End: |
| // |