448 lines · 14.5 KB · C++
RawDownload
//// Dis VM// File: scheduler.cpp// Author: arr// #include <debug.h>#include <iostream>#include <sstream>#include <queue>#include <exceptions.h>#include "scheduler.h" using disvm::vm_t; using disvm::debug::component_trace_t;using disvm::debug::log_level_t; using disvm::runtime::vm_thread_t;using disvm::runtime::vm_scheduler_t;using disvm::runtime::vm_scheduler_control_t;using disvm::runtime::vm_tool_dispatch_t;using disvm::runtime::vm_thread_state_t;using disvm::runtime::default_scheduler_t; // Empty destructors for vm scheduler 'interfaces'vm_scheduler_t::~vm_scheduler_t(){} vm_scheduler_control_t::~vm_scheduler_control_t(){} void default_scheduler_t::worker_main(default_scheduler_t &instance){    disvm::debug::log_msg(component_trace_t::scheduler, log_level_t::debug, "scheduler: worker: start");     register_system_thread(instance._vm);    auto current_thread = std::shared_ptr<thread_instance_t>{};     try    {        for (;;)        {            current_thread = instance.next_thread(std::move(current_thread));            if (current_thread == nullptr)            {                disvm::debug::log_msg(component_trace_t::scheduler, log_level_t::debug, "scheduler: worker: stop");                break;            }             if (disvm::debug::is_component_tracing_enabled<component_trace_t::scheduler>())                disvm::debug::log_msg(component_trace_t::scheduler, log_level_t::debug, "scheduler: worker: execute: %d", current_thread->vm_thread->get_thread_id());             current_thread->vm_thread->execute(instance._vm, instance._vm_thread_quanta);        }    }    catch (const vm_term_request &te)    {        std::cerr << te.what() << std::endl;         instance._terminating = true;        instance._worker_event.notify_all();    }    catch (const vm_system_exception &se)    {        std::stringstream err_msg;        err_msg << se.what() << "\n";         if (current_thread != nullptr)        {            const auto &r = current_thread->vm_thread->get_registers();            walk_stack(r, [&err_msg](const pointer_t, const vm_pc_t pc, const vm_module_ref_t &module_ref) {                auto module_name = "<No Name>";                if (module_ref.module->module_name != nullptr)                    module_name = module_ref.module->module_name->str();                 err_msg << "    " << module_name << " @" << pc << "\n";                return true;            });        }         auto err_str = err_msg.str();        std::cerr << err_str.c_str() << std::endl;         instance._terminating = true;        instance._worker_event.notify_all();    }     unregister_system_thread(instance._vm);} default_scheduler_t::default_scheduler_t(vm_t &vm, uint32_t system_thread_count, uint32_t thread_quanta)    : _gc_complete{ true }    , _gc_counter{ 1 }    , _running_vm_thread_count{ 0 }    , _terminating{ false }    , _worker_thread_count{ system_thread_count }    , _vm{ vm }    , _vm_thread_quanta{ thread_quanta }{    if (_worker_thread_count == 0)        throw vm_system_exception{ "Work thread count must be > 0" };     if (_vm_thread_quanta == 0)        throw vm_system_exception{ "Work thread quanta must be > 0" };} default_scheduler_t::~default_scheduler_t(){    // Drain the runnable queue and clear out all the threads    {        std::lock_guard<std::mutex> lock{ _vm_threads_lock };        _terminating = true;         _runnable_vm_thread_ids.clear();        _all_vm_threads.clear();    }     // Notify all worker threads    _worker_event.notify_all();     // Wait for all worker threads to finish    for (auto &w : _worker_pool)        w.join();     disvm::debug::log_msg(component_trace_t::scheduler, log_level_t::debug, "scheduler: shutdown");} bool default_scheduler_t::is_idle() const{    std::lock_guard<std::mutex> lock{ _vm_threads_lock };    return (_terminating) || (_running_vm_thread_count == 0 && _runnable_vm_thread_ids.empty() && _blocked_vm_thread_ids.empty());} vm_scheduler_control_t &default_scheduler_t::get_controller() const{    return const_cast<default_scheduler_t&>(*this);} void default_scheduler_t::schedule_thread(std::unique_ptr<vm_thread_t> thread){    assert(thread != nullptr);    if (thread->get_registers().current_thread_state != vm_thread_state_t::ready)        throw vm_system_exception{ "Scheduled thread in invalid state" };     if (disvm::debug::is_component_tracing_enabled<component_trace_t::scheduler>())        disvm::debug::log_msg(component_trace_t::scheduler, log_level_t::debug, "scheduler: scheduled: %d", thread->get_thread_id());     std::unique_lock<std::mutex> lock{ _vm_threads_lock };     // Create workers on first scheduled thread    if (_worker_pool.empty())    {        for (auto i = uint32_t{ 0 }; i < _worker_thread_count; ++i)            _worker_pool.push_back(std::thread{ default_scheduler_t::worker_main, std::ref(*this) });    }     const auto new_thread_id = thread->get_thread_id();    assert(_all_vm_threads.find(new_thread_id) == _all_vm_threads.cend());     // Allocate a new container and set the thread entry    _all_vm_threads[new_thread_id] = std::make_shared<thread_instance_t>(std::move(thread));     _runnable_vm_thread_ids.push_back(new_thread_id);     // Notify the worker a thread has been enqueued.    lock.unlock();    _worker_event.notify_one();} void default_scheduler_t::set_tool_dispatch_on_all_threads(vm_tool_dispatch_t *dispatch){    std::unique_lock<std::mutex> lock{ _vm_threads_lock };     auto threads_to_set = std::queue<uint32_t>{};     // Set all threads possible and queue the rest    for (auto entry : _all_vm_threads)    {        auto t = entry.second->vm_thread;        if (!entry.second->system_thread_ownership.try_lock())        {            threads_to_set.push(t->get_thread_id());            continue;        }         std::lock_guard<std::mutex> lock_thread{ entry.second->system_thread_ownership, std::adopt_lock };        t->set_tool_dispatch(dispatch);    }     // Loop until all threads have been set    auto count = threads_to_set.size();    while (!threads_to_set.empty())    {        if (count == 0)        {            // Reset count and sleep since all the current threads have been attempted.            count = threads_to_set.size();             // Release the vm threads lock so they can make progress            lock.unlock();            std::this_thread::sleep_for(std::chrono::milliseconds{ 50 });            lock.lock();        }         const auto thread_id = threads_to_set.front();        threads_to_set.pop();        count--;         const auto iter = _all_vm_threads.find(thread_id);         // The thread to set could have been removed while we were sleeping above        if (iter == _all_vm_threads.cend())            continue;         if (!iter->second->system_thread_ownership.try_lock())        {            threads_to_set.push(thread_id);            continue;        }         std::lock_guard<std::mutex> lock_thread{ iter->second->system_thread_ownership, std::adopt_lock };        iter->second->vm_thread->set_tool_dispatch(dispatch);    }} void default_scheduler_t::enqueue_blocked_thread(uint32_t thread_id){    all_thread_map_t::iterator thread_to_enqueue;    {        std::lock_guard<std::mutex> lock{ _vm_threads_lock };        thread_to_enqueue = _all_vm_threads.find(thread_id);        if (thread_to_enqueue == _all_vm_threads.cend())        {            assert(false && "Unknown thread to enqueue");            return;        }    }     auto thread_container = thread_to_enqueue->second;     // Lock the vm thread collections and this vm thread    std::lock(_vm_threads_lock, thread_container->system_thread_ownership);    std::unique_lock<std::mutex> lock_all{ _vm_threads_lock, std::adopt_lock };    std::lock_guard<std::mutex> lock_thread{ thread_container->system_thread_ownership, std::adopt_lock };     // If the thread is not blocked, just return.    auto blocked_iter = _blocked_vm_thread_ids.find(thread_id);    if (blocked_iter == _blocked_vm_thread_ids.cend())    {        assert(false && "Thread to enqueue not blocked");        return;    }     _blocked_vm_thread_ids.erase(blocked_iter);     if (disvm::debug::is_component_tracing_enabled<component_trace_t::scheduler>())        disvm::debug::log_msg(component_trace_t::scheduler, log_level_t::debug, "scheduler: enqueue blocked: %d", thread_id);     const bool runnable_thread = enqueue_thread_unsafe(thread_container.get(), vm_thread_state_t::ready);    if (runnable_thread)    {        lock_all.unlock();        _worker_event.notify_one();    }} std::size_t default_scheduler_t::get_system_thread_count() const{    return _worker_thread_count;} std::vector<std::shared_ptr<const vm_thread_t>> default_scheduler_t::get_all_threads() const{    auto result = std::vector<std::shared_ptr<const vm_thread_t>>{};     {        std::lock_guard<std::mutex> lock{ _vm_threads_lock };        for (auto &p : _all_vm_threads)            result.push_back(p.second->vm_thread);    }     return result;} std::shared_ptr<default_scheduler_t::thread_instance_t> default_scheduler_t::next_thread(std::shared_ptr<thread_instance_t> prev_thread){    auto next_thread = std::shared_ptr<thread_instance_t>{};     // Check if the passed in thread should be enqueued    if (prev_thread != nullptr)    {        const auto current_state = prev_thread->vm_thread->get_registers().current_thread_state;         // This system thread releases ownership of the vm thread after it has been enqueued        std::lock_guard<std::mutex> vm_thread_ownership{ prev_thread->system_thread_ownership, std::adopt_lock };         std::unique_lock<std::mutex> lock_all{ _vm_threads_lock };        --_running_vm_thread_count;        const bool runnable_thread = enqueue_thread_unsafe(prev_thread.get(), current_state);        if (runnable_thread)        {            lock_all.unlock();            _worker_event.notify_one();        }    }     for (;;)    {        // Check for a vm thread in the queue        std::unique_lock<std::mutex> lock{ _vm_threads_lock };         // Check if a GC should be performed.        // Matching part of the logic in Inferno (emu/port/dis.c).        if ((_gc_counter & 0xff) == 0)        {            const auto running_thread_count_local = _running_vm_thread_count;            const auto is_gc_thread = running_thread_count_local == 0;            perform_gc(is_gc_thread, lock);        }         if (!_runnable_vm_thread_ids.empty())        {            const auto thread_id = _runnable_vm_thread_ids.front();            _runnable_vm_thread_ids.pop_front();             auto iter = _all_vm_threads.find(thread_id);            assert(iter != _all_vm_threads.cend());            next_thread = iter->second;             ++_running_vm_thread_count;            ++_gc_counter;             // This system thread now takes ownership of the vm thread            next_thread->system_thread_ownership.lock();            return next_thread;        }         if (!_all_vm_threads.empty() && _blocked_vm_thread_ids.size() == _all_vm_threads.size())        {            disvm::debug::log_msg(component_trace_t::scheduler, log_level_t::warning, "scheduler: deadlock detected");            assert(false && "VM thread deadlock detected");        }         if (_terminating)            return{};         if (disvm::debug::is_component_tracing_enabled<component_trace_t::scheduler>())            disvm::debug::log_msg(component_trace_t::scheduler, log_level_t::debug, "scheduler: worker: waiting");         _worker_event.wait(lock);         if (_terminating)            return{};    }} void default_scheduler_t::perform_gc(bool is_gc_thread, std::unique_lock<std::mutex> &all_vm_threads_lock){    if (!is_gc_thread)    {        _gc_complete = false;        all_vm_threads_lock.unlock();        std::unique_lock<std::mutex> lock{ _gc_wait };        _gc_done_event.wait(lock, [&]{ return _gc_complete.load(); });         // After the gc completes, take back the lock.        all_vm_threads_lock.lock();    }    else    {        auto result = std::vector<std::shared_ptr<const vm_thread_t>>{};        for (auto &p : _all_vm_threads)        {            auto &vm_thread = p.second->vm_thread;            if (vm_thread->get_registers().current_thread_state != vm_thread_state_t::broken)                result.push_back(vm_thread);        }         _vm.get_garbage_collector().collect(std::move(result));        _gc_complete = true;        _gc_done_event.notify_all();    }} bool default_scheduler_t::enqueue_thread_unsafe(thread_instance_t *thread_instance, vm_thread_state_t current_state){    assert(thread_instance != nullptr);     const auto thread_id = thread_instance->vm_thread->get_thread_id();    switch (current_state)    {    case vm_thread_state_t::ready:    case vm_thread_state_t::debug:    {        _runnable_vm_thread_ids.push_back(thread_id);         if (disvm::debug::is_component_tracing_enabled<component_trace_t::scheduler>())            disvm::debug::log_msg(component_trace_t::scheduler, log_level_t::debug, "scheduler: enqueue: %d", thread_id);         return true;    }     case vm_thread_state_t::blocked_in_alt:    case vm_thread_state_t::blocked_sending:    case vm_thread_state_t::blocked_receiving:    {        _blocked_vm_thread_ids.insert(thread_id);         if (disvm::debug::is_component_tracing_enabled<component_trace_t::scheduler>())            disvm::debug::log_msg(component_trace_t::scheduler, log_level_t::debug, "scheduler: blocked: %d", thread_id);         break;    }     case vm_thread_state_t::empty_stack:    case vm_thread_state_t::exiting:    {        if (disvm::debug::is_component_tracing_enabled<component_trace_t::scheduler>())            disvm::debug::log_msg(component_trace_t::scheduler, log_level_t::debug, "scheduler: exiting: %d", thread_id);         // Remove the thread        auto thread_to_remove = _all_vm_threads.find(thread_id);        assert(thread_to_remove != _all_vm_threads.cend());        _all_vm_threads.erase(thread_to_remove);         break;    }     case vm_thread_state_t::broken:    {        if (disvm::debug::is_component_tracing_enabled<component_trace_t::scheduler>())            disvm::debug::log_msg(component_trace_t::scheduler, log_level_t::debug, "scheduler: broken: %d", thread_id);         auto err_msg = thread_instance->vm_thread->get_error_message();        assert(err_msg != nullptr);        throw vm_term_request{ err_msg };    }     default:        assert(false && "Unexpected thread state");        throw vm_system_exception{ "Unexpected thread state" };    }     return false;}