mirror of
https://github.com/alliedmodders/metamod-source.git
synced 2025-02-22 15:54:14 +01:00
Delay unloading of plugins until it is absolutely safe (bug 5034, r=ds).
This commit is contained in:
parent
3cdeb6d0d2
commit
12ff1edd6a
@ -428,6 +428,12 @@ bool Command_Meta(IMetamodSourceCommandInfo *info)
|
|||||||
bool already;
|
bool already;
|
||||||
CPluginManager::CPlugin *pl;
|
CPluginManager::CPlugin *pl;
|
||||||
|
|
||||||
|
// If we've recently tried to unload plugins, they might still
|
||||||
|
// be in the unload queue. Force them out now. This is not
|
||||||
|
// lowered to CPluginManager because it's not strictly safe
|
||||||
|
// there.
|
||||||
|
g_SourceHook.ResolvePendingUnloads(true);
|
||||||
|
|
||||||
PluginId id = g_PluginMngr.Load(full_path, Pl_Console, already, error, sizeof(error));
|
PluginId id = g_PluginMngr.Load(full_path, Pl_Console, already, error, sizeof(error));
|
||||||
pl = g_PluginMngr.FindById(id);
|
pl = g_PluginMngr.FindById(id);
|
||||||
if (!pl || id < Pl_MinId || (pl->m_Status < Pl_Paused))
|
if (!pl || id < Pl_MinId || (pl->m_Status < Pl_Paused))
|
||||||
|
@ -382,6 +382,36 @@ const char *CPluginManager::GetStatusText(CPlugin *pl)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Unloader : public SourceHook::Impl::UnloadListener
|
||||||
|
{
|
||||||
|
CPluginManager::CPlugin *plugin_;
|
||||||
|
bool destroy_;
|
||||||
|
|
||||||
|
Unloader(CPluginManager::CPlugin *plugin, bool destroy)
|
||||||
|
: plugin_(plugin), destroy_(destroy)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
void ReadyToUnload(SourceHook::Plugin plug)
|
||||||
|
{
|
||||||
|
if (plugin_->m_UnloadFn != NULL)
|
||||||
|
plugin_->m_UnloadFn();
|
||||||
|
|
||||||
|
dlclose(plugin_->m_Lib);
|
||||||
|
|
||||||
|
if (destroy_)
|
||||||
|
{
|
||||||
|
delete plugin_;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
plugin_->m_Lib = NULL;
|
||||||
|
plugin_->m_API = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
CPluginManager::CPlugin *CPluginManager::_Load(const char *file, PluginId source, char *error, size_t maxlen)
|
CPluginManager::CPlugin *CPluginManager::_Load(const char *file, PluginId source, char *error, size_t maxlen)
|
||||||
{
|
{
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
@ -552,17 +582,8 @@ CPluginManager::CPlugin *CPluginManager::_Load(const char *file, PluginId source
|
|||||||
if (pl->m_Lib && (pl->m_Status < Pl_Paused))
|
if (pl->m_Lib && (pl->m_Status < Pl_Paused))
|
||||||
{
|
{
|
||||||
pl->m_Events.clear();
|
pl->m_Events.clear();
|
||||||
g_SourceHook.UnloadPlugin(pl->m_Id);
|
|
||||||
UnregAllConCmds(pl);
|
UnregAllConCmds(pl);
|
||||||
|
g_SourceHook.UnloadPlugin(pl->m_Id, new Unloader(pl, false));
|
||||||
if (pl->m_UnloadFn != NULL)
|
|
||||||
{
|
|
||||||
pl->m_UnloadFn();
|
|
||||||
}
|
|
||||||
|
|
||||||
dlclose(pl->m_Lib);
|
|
||||||
pl->m_Lib = NULL;
|
|
||||||
pl->m_API = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pl;
|
return pl;
|
||||||
@ -580,23 +601,9 @@ bool CPluginManager::_Unload(CPluginManager::CPlugin *pl, bool force, char *erro
|
|||||||
//Note, we'll always tell the plugin it will be unloading...
|
//Note, we'll always tell the plugin it will be unloading...
|
||||||
if (pl->m_API->Unload(error, maxlen) || force)
|
if (pl->m_API->Unload(error, maxlen) || force)
|
||||||
{
|
{
|
||||||
//Make sure to detach it from sourcehook!
|
|
||||||
g_SourceHook.UnloadPlugin(pl->m_Id);
|
|
||||||
|
|
||||||
pl->m_Events.clear();
|
pl->m_Events.clear();
|
||||||
|
|
||||||
UnregAllConCmds(pl);
|
UnregAllConCmds(pl);
|
||||||
|
|
||||||
if (pl->m_UnloadFn != NULL)
|
|
||||||
{
|
|
||||||
pl->m_UnloadFn();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Clean up the DLL
|
|
||||||
dlclose(pl->m_Lib);
|
|
||||||
pl->m_Lib = NULL;
|
|
||||||
pl->m_API = NULL;
|
|
||||||
|
|
||||||
//Remove the plugin from the list
|
//Remove the plugin from the list
|
||||||
PluginIter i;
|
PluginIter i;
|
||||||
for (i=m_Plugins.begin(); i!=m_Plugins.end(); i++)
|
for (i=m_Plugins.begin(); i!=m_Plugins.end(); i++)
|
||||||
@ -607,9 +614,9 @@ bool CPluginManager::_Unload(CPluginManager::CPlugin *pl, bool force, char *erro
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Free its memory
|
|
||||||
delete pl;
|
|
||||||
|
|
||||||
|
//Make sure to detach it from sourcehook!
|
||||||
|
g_SourceHook.UnloadPlugin(pl->m_Id, new Unloader(pl, true));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
/* ======== SourceHook ========
|
/* ======== SourceHook ========
|
||||||
|
* vim: set ts=4 sw=4 tw=99 noet:
|
||||||
* Copyright (C) 2004-2010 Metamod:Source Development Team
|
* Copyright (C) 2004-2010 Metamod:Source Development Team
|
||||||
* No warranties of any kind
|
* No warranties of any kind
|
||||||
*
|
*
|
||||||
@ -357,7 +358,7 @@ namespace SourceHook
|
|||||||
return m_ContextStack.front().pOverrideRet;
|
return m_ContextStack.front().pOverrideRet;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSourceHookImpl::UnloadPlugin(Plugin plug)
|
void CSourceHookImpl::UnloadPlugin(Plugin plug, UnloadListener *listener)
|
||||||
{
|
{
|
||||||
// 1) Remove all hooks by this plugin
|
// 1) Remove all hooks by this plugin
|
||||||
|
|
||||||
@ -375,6 +376,20 @@ namespace SourceHook
|
|||||||
else
|
else
|
||||||
++iter;
|
++iter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fix for bug 5034. It's pretty tricky to find whether hookmanagers owned by this
|
||||||
|
// plugin are active. We could change how hook managers are tracked in SH, and lazily
|
||||||
|
// free them as the context stack drops to 0, or we could change the pubfunc API to
|
||||||
|
// know whether it's active or not. Rather than deal with this extra complexity, we
|
||||||
|
// just conservatively wait until the context stack hits 0 before unloading.
|
||||||
|
if (m_ContextStack.size() == 0)
|
||||||
|
{
|
||||||
|
listener->ReadyToUnload(plug);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_PendingUnloads.push_back(new PendingUnload(listener, plug));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CHookManList::iterator CSourceHookImpl::RemoveHookManager(CHookManList::iterator hookman_iter)
|
CHookManList::iterator CSourceHookImpl::RemoveHookManager(CHookManList::iterator hookman_iter)
|
||||||
@ -571,12 +586,41 @@ namespace SourceHook
|
|||||||
return pCtx;
|
return pCtx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CSourceHookImpl::ResolvePendingUnloads(bool force)
|
||||||
|
{
|
||||||
|
List<PendingUnload *>::iterator iter = m_PendingUnloads.begin();
|
||||||
|
while (iter != m_PendingUnloads.end())
|
||||||
|
{
|
||||||
|
PendingUnload *unload = *iter;
|
||||||
|
|
||||||
|
if (!force && !unload->deactivated())
|
||||||
|
{
|
||||||
|
// Unless being forced, wait one drop of the context stack
|
||||||
|
// before actually unloading. Otherwise, we'll still return
|
||||||
|
// to unloaded memory.
|
||||||
|
unload->deactivate();
|
||||||
|
iter++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
unload->listener()->ReadyToUnload(unload->plugin());
|
||||||
|
delete unload;
|
||||||
|
iter = m_PendingUnloads.erase(iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CSourceHookImpl::EndContext(IHookContext *pCtx)
|
void CSourceHookImpl::EndContext(IHookContext *pCtx)
|
||||||
{
|
{
|
||||||
// Do clean up task, if any is associated with this context
|
// Do clean up task, if any is associated with this context
|
||||||
m_ContextStack.front().DoCleanupTaskAndDeleteIt();
|
m_ContextStack.front().DoCleanupTaskAndDeleteIt();
|
||||||
// Then remove it
|
// Then remove it
|
||||||
m_ContextStack.pop();
|
m_ContextStack.pop();
|
||||||
|
|
||||||
|
// If we've reached 0 contexts and there are pending unloads,
|
||||||
|
// resolve them now.
|
||||||
|
if (m_ContextStack.size() == 0 && m_PendingUnloads.size() != 0)
|
||||||
|
ResolvePendingUnloads();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSourceHookImpl::CompleteShutdown()
|
void CSourceHookImpl::CompleteShutdown()
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
/* ======== SourceHook ========
|
/* ======== SourceHook ========
|
||||||
|
* vim: set ts=4 sw=4 tw=99 noet:
|
||||||
* Copyright (C) 2004-2010 Metamod:Source Development Team
|
* Copyright (C) 2004-2010 Metamod:Source Development Team
|
||||||
* No warranties of any kind
|
* No warranties of any kind
|
||||||
*
|
*
|
||||||
@ -255,6 +256,42 @@ namespace SourceHook
|
|||||||
|
|
||||||
typedef CStack<CHookContext> HookContextStack;
|
typedef CStack<CHookContext> HookContextStack;
|
||||||
|
|
||||||
|
class UnloadListener
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void ReadyToUnload(Plugin plug) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PendingUnload
|
||||||
|
{
|
||||||
|
UnloadListener *listener_;
|
||||||
|
Plugin plug_;
|
||||||
|
bool deactivated_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PendingUnload(UnloadListener *listener, Plugin plug)
|
||||||
|
: listener_(listener), plug_(plug), deactivated_(false)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
Plugin plugin() const
|
||||||
|
{
|
||||||
|
return plug_;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnloadListener *listener() const
|
||||||
|
{
|
||||||
|
return listener_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void deactivate()
|
||||||
|
{
|
||||||
|
deactivated_ = true;
|
||||||
|
}
|
||||||
|
bool deactivated() const
|
||||||
|
{
|
||||||
|
return deactivated_;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class CSourceHookImpl : public ISourceHook
|
class CSourceHookImpl : public ISourceHook
|
||||||
{
|
{
|
||||||
@ -263,6 +300,7 @@ namespace SourceHook
|
|||||||
CVfnPtrList m_VfnPtrs;
|
CVfnPtrList m_VfnPtrs;
|
||||||
CHookIDManager m_HookIDMan;
|
CHookIDManager m_HookIDMan;
|
||||||
HookContextStack m_ContextStack;
|
HookContextStack m_ContextStack;
|
||||||
|
List<PendingUnload *> m_PendingUnloads;
|
||||||
|
|
||||||
bool SetHookPaused(int hookid, bool paused);
|
bool SetHookPaused(int hookid, bool paused);
|
||||||
CHookManList::iterator RemoveHookManager(CHookManList::iterator iter);
|
CHookManList::iterator RemoveHookManager(CHookManList::iterator iter);
|
||||||
@ -304,10 +342,16 @@ namespace SourceHook
|
|||||||
|
|
||||||
void *GetOverrideRetPtr(); //!< Used for setting the override return value
|
void *GetOverrideRetPtr(); //!< Used for setting the override return value
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* @brief Make sure that a plugin is not used by any other plugins anymore, and unregister all its hook managers
|
* @brief Make sure that a plugin is not used by any
|
||||||
*/
|
* other plugins anymore, and unregister all its hook
|
||||||
void UnloadPlugin(Plugin plug);
|
* managers. If any hooks owned by this plugin are
|
||||||
|
* still on the callstack, defers notifying the listener
|
||||||
|
* until the count has dropped to 0.
|
||||||
|
*/
|
||||||
|
void UnloadPlugin(Plugin plug, UnloadListener *listener);
|
||||||
|
|
||||||
|
void ResolvePendingUnloads(bool force = false);
|
||||||
|
|
||||||
void RemoveHookManager(Plugin plug, HookManagerPubFunc pubFunc);
|
void RemoveHookManager(Plugin plug, HookManagerPubFunc pubFunc);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user