Evict plugins that fail to load.

This commit is contained in:
David Anderson 2015-09-20 00:57:02 -07:00
parent 62edc5f4c0
commit 87e9dee78b
3 changed files with 128 additions and 45 deletions

View File

@ -63,9 +63,9 @@ CPlugin::CPlugin(const char *file)
m_LibraryMissing(false), m_LibraryMissing(false),
m_pContext(nullptr), m_pContext(nullptr),
m_MaxClientsVar(nullptr), m_MaxClientsVar(nullptr),
m_ident(nullptr),
m_bGotAllLoaded(false), m_bGotAllLoaded(false),
m_FileVersion(0), m_FileVersion(0),
m_ident(nullptr),
m_LastFileModTime(0), m_LastFileModTime(0),
m_handle(BAD_HANDLE) m_handle(BAD_HANDLE)
{ {
@ -83,16 +83,7 @@ CPlugin::CPlugin(const char *file)
CPlugin::~CPlugin() CPlugin::~CPlugin()
{ {
if (m_handle) DestroyIdentity();
{
HandleSecurity sec;
sec.pOwner = g_PluginSys.GetIdentity();
sec.pIdentity = sec.pOwner;
handlesys->FreeHandle(m_handle, &sec);
g_ShareSys.DestroyIdentity(m_ident);
}
for (size_t i=0; i<m_configs.size(); i++) for (size_t i=0; i<m_configs.size(); i++)
delete m_configs[i]; delete m_configs[i];
m_configs.clear(); m_configs.clear();
@ -100,13 +91,71 @@ CPlugin::~CPlugin()
void CPlugin::InitIdentity() void CPlugin::InitIdentity()
{ {
if (!m_handle) if (m_handle)
{ return;
m_ident = g_ShareSys.CreateIdentity(g_PluginIdent, this);
m_handle = handlesys->CreateHandle(g_PluginType, this, g_PluginSys.GetIdentity(), g_PluginSys.GetIdentity(), NULL); m_ident = g_ShareSys.CreateIdentity(g_PluginIdent, this);
m_pRuntime->GetDefaultContext()->SetKey(1, m_ident); m_handle = handlesys->CreateHandle(g_PluginType, this, g_PluginSys.GetIdentity(), g_PluginSys.GetIdentity(), NULL);
m_pRuntime->GetDefaultContext()->SetKey(2, (IPlugin *)this); m_pRuntime->GetDefaultContext()->SetKey(1, m_ident);
m_pRuntime->GetDefaultContext()->SetKey(2, (IPlugin *)this);
}
void CPlugin::DestroyIdentity()
{
if (m_handle) {
HandleSecurity sec(g_PluginSys.GetIdentity(), g_PluginSys.GetIdentity());
handlesys->FreeHandle(m_handle, &sec);
m_handle = BAD_HANDLE;
} }
if (m_ident) {
g_ShareSys.DestroyIdentity(m_ident);
m_ident = nullptr;
}
}
bool CPlugin::IsEvictionCandidate() const
{
switch (Status()) {
case Plugin_Running:
case Plugin_Loaded:
// These states are valid, we should never evict.
return false;
case Plugin_BadLoad:
case Plugin_Uncompiled:
// These states imply that the plugin never loaded to begin with,
// so we have nothing to evict.
return false;
case Plugin_Evicted:
// We cannot be evicted twice.
return false;
default:
return true;
}
}
void CPlugin::FinishEviction()
{
assert(IsEvictionCandidate());
// Revoke our identity and handle. This could maybe be seen as bad faith,
// since other plugins could be holding the handle and will now error. But
// this was already an existing problem. We need a listener API to solve
// it.
DestroyIdentity();
// Note that we do not set our status to Plugin_Evicted. This is a pseudo-status
// so consumers won't attempt to read the context or runtime. The real state
// is reflected here.
m_state = PluginState::Evicted;
m_pRuntime = nullptr;
m_pPhrases = nullptr;
m_pContext = nullptr;
m_MaxClientsVar = nullptr;
m_Props.clear();
m_configs.clear();
m_Libraries.clear();
m_bGotAllLoaded = false;
m_FileVersion = 0;
} }
unsigned int CPlugin::CalcMemUsage() unsigned int CPlugin::CalcMemUsage()
@ -489,6 +538,15 @@ unsigned int CPlugin::GetSerial()
PluginStatus CPlugin::GetStatus() PluginStatus CPlugin::GetStatus()
{ {
return Status();
}
PluginStatus CPlugin::Status() const
{
// Even though we're evicted, we previously guaranteed a valid runtime
// for error/fail states. A new failure case above BadLoad solves this.
if (m_state == PluginState::Evicted)
return Plugin_Evicted;
return m_status; return m_status;
} }
@ -981,6 +1039,13 @@ void CPluginManager::AddPlugin(CPlugin *pPlugin)
for (ListenerIter iter(m_listeners); !iter.done(); iter.next()) for (ListenerIter iter(m_listeners); !iter.done(); iter.next())
(*iter)->OnPluginCreated(pPlugin); (*iter)->OnPluginCreated(pPlugin);
if (pPlugin->IsEvictionCandidate()) {
// If we get here, and the plugin isn't running, we evict it. This
// should be safe since our call stack should be empty.
Purge(pPlugin);
pPlugin->FinishEviction();
}
} }
void CPluginManager::LoadAll_SecondPass() void CPluginManager::LoadAll_SecondPass()
@ -991,7 +1056,8 @@ void CPluginManager::LoadAll_SecondPass()
char error[256] = {0}; char error[256] = {0};
if (!RunSecondPass(pPlugin)) { if (!RunSecondPass(pPlugin)) {
g_Logger.LogError("[SM] Unable to load plugin \"%s\": %s", pPlugin->GetFilename(), pPlugin->GetErrorMsg()); g_Logger.LogError("[SM] Unable to load plugin \"%s\": %s", pPlugin->GetFilename(), pPlugin->GetErrorMsg());
pPlugin->EvictWithError(Plugin_BadLoad, "%s", error); Purge(pPlugin);
pPlugin->FinishEviction();
} }
} }
} }
@ -1397,7 +1463,6 @@ bool CPluginManager::ScheduleUnload(CPlugin *pPlugin)
void CPluginManager::Purge(CPlugin *plugin) void CPluginManager::Purge(CPlugin *plugin)
{ {
assert(plugin->State() != PluginState::Unregistered);
// Go through our libraries and tell other plugins they're gone. // Go through our libraries and tell other plugins they're gone.
plugin->LibraryActions(LibraryAction_Removed); plugin->LibraryActions(LibraryAction_Removed);
@ -1424,7 +1489,11 @@ void CPluginManager::UnloadPluginImpl(CPlugin *pPlugin)
{ {
m_plugins.remove(pPlugin); m_plugins.remove(pPlugin);
m_LoadLookup.remove(pPlugin->GetFilename()); m_LoadLookup.remove(pPlugin->GetFilename());
Purge(pPlugin);
// Evicted plugins were already purged from external systems.
if (pPlugin->State() != PluginState::Evicted)
Purge(pPlugin);
delete pPlugin; delete pPlugin;
} }
@ -1635,7 +1704,7 @@ void CPluginManager::OnRootConsoleCommand(const char *cmdname, const ICommandArg
const sm_plugininfo_t *info = pl->GetPublicInfo(); const sm_plugininfo_t *info = pl->GetPublicInfo();
if (pl->GetStatus() != Plugin_Running && !pl->IsSilentlyFailed()) if (pl->GetStatus() != Plugin_Running && !pl->IsSilentlyFailed())
{ {
len += ke::SafeSprintf(buffer, sizeof(buffer), " %02d <%s>", id, GetStatusText(pl->GetStatus())); len += ke::SafeSprintf(buffer, sizeof(buffer), " %02d <%s>", id, GetStatusText(pl->GetDisplayStatus()));
/* Plugin has failed to load. */ /* Plugin has failed to load. */
fail_list.append(pl); fail_list.append(pl);
@ -1644,7 +1713,7 @@ void CPluginManager::OnRootConsoleCommand(const char *cmdname, const ICommandArg
{ {
len += ke::SafeSprintf(buffer, sizeof(buffer), " %02d", id); len += ke::SafeSprintf(buffer, sizeof(buffer), " %02d", id);
} }
if (pl->GetStatus() < Plugin_Created) if (pl->GetStatus() < Plugin_Created || pl->GetStatus() == Plugin_Evicted)
{ {
if (pl->IsSilentlyFailed()) if (pl->IsSilentlyFailed())
len += ke::SafeSprintf(&buffer[len], sizeof(buffer)-len, " Disabled:"); len += ke::SafeSprintf(&buffer[len], sizeof(buffer)-len, " Disabled:");

View File

@ -84,6 +84,9 @@ enum class PluginState
// The plugin is a member of the global plugin list. // The plugin is a member of the global plugin list.
Registered, Registered,
// The plugin has been evicted.
Evicted,
// The plugin is waiting to be unloaded. // The plugin is waiting to be unloaded.
WaitingToUnload WaitingToUnload
}; };
@ -103,6 +106,7 @@ public:
const char *GetFilename(); const char *GetFilename();
bool IsDebugging(); bool IsDebugging();
PluginStatus GetStatus(); PluginStatus GetStatus();
PluginStatus Status() const;
bool IsSilentlyFailed(); bool IsSilentlyFailed();
const sm_plugininfo_t *GetPublicInfo(); const sm_plugininfo_t *GetPublicInfo();
bool SetPauseState(bool paused); bool SetPauseState(bool paused);
@ -146,6 +150,7 @@ public:
public: public:
// Evicts the plugin from memory and sets an error state. // Evicts the plugin from memory and sets an error state.
void EvictWithError(PluginStatus status, const char *error_fmt, ...); void EvictWithError(PluginStatus status, const char *error_fmt, ...);
void FinishEviction();
// Initializes the plugin's identity information // Initializes the plugin's identity information
void InitIdentity(); void InitIdentity();
@ -173,6 +178,11 @@ public:
void SetRegistered(); void SetRegistered();
void SetWaitingToUnload(); void SetWaitingToUnload();
PluginStatus GetDisplayStatus() const {
return m_status;
}
bool IsEvictionCandidate() const;
public: public:
// Returns true if the plugin was running, but is now invalid. // Returns true if the plugin was running, but is now invalid.
bool WasRunning(); bool WasRunning();
@ -235,11 +245,12 @@ public:
void BindFakeNativesTo(CPlugin *other); void BindFakeNativesTo(CPlugin *other);
protected: protected:
bool ReadInfo();
void DependencyDropped(CPlugin *pOwner); void DependencyDropped(CPlugin *pOwner);
private: private:
time_t GetFileTimeStamp(); time_t GetFileTimeStamp();
bool ReadInfo();
void DestroyIdentity();
private: private:
// This information is static for the lifetime of the plugin. // This information is static for the lifetime of the plugin.
@ -264,13 +275,13 @@ private:
sp_pubvar_t *m_MaxClientsVar; sp_pubvar_t *m_MaxClientsVar;
StringHashMap<void *> m_Props; StringHashMap<void *> m_Props;
CVector<AutoConfig *> m_configs; CVector<AutoConfig *> m_configs;
IdentityToken_t *m_ident; List<String> m_Libraries;
bool m_bGotAllLoaded; bool m_bGotAllLoaded;
int m_FileVersion; int m_FileVersion;
// Information that survives past eviction. // Information that survives past eviction.
List<String> m_RequiredLibs; List<String> m_RequiredLibs;
List<String> m_Libraries; IdentityToken_t *m_ident;
time_t m_LastFileModTime; time_t m_LastFileModTime;
Handle_t m_handle; Handle_t m_handle;
char m_DateTime[256]; char m_DateTime[256];

View File

@ -38,7 +38,7 @@
#include <sp_vm_api.h> #include <sp_vm_api.h>
#define SMINTERFACE_PLUGINSYSTEM_NAME "IPluginManager" #define SMINTERFACE_PLUGINSYSTEM_NAME "IPluginManager"
#define SMINTERFACE_PLUGINSYSTEM_VERSION 5 #define SMINTERFACE_PLUGINSYSTEM_VERSION 6
/** Context user slot 3 is used Core for holding an IPluginContext pointer. */ /** Context user slot 3 is used Core for holding an IPluginContext pointer. */
#define SM_CONTEXTVAR_USER 3 #define SM_CONTEXTVAR_USER 3
@ -96,7 +96,13 @@ namespace SourceMod
// @brief The plugin could not be loaded. Either its file was missing // @brief The plugin could not be loaded. Either its file was missing
// or could not be recognized as a valid SourcePawn binary. Plugins // or could not be recognized as a valid SourcePawn binary. Plugins
// in this state do not have a context or runtime. // in this state do not have a context or runtime.
Plugin_BadLoad Plugin_BadLoad,
// @brief The plugin was once in a state <= Created, but has since
// been destroyed. We have left its IPlugin instance around for
// informational purposes. Plugins in this state do not have a
// context or runtime.
Plugin_Evicted
}; };
@ -275,38 +281,35 @@ namespace SourceMod
class IPluginsListener class IPluginsListener
{ {
public: public:
/** // @brief This callback should not be used since plugins may be in
* @brief Called when a plugin is created/mapped into memory. // an unusable state.
*/ virtual void OnPluginCreated(IPlugin *plugin) final
virtual void OnPluginCreated(IPlugin *plugin)
{ {
} }
/** // @brief Called when a plugin's required dependencies and natives have
* @brief Called when a plugin is fully loaded successfully. // been bound. Plugins at this phase may be in any state Failed or
*/ // lower. This is invoked immediately before OnPluginStart, and sometime
// after OnPluginCreated.
virtual void OnPluginLoaded(IPlugin *plugin) virtual void OnPluginLoaded(IPlugin *plugin)
{ {
} }
/** // @brief Called when a plugin is paused or unpaused.
* @brief Called when a plugin is paused or unpaused.
*/
virtual void OnPluginPauseChange(IPlugin *plugin, bool paused) virtual void OnPluginPauseChange(IPlugin *plugin, bool paused)
{ {
} }
/** // @brief Called when a plugin is about to be unloaded. This is called for
* @brief Called when a plugin is unloaded (only if fully loaded). // any plugin for which OnPluginLoaded was called, and is invoked
*/ // immediately after OnPluginEnd(). The plugin may be in any state Failed
// or lower.
virtual void OnPluginUnloaded(IPlugin *plugin) virtual void OnPluginUnloaded(IPlugin *plugin)
{ {
} }
/** // @brief Called when a plugin is destroyed. This is called on all plugins for
* @brief Called when a plugin is destroyed. // which OnPluginCreated was called. The plugin may be in any state.
* NOTE: Always called if Created, even if load failed.
*/
virtual void OnPluginDestroyed(IPlugin *plugin) virtual void OnPluginDestroyed(IPlugin *plugin)
{ {
} }