lib/nfs/Manager: defer NfsConnection destruction

Avoids a crash that occurs when NfsConnection::OnSocketReady()
dereferences itself before returning.
This commit is contained in:
Max Kellermann 2014-11-25 10:42:52 +01:00
parent b293b16007
commit 3cef348f30
3 changed files with 56 additions and 9 deletions

2
NEWS
View File

@ -1,4 +1,6 @@
ver 0.19.5 (not yet released) ver 0.19.5 (not yet released)
* input
- nfs: fix crash on connection failure
* decoder * decoder
- dsdiff, dsf, opus: fix deadlock while seeking - dsdiff, dsf, opus: fix deadlock while seeking
- mp4v2: remove because of incompatible license - mp4v2: remove because of incompatible license

View File

@ -29,8 +29,10 @@ NfsManager::ManagedConnection::OnNfsConnectionError(Error &&error)
{ {
FormatError(error, "NFS error on %s:%s", GetServer(), GetExportName()); FormatError(error, "NFS error on %s:%s", GetServer(), GetExportName());
manager.connections.erase(manager.connections.iterator_to(*this)); /* defer deletion so the caller
delete this; (i.e. NfsConnection::OnSocketReady()) can still use this
object */
manager.ScheduleDelete(*this);
} }
inline bool inline bool
@ -59,7 +61,9 @@ NfsManager::Compare::operator()(const ManagedConnection &a,
NfsManager::~NfsManager() NfsManager::~NfsManager()
{ {
assert(loop.IsInside()); assert(GetEventLoop().IsInside());
CollectGarbage();
connections.clear_and_dispose([](ManagedConnection *c){ connections.clear_and_dispose([](ManagedConnection *c){
delete c; delete c;
@ -71,13 +75,13 @@ NfsManager::GetConnection(const char *server, const char *export_name)
{ {
assert(server != nullptr); assert(server != nullptr);
assert(export_name != nullptr); assert(export_name != nullptr);
assert(loop.IsInside()); assert(GetEventLoop().IsInside());
Map::insert_commit_data hint; Map::insert_commit_data hint;
auto result = connections.insert_check(LookupKey{server, export_name}, auto result = connections.insert_check(LookupKey{server, export_name},
Compare(), hint); Compare(), hint);
if (result.second) { if (result.second) {
auto c = new ManagedConnection(*this, loop, auto c = new ManagedConnection(*this, GetEventLoop(),
server, export_name); server, export_name);
connections.insert_commit(*c, hint); connections.insert_commit(*c, hint);
return *c; return *c;
@ -85,3 +89,19 @@ NfsManager::GetConnection(const char *server, const char *export_name)
return *result.first; return *result.first;
} }
} }
void
NfsManager::CollectGarbage()
{
assert(GetEventLoop().IsInside());
garbage.clear_and_dispose([](ManagedConnection *c){
delete c;
});
}
void
NfsManager::OnIdle()
{
CollectGarbage();
}

View File

@ -23,14 +23,16 @@
#include "check.h" #include "check.h"
#include "Connection.hxx" #include "Connection.hxx"
#include "Compiler.h" #include "Compiler.h"
#include "event/IdleMonitor.hxx"
#include <boost/intrusive/set.hpp> #include <boost/intrusive/set.hpp>
#include <boost/intrusive/slist.hpp>
/** /**
* A manager for NFS connections. Handles multiple connections to * A manager for NFS connections. Handles multiple connections to
* multiple NFS servers. * multiple NFS servers.
*/ */
class NfsManager { class NfsManager final : IdleMonitor {
struct LookupKey { struct LookupKey {
const char *server; const char *server;
const char *export_name; const char *export_name;
@ -38,6 +40,7 @@ class NfsManager {
class ManagedConnection final class ManagedConnection final
: public NfsConnection, : public NfsConnection,
public boost::intrusive::slist_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>,
public boost::intrusive::set_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>> { public boost::intrusive::set_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>> {
NfsManager &manager; NfsManager &manager;
@ -63,8 +66,6 @@ class NfsManager {
const LookupKey b) const; const LookupKey b) const;
}; };
EventLoop &loop;
/** /**
* Maps server and export_name to #ManagedConnection. * Maps server and export_name to #ManagedConnection.
*/ */
@ -74,9 +75,18 @@ class NfsManager {
Map connections; Map connections;
typedef boost::intrusive::slist<ManagedConnection> List;
/**
* A list of "garbage" connection objects. Their destruction
* is postponed because they were thrown into the garbage list
* when callers on the stack were still using them.
*/
List garbage;
public: public:
NfsManager(EventLoop &_loop) NfsManager(EventLoop &_loop)
:loop(_loop) {} :IdleMonitor(_loop) {}
/** /**
* Must be run from EventLoop's thread. * Must be run from EventLoop's thread.
@ -86,6 +96,21 @@ public:
gcc_pure gcc_pure
NfsConnection &GetConnection(const char *server, NfsConnection &GetConnection(const char *server,
const char *export_name); const char *export_name);
private:
void ScheduleDelete(ManagedConnection &c) {
connections.erase(connections.iterator_to(c));
garbage.push_front(c);
IdleMonitor::Schedule();
}
/**
* Delete all connections on the #garbage list.
*/
void CollectGarbage();
/* virtual methods from IdleMonitor */
void OnIdle() override;
}; };
#endif #endif