server/state: Check that modules are accessed according to dependencies to avoid deadlocks
This commit is contained in:
parent
35700e7082
commit
3501693188
@ -37,6 +37,11 @@ Function naming:
|
|||||||
Do not use assert(); throw anonymous exceptions instead:
|
Do not use assert(); throw anonymous exceptions instead:
|
||||||
- if(fail) throw Exception("Thing failed");
|
- if(fail) throw Exception("Thing failed");
|
||||||
|
|
||||||
|
In normal operation, zero exceptions should be thrown. Unless any potential bugs
|
||||||
|
are occurring, you should be able to play the game fine if you set a breakpoint
|
||||||
|
on a GCC Linux build to __cxa_throw, which will stop the program immediately
|
||||||
|
when an exception occurs.
|
||||||
|
|
||||||
Naming:
|
Naming:
|
||||||
- "type": Numeric id representing a type
|
- "type": Numeric id representing a type
|
||||||
- "name": A string; generally works as an identifier but not necessarily
|
- "name": A string; generally works as an identifier but not necessarily
|
||||||
|
@ -68,7 +68,5 @@ Buildat TODO
|
|||||||
- Make some kind of an ace of spades clone (or less of a clone); something that
|
- Make some kind of an ace of spades clone (or less of a clone); something that
|
||||||
requires the game to control the creation and deletion of the world anyway,
|
requires the game to control the creation and deletion of the world anyway,
|
||||||
and implement the required interfaces
|
and implement the required interfaces
|
||||||
- Check that modules are accessed in order of dependency to effectively maintain
|
|
||||||
recursive locking
|
|
||||||
- Fix everything in /games/
|
- Fix everything in /games/
|
||||||
- Use builtin/main_context
|
- Use builtin/main_context
|
||||||
|
@ -65,8 +65,10 @@ namespace interface
|
|||||||
virtual ss_ get_module_path(const ss_ &module_name) = 0;
|
virtual ss_ get_module_path(const ss_ &module_name) = 0;
|
||||||
virtual bool has_module(const ss_ &module_name) = 0;
|
virtual bool has_module(const ss_ &module_name) = 0;
|
||||||
virtual sv_<ss_> get_loaded_modules() = 0;
|
virtual sv_<ss_> get_loaded_modules() = 0;
|
||||||
virtual bool access_module(const ss_ &module_name,
|
virtual bool access_module(const ss_ &module_name, // Always returns true
|
||||||
std::function<void(interface::Module*)> cb) = 0;
|
std::function<void(interface::Module*)> cb) = 0;
|
||||||
|
/*virtual bool access_module_optional(const ss_ &module_name, TODO
|
||||||
|
std::function<void(interface::Module*)> cb) = 0;*/
|
||||||
|
|
||||||
virtual void sub_event(struct Module *module, const Event::Type &type) = 0;
|
virtual void sub_event(struct Module *module, const Event::Type &type) = 0;
|
||||||
virtual void emit_event(Event event) = 0;
|
virtual void emit_event(Event event) = 0;
|
||||||
|
@ -50,6 +50,7 @@ struct ModuleThread: public interface::ThreadedThing
|
|||||||
|
|
||||||
struct ModuleContainer
|
struct ModuleContainer
|
||||||
{
|
{
|
||||||
|
interface::Server *server;
|
||||||
interface::ThreadLocalKey *thread_local_key; // Stores mc*
|
interface::ThreadLocalKey *thread_local_key; // Stores mc*
|
||||||
up_<interface::Module> module;
|
up_<interface::Module> module;
|
||||||
interface::ModuleInfo info;
|
interface::ModuleInfo info;
|
||||||
@ -69,9 +70,11 @@ struct ModuleContainer
|
|||||||
// post() when direct_cb becomes free, wait() for that to happen
|
// post() when direct_cb becomes free, wait() for that to happen
|
||||||
interface::Semaphore direct_cb_free_sem;
|
interface::Semaphore direct_cb_free_sem;
|
||||||
|
|
||||||
ModuleContainer(interface::ThreadLocalKey *thread_local_key = NULL,
|
ModuleContainer(interface::Server *server = nullptr,
|
||||||
|
interface::ThreadLocalKey *thread_local_key = NULL,
|
||||||
interface::Module *module = NULL,
|
interface::Module *module = NULL,
|
||||||
const interface::ModuleInfo &info = interface::ModuleInfo()):
|
const interface::ModuleInfo &info = interface::ModuleInfo()):
|
||||||
|
server(server),
|
||||||
thread_local_key(thread_local_key),
|
thread_local_key(thread_local_key),
|
||||||
module(module),
|
module(module),
|
||||||
info(info)
|
info(info)
|
||||||
@ -196,6 +199,7 @@ void ModuleThread::run(interface::Thread *thread)
|
|||||||
cs(mc->info.name));
|
cs(mc->info.name));
|
||||||
} catch(std::exception &e){
|
} catch(std::exception &e){
|
||||||
log_w(MODULE, "direct_cb() failed: %s", e.what());
|
log_w(MODULE, "direct_cb() failed: %s", e.what());
|
||||||
|
mc->server->shutdown(1, mc->info.name+" failed: "+e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@ -215,6 +219,7 @@ void ModuleThread::run(interface::Thread *thread)
|
|||||||
mc->module->event(event.type, event.p.get());
|
mc->module->event(event.type, event.p.get());
|
||||||
} catch(std::exception &e){
|
} catch(std::exception &e){
|
||||||
log_w(MODULE, "module->event() failed: %s", e.what());
|
log_w(MODULE, "module->event() failed: %s", e.what());
|
||||||
|
mc->server->shutdown(1, mc->info.name+" failed: "+e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -495,7 +500,7 @@ struct CState: public State, public interface::Server
|
|||||||
m_module_info[info.name] = info;
|
m_module_info[info.name] = info;
|
||||||
|
|
||||||
mc = sp_<ModuleContainer>(new ModuleContainer(
|
mc = sp_<ModuleContainer>(new ModuleContainer(
|
||||||
&m_thread_local_mc_key, m, info));
|
this, &m_thread_local_mc_key, m, info));
|
||||||
m_modules[info.name] = mc;
|
m_modules[info.name] = mc;
|
||||||
m_module_load_order.push_back(info.name);
|
m_module_load_order.push_back(info.name);
|
||||||
}
|
}
|
||||||
@ -531,7 +536,7 @@ struct CState: public State, public interface::Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
mc = sp_<ModuleContainer>(new ModuleContainer(
|
mc = sp_<ModuleContainer>(new ModuleContainer(
|
||||||
&m_thread_local_mc_key, m, info));
|
this, &m_thread_local_mc_key, m, info));
|
||||||
m_modules[info.name] = mc;
|
m_modules[info.name] = mc;
|
||||||
m_module_load_order.push_back(info.name);
|
m_module_load_order.push_back(info.name);
|
||||||
}
|
}
|
||||||
@ -707,18 +712,73 @@ struct CState: public State, public interface::Server
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call with m_modules_mutex locked
|
||||||
|
bool is_dependency_u(ModuleContainer *mc_should_be_dependent,
|
||||||
|
ModuleContainer *mc_should_be_dependency)
|
||||||
|
{
|
||||||
|
const ss_ &search_dep_name = mc_should_be_dependency->info.name;
|
||||||
|
const interface::ModuleInfo &info = mc_should_be_dependent->info;
|
||||||
|
// Breadth-first
|
||||||
|
for(const interface::ModuleDependency &dep : info.meta.dependencies){
|
||||||
|
/*log_t(MODULE, "is_dependency_u(): \"%s\" has dependency \"%s\"; "
|
||||||
|
"searching for \"%s\"",
|
||||||
|
cs(info.name), cs(dep.module), cs(search_dep_name));*/
|
||||||
|
if(dep.module == search_dep_name)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for(const interface::ModuleDependency &dep : info.meta.dependencies){
|
||||||
|
auto it = m_modules.find(dep.module);
|
||||||
|
if(it == m_modules.end())
|
||||||
|
continue;
|
||||||
|
ModuleContainer *mc_dependency = it->second.get();
|
||||||
|
bool is = is_dependency_u(mc_dependency, mc_should_be_dependency);
|
||||||
|
if(is)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/*log_t(MODULE, "is_dependency_u(): \"%s\" does not depend on \"%s\"",
|
||||||
|
cs(info.name), cs(search_dep_name));*/
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throws on invalid access
|
||||||
|
// Call with m_modules_mutex locked
|
||||||
|
void check_valid_access_u(
|
||||||
|
ModuleContainer *target_mc,
|
||||||
|
ModuleContainer *caller_mc
|
||||||
|
){
|
||||||
|
const ss_ &target_name = target_mc->info.name;
|
||||||
|
const ss_ &caller_name = caller_mc->info.name;
|
||||||
|
|
||||||
|
// Access is invalid if target is caller
|
||||||
|
if(caller_mc == target_mc)
|
||||||
|
throw Exception("Cannot access \""+target_name+"\" from \""+
|
||||||
|
caller_name+"\": Accessing itself is disallowed");
|
||||||
|
|
||||||
|
// Access is invalid if target is being accessed by somebody who is
|
||||||
|
// being accessed by caller, directly or indirectly
|
||||||
|
if(!is_dependency_u(caller_mc, target_mc))
|
||||||
|
throw Exception("Cannot access \""+target_name+"\" from \""+
|
||||||
|
caller_name+"\": Not a dependency");
|
||||||
|
|
||||||
|
// Access is valid
|
||||||
|
}
|
||||||
|
|
||||||
bool access_module(const ss_ &module_name,
|
bool access_module(const ss_ &module_name,
|
||||||
std::function<void(interface::Module*)> cb)
|
std::function<void(interface::Module*)> cb)
|
||||||
{
|
{
|
||||||
sp_<ModuleContainer> mc;
|
sp_<ModuleContainer> mc;
|
||||||
|
ss_ caller_module_name;
|
||||||
{
|
{
|
||||||
// This prevents module from being deleted while a reference is
|
|
||||||
// being copied
|
|
||||||
interface::MutexScope ms(m_modules_mutex);
|
interface::MutexScope ms(m_modules_mutex);
|
||||||
|
|
||||||
auto it = m_modules.find(module_name);
|
auto it = m_modules.find(module_name);
|
||||||
if(it == m_modules.end())
|
if(it == m_modules.end())
|
||||||
return false;
|
throw Exception("access_module(): Module \""+module_name+
|
||||||
|
"\" not found");
|
||||||
mc = it->second;
|
mc = it->second;
|
||||||
|
if(!mc)
|
||||||
|
throw Exception("access_module(): Module \""+module_name+
|
||||||
|
"\" container is null");
|
||||||
|
|
||||||
// Get container of current module
|
// Get container of current module
|
||||||
ModuleContainer *caller_mc =
|
ModuleContainer *caller_mc =
|
||||||
@ -726,15 +786,22 @@ struct CState: public State, public interface::Server
|
|||||||
if(caller_mc){
|
if(caller_mc){
|
||||||
log_t(MODULE, "access_module(\"%s\"): Called by \"%s\"",
|
log_t(MODULE, "access_module(\"%s\"): Called by \"%s\"",
|
||||||
cs(mc->info.name), cs(caller_mc->info.name));
|
cs(mc->info.name), cs(caller_mc->info.name));
|
||||||
|
|
||||||
|
// Throws exception if not valid.
|
||||||
|
// If accessing a module from a nested access_module(), this
|
||||||
|
// function is called from the thread of the nested module,
|
||||||
|
// effectively taking into account the lock hierarchy.
|
||||||
|
check_valid_access_u(mc.get(), caller_mc);
|
||||||
|
|
||||||
|
// Access OK; copy the caller's name so we can use it
|
||||||
|
caller_module_name = caller_mc->info.name;
|
||||||
} else {
|
} else {
|
||||||
log_t(MODULE, "access_module(\"%s\"): Called by something else"
|
log_t(MODULE, "access_module(\"%s\"): Called by something else"
|
||||||
" than a module", cs(mc->info.name));
|
" than a module", cs(mc->info.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Check that the module being accessed is a direct or
|
|
||||||
// indirect dependency of the module accessing it, and not the
|
|
||||||
// module itself
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Execute callback in module thread
|
||||||
mc->execute_direct_cb(cb);
|
mc->execute_direct_cb(cb);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user