From 51464b43179ca53373080c14739be6feae4af9c5 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 14 Dec 2014 14:24:49 +0100 Subject: lib/nfs/Connection: add assertions --- src/lib/nfs/Connection.cxx | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/lib/nfs/Connection.cxx b/src/lib/nfs/Connection.cxx index 06d2a4d2a..410711275 100644 --- a/src/lib/nfs/Connection.cxx +++ b/src/lib/nfs/Connection.cxx @@ -39,6 +39,8 @@ NfsConnection::CancellableCallback::Stat(nfs_context *ctx, const char *path, Error &error) { + assert(connection.GetEventLoop().IsInside()); + int result = nfs_stat_async(ctx, path, Callback, this); if (result < 0) { error.Format(nfs_domain, "nfs_stat_async() failed: %s", @@ -54,6 +56,8 @@ NfsConnection::CancellableCallback::OpenDirectory(nfs_context *ctx, const char *path, Error &error) { + assert(connection.GetEventLoop().IsInside()); + int result = nfs_opendir_async(ctx, path, Callback, this); if (result < 0) { error.Format(nfs_domain, "nfs_opendir_async() failed: %s", @@ -69,6 +73,8 @@ NfsConnection::CancellableCallback::Open(nfs_context *ctx, const char *path, int flags, Error &error) { + assert(connection.GetEventLoop().IsInside()); + int result = nfs_open_async(ctx, path, flags, Callback, this); if (result < 0) { @@ -85,6 +91,8 @@ NfsConnection::CancellableCallback::Stat(nfs_context *ctx, struct nfsfh *fh, Error &error) { + assert(connection.GetEventLoop().IsInside()); + int result = nfs_fstat_async(ctx, fh, Callback, this); if (result < 0) { error.Format(nfs_domain, "nfs_fstat_async() failed: %s", @@ -100,6 +108,8 @@ NfsConnection::CancellableCallback::Read(nfs_context *ctx, struct nfsfh *fh, uint64_t offset, size_t size, Error &error) { + assert(connection.GetEventLoop().IsInside()); + int result = nfs_pread_async(ctx, fh, offset, size, Callback, this); if (result < 0) { error.Format(nfs_domain, "nfs_pread_async() failed: %s", @@ -113,6 +123,7 @@ NfsConnection::CancellableCallback::Read(nfs_context *ctx, struct nfsfh *fh, inline void NfsConnection::CancellableCallback::CancelAndScheduleClose(struct nfsfh *fh) { + assert(connection.GetEventLoop().IsInside()); assert(!open); assert(close_fh == nullptr); assert(fh != nullptr); @@ -124,6 +135,8 @@ NfsConnection::CancellableCallback::CancelAndScheduleClose(struct nfsfh *fh) inline void NfsConnection::CancellableCallback::Callback(int err, void *data) { + assert(connection.GetEventLoop().IsInside()); + if (!IsCancelled()) { assert(close_fh == nullptr); @@ -209,6 +222,7 @@ NfsConnection::RemoveLease(NfsLease &lease) bool NfsConnection::Stat(const char *path, NfsCallback &callback, Error &error) { + assert(GetEventLoop().IsInside()); assert(!callbacks.Contains(callback)); auto &c = callbacks.Add(callback, *this, false); @@ -225,6 +239,7 @@ bool NfsConnection::OpenDirectory(const char *path, NfsCallback &callback, Error &error) { + assert(GetEventLoop().IsInside()); assert(!callbacks.Contains(callback)); auto &c = callbacks.Add(callback, *this, true); @@ -240,12 +255,16 @@ NfsConnection::OpenDirectory(const char *path, NfsCallback &callback, const struct nfsdirent * NfsConnection::ReadDirectory(struct nfsdir *dir) { + assert(GetEventLoop().IsInside()); + return nfs_readdir(context, dir); } void NfsConnection::CloseDirectory(struct nfsdir *dir) { + assert(GetEventLoop().IsInside()); + return nfs_closedir(context, dir); } @@ -253,6 +272,7 @@ bool NfsConnection::Open(const char *path, int flags, NfsCallback &callback, Error &error) { + assert(GetEventLoop().IsInside()); assert(!callbacks.Contains(callback)); auto &c = callbacks.Add(callback, *this, true); @@ -268,6 +288,7 @@ NfsConnection::Open(const char *path, int flags, NfsCallback &callback, bool NfsConnection::Stat(struct nfsfh *fh, NfsCallback &callback, Error &error) { + assert(GetEventLoop().IsInside()); assert(!callbacks.Contains(callback)); auto &c = callbacks.Add(callback, *this, false); @@ -284,6 +305,7 @@ bool NfsConnection::Read(struct nfsfh *fh, uint64_t offset, size_t size, NfsCallback &callback, Error &error) { + assert(GetEventLoop().IsInside()); assert(!callbacks.Contains(callback)); auto &c = callbacks.Add(callback, *this, false); @@ -310,6 +332,8 @@ DummyCallback(int, struct nfs_context *, void *, void *) void NfsConnection::Close(struct nfsfh *fh) { + assert(GetEventLoop().IsInside()); + nfs_close_async(context, fh, DummyCallback, nullptr); ScheduleSocket(); } @@ -341,6 +365,7 @@ NfsConnection::DestroyContext() inline void NfsConnection::DeferClose(struct nfsfh *fh) { + assert(GetEventLoop().IsInside()); assert(in_event); assert(in_service); @@ -350,6 +375,7 @@ NfsConnection::DeferClose(struct nfsfh *fh) void NfsConnection::ScheduleSocket() { + assert(GetEventLoop().IsInside()); assert(context != nullptr); if (!SocketMonitor::IsDefined()) { @@ -367,6 +393,7 @@ NfsConnection::ScheduleSocket() bool NfsConnection::OnSocketReady(unsigned flags) { + assert(GetEventLoop().IsInside()); assert(deferred_close.empty()); bool closed = false; @@ -444,6 +471,7 @@ inline void NfsConnection::MountCallback(int status, gcc_unused nfs_context *nfs, gcc_unused void *data) { + assert(GetEventLoop().IsInside()); assert(context == nfs); mount_finished = true; @@ -468,6 +496,7 @@ NfsConnection::MountCallback(int status, nfs_context *nfs, void *data, inline bool NfsConnection::MountInternal(Error &error) { + assert(GetEventLoop().IsInside()); assert(context == nullptr); context = nfs_init_context(); @@ -538,6 +567,8 @@ NfsConnection::BroadcastError(Error &&error) void NfsConnection::RunDeferred() { + assert(GetEventLoop().IsInside()); + if (context == nullptr) { Error error; if (!MountInternal(error)) { -- cgit v1.2.3 From 3c29aa62713126ebe8cc6f020fe8d896b7164ae3 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 14 Dec 2014 14:47:32 +0100 Subject: event/Loop: read the "again" flag while holding mutex --- src/event/Loop.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/event/Loop.cxx b/src/event/Loop.cxx index 4ded68ff4..1bac7c551 100644 --- a/src/event/Loop.cxx +++ b/src/event/Loop.cxx @@ -179,9 +179,10 @@ EventLoop::Run() mutex.lock(); HandleDeferred(); busy = false; + const bool _again = again; mutex.unlock(); - if (again) + if (_again) /* re-evaluate timers because one of the IdleMonitors may have added a new timeout */ -- cgit v1.2.3 From 4b8d258cff85678c7ac90c03e337f9316e1f7b98 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 14 Dec 2014 15:09:55 +0100 Subject: lib/nfs/Connection: fix crash while canceling a failing Open() The method NfsConnection::CancellableCallback::Callback() will always invoke NfsConnection::Close() on the file handle, even if the void pointer is not a nfsfh. This can happen if the Open() was not successful, e.g. when the file does not exist. --- NEWS | 2 ++ src/lib/nfs/Connection.cxx | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index b40023771..c1f967b74 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,6 @@ ver 0.19.7 (not yet released) +* input + - nfs: fix crash while canceling a failing file open operation * playlist - don't skip non-existent songs in "listplaylist" * fix memory allocator bug on Windows diff --git a/src/lib/nfs/Connection.cxx b/src/lib/nfs/Connection.cxx index 410711275..c57cb9b79 100644 --- a/src/lib/nfs/Connection.cxx +++ b/src/lib/nfs/Connection.cxx @@ -156,8 +156,10 @@ NfsConnection::CancellableCallback::Callback(int err, void *data) allocated file handle immediately */ assert(close_fh == nullptr); - struct nfsfh *fh = (struct nfsfh *)data; - connection.Close(fh); + if (err >= 0) { + struct nfsfh *fh = (struct nfsfh *)data; + connection.Close(fh); + } } else if (close_fh != nullptr) connection.DeferClose(close_fh); -- cgit v1.2.3 From ab4bb26a0aeac99a7ea5aa9fdd93d379bf3c054d Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 14 Dec 2014 15:20:40 +0100 Subject: lib/nfs/Connection: make in_service and in_event debug-only flags --- src/lib/nfs/Connection.cxx | 9 +++++++++ src/lib/nfs/Connection.hxx | 2 ++ 2 files changed, 11 insertions(+) diff --git a/src/lib/nfs/Connection.cxx b/src/lib/nfs/Connection.cxx index c57cb9b79..c62b11acb 100644 --- a/src/lib/nfs/Connection.cxx +++ b/src/lib/nfs/Connection.cxx @@ -407,17 +407,21 @@ NfsConnection::OnSocketReady(unsigned flags) re-register it each time */ SocketMonitor::Steal(); +#ifndef NDEBUG assert(!in_event); in_event = true; assert(!in_service); in_service = true; +#endif int result = nfs_service(context, events_to_libnfs(flags)); +#ifndef NDEBUG assert(context != nullptr); assert(in_service); in_service = false; +#endif while (!deferred_close.empty()) { nfs_close_async(context, deferred_close.front(), @@ -460,8 +464,10 @@ NfsConnection::OnSocketReady(unsigned flags) closed = true; } +#ifndef NDEBUG assert(in_event); in_event = false; +#endif if (context != nullptr) ScheduleSocket(); @@ -509,8 +515,11 @@ NfsConnection::MountInternal(Error &error) postponed_mount_error.Clear(); mount_finished = false; + +#ifndef NDEBUG in_service = false; in_event = false; +#endif if (nfs_mount_async(context, server.c_str(), export_name.c_str(), MountCallback, this) != 0) { diff --git a/src/lib/nfs/Connection.hxx b/src/lib/nfs/Connection.hxx index aebafd4d3..e63782931 100644 --- a/src/lib/nfs/Connection.hxx +++ b/src/lib/nfs/Connection.hxx @@ -111,6 +111,7 @@ class NfsConnection : SocketMonitor, DeferredMonitor { Error postponed_mount_error; +#ifndef NDEBUG /** * True when nfs_service() is being called. */ @@ -121,6 +122,7 @@ class NfsConnection : SocketMonitor, DeferredMonitor { * event updates are omitted. */ bool in_event; +#endif bool mount_finished; -- cgit v1.2.3 From 7fa1a84ec37ba6e891d2eb9d557cf3ddfb902023 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 14 Dec 2014 15:38:09 +0100 Subject: lib/nfs/Connection: move code to method InternalClose() --- src/lib/nfs/Connection.cxx | 15 ++++++++++++--- src/lib/nfs/Connection.hxx | 5 +++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/lib/nfs/Connection.cxx b/src/lib/nfs/Connection.cxx index c62b11acb..433d49ecf 100644 --- a/src/lib/nfs/Connection.cxx +++ b/src/lib/nfs/Connection.cxx @@ -331,12 +331,22 @@ DummyCallback(int, struct nfs_context *, void *, void *) { } +inline void +NfsConnection::InternalClose(struct nfsfh *fh) +{ + assert(GetEventLoop().IsInside()); + assert(context != nullptr); + assert(fh != nullptr); + + nfs_close_async(context, fh, DummyCallback, nullptr); +} + void NfsConnection::Close(struct nfsfh *fh) { assert(GetEventLoop().IsInside()); - nfs_close_async(context, fh, DummyCallback, nullptr); + InternalClose(fh); ScheduleSocket(); } @@ -424,8 +434,7 @@ NfsConnection::OnSocketReady(unsigned flags) #endif while (!deferred_close.empty()) { - nfs_close_async(context, deferred_close.front(), - DummyCallback, nullptr); + InternalClose(deferred_close.front()); deferred_close.pop_front(); } diff --git a/src/lib/nfs/Connection.hxx b/src/lib/nfs/Connection.hxx index e63782931..93bb4e236 100644 --- a/src/lib/nfs/Connection.hxx +++ b/src/lib/nfs/Connection.hxx @@ -186,6 +186,11 @@ protected: private: void DestroyContext(); + /** + * Wrapper for nfs_close_async(). + */ + void InternalClose(struct nfsfh *fh); + /** * Invoke nfs_close_async() after nfs_service() returns. */ -- cgit v1.2.3 From 32bca6492034eefee1acb5c71a01a0481ba03245 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 14 Dec 2014 15:40:29 +0100 Subject: lib/nfs/Connection: add assertions --- src/lib/nfs/Connection.cxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/nfs/Connection.cxx b/src/lib/nfs/Connection.cxx index 433d49ecf..cae9ff102 100644 --- a/src/lib/nfs/Connection.cxx +++ b/src/lib/nfs/Connection.cxx @@ -380,6 +380,8 @@ NfsConnection::DeferClose(struct nfsfh *fh) assert(GetEventLoop().IsInside()); assert(in_event); assert(in_service); + assert(context != nullptr); + assert(fh != nullptr); deferred_close.push_front(fh); } -- cgit v1.2.3 From 80f2ba7fca533de38575bdaf737ea89284ed2b7b Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 14 Dec 2014 15:45:10 +0100 Subject: lib/nfs/Connection: move code to Service() --- src/lib/nfs/Connection.cxx | 35 +++++++++++++++++++++++------------ src/lib/nfs/Connection.hxx | 5 +++++ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/lib/nfs/Connection.cxx b/src/lib/nfs/Connection.cxx index cae9ff102..48db620be 100644 --- a/src/lib/nfs/Connection.cxx +++ b/src/lib/nfs/Connection.cxx @@ -404,20 +404,11 @@ NfsConnection::ScheduleSocket() SocketMonitor::Schedule(libnfs_to_events(nfs_which_events(context))); } -bool -NfsConnection::OnSocketReady(unsigned flags) +inline int +NfsConnection::Service(unsigned flags) { assert(GetEventLoop().IsInside()); - assert(deferred_close.empty()); - - bool closed = false; - - const bool was_mounted = mount_finished; - if (!mount_finished) - /* until the mount is finished, the NFS client may use - various sockets, therefore we unregister and - re-register it each time */ - SocketMonitor::Steal(); + assert(context != nullptr); #ifndef NDEBUG assert(!in_event); @@ -435,6 +426,26 @@ NfsConnection::OnSocketReady(unsigned flags) in_service = false; #endif + return result; +} + +bool +NfsConnection::OnSocketReady(unsigned flags) +{ + assert(GetEventLoop().IsInside()); + assert(deferred_close.empty()); + + bool closed = false; + + const bool was_mounted = mount_finished; + if (!mount_finished) + /* until the mount is finished, the NFS client may use + various sockets, therefore we unregister and + re-register it each time */ + SocketMonitor::Steal(); + + const int result = Service(flags); + while (!deferred_close.empty()) { InternalClose(deferred_close.front()); deferred_close.pop_front(); diff --git a/src/lib/nfs/Connection.hxx b/src/lib/nfs/Connection.hxx index 93bb4e236..3d872eb3a 100644 --- a/src/lib/nfs/Connection.hxx +++ b/src/lib/nfs/Connection.hxx @@ -207,6 +207,11 @@ private: void ScheduleSocket(); + /** + * Wrapper for nfs_service(). + */ + int Service(unsigned flags); + /* virtual methods from SocketMonitor */ virtual bool OnSocketReady(unsigned flags) override; -- cgit v1.2.3 From a543627abd19f321ddd4259e222e6437e8312417 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 14 Dec 2014 15:56:53 +0100 Subject: lib/nfs/Connection: fix memory leak (and assertion failure) nfs_destroy_context() will invoke all pending callbacks with err==-EINTR. In CancellableCallback::Callback(), this will invoke NfsConnection::DeferClose(), which however is only designed to be called from nfs_service(). In non-debug mode, this will leak memory because nfs_close_async() is never called. Workaround: before nfs_destroy_context(), invoke nfs_close_async() on all pending file handles. --- NEWS | 1 + src/lib/nfs/Cancellable.hxx | 6 ++++++ src/lib/nfs/Connection.cxx | 15 +++++++++++++++ src/lib/nfs/Connection.hxx | 7 +++++++ 4 files changed, 29 insertions(+) diff --git a/NEWS b/NEWS index c1f967b74..6e83245c4 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,7 @@ ver 0.19.7 (not yet released) * input - nfs: fix crash while canceling a failing file open operation + - nfs: fix memory leak on connection failure * playlist - don't skip non-existent songs in "listplaylist" * fix memory allocator bug on Windows diff --git a/src/lib/nfs/Cancellable.hxx b/src/lib/nfs/Cancellable.hxx index be4527ac3..151be0528 100644 --- a/src/lib/nfs/Cancellable.hxx +++ b/src/lib/nfs/Cancellable.hxx @@ -157,6 +157,12 @@ public: return *i; } + + template + void ForEach(F &&f) { + for (CT &i : list) + f(i); + } }; #endif diff --git a/src/lib/nfs/Connection.cxx b/src/lib/nfs/Connection.cxx index 48db620be..83b388dc4 100644 --- a/src/lib/nfs/Connection.cxx +++ b/src/lib/nfs/Connection.cxx @@ -132,6 +132,17 @@ NfsConnection::CancellableCallback::CancelAndScheduleClose(struct nfsfh *fh) Cancel(); } +inline void +NfsConnection::CancellableCallback::PrepareDestroyContext() +{ + assert(IsCancelled()); + + if (close_fh != nullptr) { + connection.InternalClose(close_fh); + close_fh = nullptr; + } +} + inline void NfsConnection::CancellableCallback::Callback(int err, void *data) { @@ -370,6 +381,10 @@ NfsConnection::DestroyContext() if (SocketMonitor::IsDefined()) SocketMonitor::Cancel(); + callbacks.ForEach([](CancellableCallback &c){ + c.PrepareDestroyContext(); + }); + nfs_destroy_context(context); context = nullptr; } diff --git a/src/lib/nfs/Connection.hxx b/src/lib/nfs/Connection.hxx index 3d872eb3a..e47ba404b 100644 --- a/src/lib/nfs/Connection.hxx +++ b/src/lib/nfs/Connection.hxx @@ -84,6 +84,13 @@ class NfsConnection : SocketMonitor, DeferredMonitor { */ void CancelAndScheduleClose(struct nfsfh *fh); + /** + * Called by NfsConnection::DestroyContext() right + * before nfs_destroy_context(). This object is given + * a chance to prepare for the latter. + */ + void PrepareDestroyContext(); + private: static void Callback(int err, struct nfs_context *nfs, void *data, void *private_data); -- cgit v1.2.3 From d653f35bb7488b2da3412fc23e16b4da1770f8d4 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 14 Dec 2014 22:49:09 +0100 Subject: lib/nfs/Connection: fix typo in code comment --- src/lib/nfs/Connection.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/nfs/Connection.cxx b/src/lib/nfs/Connection.cxx index 83b388dc4..e0d8ad774 100644 --- a/src/lib/nfs/Connection.cxx +++ b/src/lib/nfs/Connection.cxx @@ -485,7 +485,7 @@ NfsConnection::OnSocketReady(unsigned flags) closed = true; } else if (SocketMonitor::IsDefined() && nfs_get_fd(context) < 0) { /* this happens when rpc_reconnect_requeue() is called - after the connection broke, but autoreconnet was + after the connection broke, but autoreconnect was disabled - nfs_service() returns 0 */ Error error; const char *msg = nfs_get_error(context); -- cgit v1.2.3 From 02563a35f0ce4bb5154a68bd568d0263c76ec261 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 14 Dec 2014 22:48:46 +0100 Subject: lib/nfs/Connection: fix reconnect after mount failure When mounting had not yet finished, SocketMonitor::IsDefined() was always false, due to the workaround at the beginning of the function that calls SocketMonitor::Steal(). This commit drops the IsDefined() check because it was never necessary and breaks reconnect. --- NEWS | 1 + src/lib/nfs/Connection.cxx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 6e83245c4..d20dba397 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,7 @@ ver 0.19.7 (not yet released) * input - nfs: fix crash while canceling a failing file open operation - nfs: fix memory leak on connection failure + - nfs: fix reconnect after mount failure * playlist - don't skip non-existent songs in "listplaylist" * fix memory allocator bug on Windows diff --git a/src/lib/nfs/Connection.cxx b/src/lib/nfs/Connection.cxx index e0d8ad774..04c5a9e53 100644 --- a/src/lib/nfs/Connection.cxx +++ b/src/lib/nfs/Connection.cxx @@ -483,7 +483,7 @@ NfsConnection::OnSocketReady(unsigned flags) DestroyContext(); closed = true; - } else if (SocketMonitor::IsDefined() && nfs_get_fd(context) < 0) { + } else if (nfs_get_fd(context) < 0) { /* this happens when rpc_reconnect_requeue() is called after the connection broke, but autoreconnect was disabled - nfs_service() returns 0 */ -- cgit v1.2.3 From 1d3a09d3770cb9b0f9018bd4ee68240fca5171fe Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 14 Dec 2014 22:45:29 +0100 Subject: lib/nfs/Connection: add assertion --- src/lib/nfs/Connection.cxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/nfs/Connection.cxx b/src/lib/nfs/Connection.cxx index 04c5a9e53..56abf7129 100644 --- a/src/lib/nfs/Connection.cxx +++ b/src/lib/nfs/Connection.cxx @@ -501,6 +501,8 @@ NfsConnection::OnSocketReady(unsigned flags) closed = true; } + assert(context == nullptr || nfs_get_fd(context) >= 0); + #ifndef NDEBUG assert(in_event); in_event = false; -- cgit v1.2.3 From 7fa91ec175452823e04dba199bc48df12d3a64f0 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 14 Dec 2014 15:31:49 +0100 Subject: lib/nfs/Connection: add debug flag "in_destroy" --- src/lib/nfs/Connection.cxx | 6 ++++++ src/lib/nfs/Connection.hxx | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/src/lib/nfs/Connection.cxx b/src/lib/nfs/Connection.cxx index 56abf7129..4b6a6fc78 100644 --- a/src/lib/nfs/Connection.cxx +++ b/src/lib/nfs/Connection.cxx @@ -374,6 +374,11 @@ NfsConnection::DestroyContext() assert(GetEventLoop().IsInside()); assert(context != nullptr); +#ifndef NDEBUG + assert(!in_destroy); + in_destroy = true; +#endif + /* cancel pending DeferredMonitor that was scheduled to notify new leases */ DeferredMonitor::Cancel(); @@ -558,6 +563,7 @@ NfsConnection::MountInternal(Error &error) #ifndef NDEBUG in_service = false; in_event = false; + in_destroy = false; #endif if (nfs_mount_async(context, server.c_str(), export_name.c_str(), diff --git a/src/lib/nfs/Connection.hxx b/src/lib/nfs/Connection.hxx index e47ba404b..310ccdc44 100644 --- a/src/lib/nfs/Connection.hxx +++ b/src/lib/nfs/Connection.hxx @@ -129,6 +129,11 @@ class NfsConnection : SocketMonitor, DeferredMonitor { * event updates are omitted. */ bool in_event; + + /** + * True when DestroyContext() is being called. + */ + bool in_destroy; #endif bool mount_finished; -- cgit v1.2.3 From 82da364b8b9c6073e4af58cddc85357674a1a2e5 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 14 Dec 2014 23:27:57 +0100 Subject: lib/nfs/Connection: implement mount timeout --- NEWS | 1 + src/lib/nfs/Connection.cxx | 24 ++++++++++++++++++++++++ src/lib/nfs/Connection.hxx | 9 +++++++-- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index d20dba397..c84f4b891 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,7 @@ ver 0.19.7 (not yet released) - nfs: fix crash while canceling a failing file open operation - nfs: fix memory leak on connection failure - nfs: fix reconnect after mount failure + - nfs: implement mount timeout (60 seconds) * playlist - don't skip non-existent songs in "listplaylist" * fix memory allocator bug on Windows diff --git a/src/lib/nfs/Connection.cxx b/src/lib/nfs/Connection.cxx index 4b6a6fc78..9d9cd8a52 100644 --- a/src/lib/nfs/Connection.cxx +++ b/src/lib/nfs/Connection.cxx @@ -34,6 +34,8 @@ extern "C" { #include /* for POLLIN, POLLOUT */ +static constexpr unsigned NFS_MOUNT_TIMEOUT = 60; + inline bool NfsConnection::CancellableCallback::Stat(nfs_context *ctx, const char *path, @@ -379,6 +381,11 @@ NfsConnection::DestroyContext() in_destroy = true; #endif + if (!mount_finished) { + assert(TimeoutMonitor::IsActive()); + TimeoutMonitor::Cancel(); + } + /* cancel pending DeferredMonitor that was scheduled to notify new leases */ DeferredMonitor::Cancel(); @@ -528,6 +535,9 @@ NfsConnection::MountCallback(int status, gcc_unused nfs_context *nfs, mount_finished = true; + assert(TimeoutMonitor::IsActive() || in_destroy); + TimeoutMonitor::Cancel(); + if (status < 0) { postponed_mount_error.Format(nfs_domain, status, "nfs_mount_async() failed: %s", @@ -560,6 +570,8 @@ NfsConnection::MountInternal(Error &error) postponed_mount_error.Clear(); mount_finished = false; + TimeoutMonitor::ScheduleSeconds(NFS_MOUNT_TIMEOUT); + #ifndef NDEBUG in_service = false; in_event = false; @@ -620,6 +632,18 @@ NfsConnection::BroadcastError(Error &&error) BroadcastMountError(std::move(error)); } +void +NfsConnection::OnTimeout() +{ + assert(GetEventLoop().IsInside()); + assert(!mount_finished); + + mount_finished = true; + DestroyContext(); + + BroadcastMountError(Error(nfs_domain, "Mount timeout")); +} + void NfsConnection::RunDeferred() { diff --git a/src/lib/nfs/Connection.hxx b/src/lib/nfs/Connection.hxx index 310ccdc44..3969a7e8f 100644 --- a/src/lib/nfs/Connection.hxx +++ b/src/lib/nfs/Connection.hxx @@ -23,6 +23,7 @@ #include "Lease.hxx" #include "Cancellable.hxx" #include "event/SocketMonitor.hxx" +#include "event/TimeoutMonitor.hxx" #include "event/DeferredMonitor.hxx" #include "util/Error.hxx" @@ -40,7 +41,7 @@ class NfsCallback; /** * An asynchronous connection to a NFS server. */ -class NfsConnection : SocketMonitor, DeferredMonitor { +class NfsConnection : SocketMonitor, TimeoutMonitor, DeferredMonitor { class CancellableCallback : public CancellablePointer { NfsConnection &connection; @@ -142,7 +143,8 @@ public: gcc_nonnull_all NfsConnection(EventLoop &_loop, const char *_server, const char *_export_name) - :SocketMonitor(_loop), DeferredMonitor(_loop), + :SocketMonitor(_loop), TimeoutMonitor(_loop), + DeferredMonitor(_loop), server(_server), export_name(_export_name), context(nullptr) {} @@ -227,6 +229,9 @@ private: /* virtual methods from SocketMonitor */ virtual bool OnSocketReady(unsigned flags) override; + /* virtual methods from TimeoutMonitor */ + void OnTimeout() final; + /* virtual methods from DeferredMonitor */ virtual void RunDeferred() override; }; -- cgit v1.2.3 From 7e8474a85ab179d19395495c734fb6e9d57a57a3 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 15 Dec 2014 00:31:12 +0100 Subject: lib/nfs/Connection: unregister socket with SocketMonitor::Steal() SocketMonitor::Cancel() does not actually unregister the socket; it only disables the event. --- src/lib/nfs/Connection.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/nfs/Connection.cxx b/src/lib/nfs/Connection.cxx index 9d9cd8a52..6e9f77345 100644 --- a/src/lib/nfs/Connection.cxx +++ b/src/lib/nfs/Connection.cxx @@ -391,7 +391,7 @@ NfsConnection::DestroyContext() DeferredMonitor::Cancel(); if (SocketMonitor::IsDefined()) - SocketMonitor::Cancel(); + SocketMonitor::Steal(); callbacks.ForEach([](CancellableCallback &c){ c.PrepareDestroyContext(); -- cgit v1.2.3 From 68d1abdb851a839fefc55b00eb49eb22a399595b Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 15 Dec 2014 00:39:18 +0100 Subject: storage/nfs: clear last_error in SetState() Fixes bogus assertion failure. --- src/storage/plugins/NfsStorage.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/storage/plugins/NfsStorage.cxx b/src/storage/plugins/NfsStorage.cxx index 8ddb14250..823d662c5 100644 --- a/src/storage/plugins/NfsStorage.cxx +++ b/src/storage/plugins/NfsStorage.cxx @@ -146,6 +146,7 @@ private: const ScopeLock protect(mutex); state = _state; + last_error.Clear(); last_error.Set(error); cond.broadcast(); } -- cgit v1.2.3 From a48704925d6c3e5c01057192403e55f3663b315c Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 14 Dec 2014 21:16:34 +0100 Subject: storage/nfs: add timeout --- NEWS | 2 ++ src/lib/nfs/Blocking.cxx | 7 ++++++- src/lib/nfs/Blocking.hxx | 9 +++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index c84f4b891..34ab55db9 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,8 @@ ver 0.19.7 (not yet released) - nfs: fix memory leak on connection failure - nfs: fix reconnect after mount failure - nfs: implement mount timeout (60 seconds) +* storage + - nfs: implement I/O timeout (60 seconds) * playlist - don't skip non-existent songs in "listplaylist" * fix memory allocator bug on Windows diff --git a/src/lib/nfs/Blocking.cxx b/src/lib/nfs/Blocking.cxx index 5f769c408..58eaf6af2 100644 --- a/src/lib/nfs/Blocking.cxx +++ b/src/lib/nfs/Blocking.cxx @@ -20,7 +20,9 @@ #include "config.h" #include "Blocking.hxx" #include "Connection.hxx" +#include "Domain.hxx" #include "event/Call.hxx" +#include "util/Error.hxx" bool BlockingNfsOperation::Run(Error &_error) @@ -31,7 +33,10 @@ BlockingNfsOperation::Run(Error &_error) [this](){ connection.AddLease(*this); }); /* wait for completion */ - LockWaitFinished(); + if (!LockWaitFinished()) { + _error.Set(nfs_domain, 0, "Timeout"); + return false; + } /* check for error */ if (error.IsDefined()) { diff --git a/src/lib/nfs/Blocking.hxx b/src/lib/nfs/Blocking.hxx index f8354822d..eb16dfb8c 100644 --- a/src/lib/nfs/Blocking.hxx +++ b/src/lib/nfs/Blocking.hxx @@ -35,6 +35,8 @@ class NfsConnection; * thread, and method Run() waits for completion. */ class BlockingNfsOperation : protected NfsCallback, NfsLease { + static constexpr unsigned timeout_ms = 60000; + Mutex mutex; Cond cond; @@ -52,10 +54,13 @@ public: bool Run(Error &error); private: - void LockWaitFinished() { + bool LockWaitFinished() { const ScopeLock protect(mutex); while (!finished) - cond.wait(mutex); + if (!cond.timed_wait(mutex, timeout_ms)) + return false; + + return true; } /** -- cgit v1.2.3