Merge branch 'v0.19.x'

This commit is contained in:
Max Kellermann 2014-12-15 00:46:56 +01:00
commit adfc5db3d2
8 changed files with 190 additions and 25 deletions

7
NEWS
View File

@ -19,6 +19,13 @@ ver 0.20 (not yet released)
* remove dependency on GLib * remove dependency on GLib
ver 0.19.7 (not yet released) 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
- nfs: implement mount timeout (60 seconds)
* storage
- nfs: implement I/O timeout (60 seconds)
* playlist * playlist
- don't skip non-existent songs in "listplaylist" - don't skip non-existent songs in "listplaylist"
* fix memory allocator bug on Windows * fix memory allocator bug on Windows

View File

@ -179,9 +179,10 @@ EventLoop::Run()
mutex.lock(); mutex.lock();
HandleDeferred(); HandleDeferred();
busy = false; busy = false;
const bool _again = again;
mutex.unlock(); mutex.unlock();
if (again) if (_again)
/* re-evaluate timers because one of the /* re-evaluate timers because one of the
IdleMonitors may have added a new IdleMonitors may have added a new
timeout */ timeout */

View File

@ -20,7 +20,9 @@
#include "config.h" #include "config.h"
#include "Blocking.hxx" #include "Blocking.hxx"
#include "Connection.hxx" #include "Connection.hxx"
#include "Domain.hxx"
#include "event/Call.hxx" #include "event/Call.hxx"
#include "util/Error.hxx"
bool bool
BlockingNfsOperation::Run(Error &_error) BlockingNfsOperation::Run(Error &_error)
@ -31,7 +33,10 @@ BlockingNfsOperation::Run(Error &_error)
[this](){ connection.AddLease(*this); }); [this](){ connection.AddLease(*this); });
/* wait for completion */ /* wait for completion */
LockWaitFinished(); if (!LockWaitFinished()) {
_error.Set(nfs_domain, 0, "Timeout");
return false;
}
/* check for error */ /* check for error */
if (error.IsDefined()) { if (error.IsDefined()) {

View File

@ -35,6 +35,8 @@ class NfsConnection;
* thread, and method Run() waits for completion. * thread, and method Run() waits for completion.
*/ */
class BlockingNfsOperation : protected NfsCallback, NfsLease { class BlockingNfsOperation : protected NfsCallback, NfsLease {
static constexpr unsigned timeout_ms = 60000;
Mutex mutex; Mutex mutex;
Cond cond; Cond cond;
@ -52,10 +54,13 @@ public:
bool Run(Error &error); bool Run(Error &error);
private: private:
void LockWaitFinished() { bool LockWaitFinished() {
const ScopeLock protect(mutex); const ScopeLock protect(mutex);
while (!finished) while (!finished)
cond.wait(mutex); if (!cond.timed_wait(mutex, timeout_ms))
return false;
return true;
} }
/** /**

View File

@ -157,6 +157,12 @@ public:
return *i; return *i;
} }
template<typename F>
void ForEach(F &&f) {
for (CT &i : list)
f(i);
}
}; };
#endif #endif

View File

@ -34,11 +34,15 @@ extern "C" {
#include <poll.h> /* for POLLIN, POLLOUT */ #include <poll.h> /* for POLLIN, POLLOUT */
static constexpr unsigned NFS_MOUNT_TIMEOUT = 60;
inline bool inline bool
NfsConnection::CancellableCallback::Stat(nfs_context *ctx, NfsConnection::CancellableCallback::Stat(nfs_context *ctx,
const char *path, const char *path,
Error &error) Error &error)
{ {
assert(connection.GetEventLoop().IsInside());
int result = nfs_stat_async(ctx, path, Callback, this); int result = nfs_stat_async(ctx, path, Callback, this);
if (result < 0) { if (result < 0) {
error.Format(nfs_domain, "nfs_stat_async() failed: %s", error.Format(nfs_domain, "nfs_stat_async() failed: %s",
@ -54,6 +58,8 @@ NfsConnection::CancellableCallback::OpenDirectory(nfs_context *ctx,
const char *path, const char *path,
Error &error) Error &error)
{ {
assert(connection.GetEventLoop().IsInside());
int result = nfs_opendir_async(ctx, path, Callback, this); int result = nfs_opendir_async(ctx, path, Callback, this);
if (result < 0) { if (result < 0) {
error.Format(nfs_domain, "nfs_opendir_async() failed: %s", error.Format(nfs_domain, "nfs_opendir_async() failed: %s",
@ -69,6 +75,8 @@ NfsConnection::CancellableCallback::Open(nfs_context *ctx,
const char *path, int flags, const char *path, int flags,
Error &error) Error &error)
{ {
assert(connection.GetEventLoop().IsInside());
int result = nfs_open_async(ctx, path, flags, int result = nfs_open_async(ctx, path, flags,
Callback, this); Callback, this);
if (result < 0) { if (result < 0) {
@ -85,6 +93,8 @@ NfsConnection::CancellableCallback::Stat(nfs_context *ctx,
struct nfsfh *fh, struct nfsfh *fh,
Error &error) Error &error)
{ {
assert(connection.GetEventLoop().IsInside());
int result = nfs_fstat_async(ctx, fh, Callback, this); int result = nfs_fstat_async(ctx, fh, Callback, this);
if (result < 0) { if (result < 0) {
error.Format(nfs_domain, "nfs_fstat_async() failed: %s", error.Format(nfs_domain, "nfs_fstat_async() failed: %s",
@ -100,6 +110,8 @@ NfsConnection::CancellableCallback::Read(nfs_context *ctx, struct nfsfh *fh,
uint64_t offset, size_t size, uint64_t offset, size_t size,
Error &error) Error &error)
{ {
assert(connection.GetEventLoop().IsInside());
int result = nfs_pread_async(ctx, fh, offset, size, Callback, this); int result = nfs_pread_async(ctx, fh, offset, size, Callback, this);
if (result < 0) { if (result < 0) {
error.Format(nfs_domain, "nfs_pread_async() failed: %s", error.Format(nfs_domain, "nfs_pread_async() failed: %s",
@ -113,6 +125,7 @@ NfsConnection::CancellableCallback::Read(nfs_context *ctx, struct nfsfh *fh,
inline void inline void
NfsConnection::CancellableCallback::CancelAndScheduleClose(struct nfsfh *fh) NfsConnection::CancellableCallback::CancelAndScheduleClose(struct nfsfh *fh)
{ {
assert(connection.GetEventLoop().IsInside());
assert(!open); assert(!open);
assert(close_fh == nullptr); assert(close_fh == nullptr);
assert(fh != nullptr); assert(fh != nullptr);
@ -121,9 +134,22 @@ NfsConnection::CancellableCallback::CancelAndScheduleClose(struct nfsfh *fh)
Cancel(); Cancel();
} }
inline void
NfsConnection::CancellableCallback::PrepareDestroyContext()
{
assert(IsCancelled());
if (close_fh != nullptr) {
connection.InternalClose(close_fh);
close_fh = nullptr;
}
}
inline void inline void
NfsConnection::CancellableCallback::Callback(int err, void *data) NfsConnection::CancellableCallback::Callback(int err, void *data)
{ {
assert(connection.GetEventLoop().IsInside());
if (!IsCancelled()) { if (!IsCancelled()) {
assert(close_fh == nullptr); assert(close_fh == nullptr);
@ -143,8 +169,10 @@ NfsConnection::CancellableCallback::Callback(int err, void *data)
allocated file handle immediately */ allocated file handle immediately */
assert(close_fh == nullptr); assert(close_fh == nullptr);
struct nfsfh *fh = (struct nfsfh *)data; if (err >= 0) {
connection.Close(fh); struct nfsfh *fh = (struct nfsfh *)data;
connection.Close(fh);
}
} else if (close_fh != nullptr) } else if (close_fh != nullptr)
connection.DeferClose(close_fh); connection.DeferClose(close_fh);
@ -209,6 +237,7 @@ NfsConnection::RemoveLease(NfsLease &lease)
bool bool
NfsConnection::Stat(const char *path, NfsCallback &callback, Error &error) NfsConnection::Stat(const char *path, NfsCallback &callback, Error &error)
{ {
assert(GetEventLoop().IsInside());
assert(!callbacks.Contains(callback)); assert(!callbacks.Contains(callback));
auto &c = callbacks.Add(callback, *this, false); auto &c = callbacks.Add(callback, *this, false);
@ -225,6 +254,7 @@ bool
NfsConnection::OpenDirectory(const char *path, NfsCallback &callback, NfsConnection::OpenDirectory(const char *path, NfsCallback &callback,
Error &error) Error &error)
{ {
assert(GetEventLoop().IsInside());
assert(!callbacks.Contains(callback)); assert(!callbacks.Contains(callback));
auto &c = callbacks.Add(callback, *this, true); auto &c = callbacks.Add(callback, *this, true);
@ -240,12 +270,16 @@ NfsConnection::OpenDirectory(const char *path, NfsCallback &callback,
const struct nfsdirent * const struct nfsdirent *
NfsConnection::ReadDirectory(struct nfsdir *dir) NfsConnection::ReadDirectory(struct nfsdir *dir)
{ {
assert(GetEventLoop().IsInside());
return nfs_readdir(context, dir); return nfs_readdir(context, dir);
} }
void void
NfsConnection::CloseDirectory(struct nfsdir *dir) NfsConnection::CloseDirectory(struct nfsdir *dir)
{ {
assert(GetEventLoop().IsInside());
return nfs_closedir(context, dir); return nfs_closedir(context, dir);
} }
@ -253,6 +287,7 @@ bool
NfsConnection::Open(const char *path, int flags, NfsCallback &callback, NfsConnection::Open(const char *path, int flags, NfsCallback &callback,
Error &error) Error &error)
{ {
assert(GetEventLoop().IsInside());
assert(!callbacks.Contains(callback)); assert(!callbacks.Contains(callback));
auto &c = callbacks.Add(callback, *this, true); auto &c = callbacks.Add(callback, *this, true);
@ -268,6 +303,7 @@ NfsConnection::Open(const char *path, int flags, NfsCallback &callback,
bool bool
NfsConnection::Stat(struct nfsfh *fh, NfsCallback &callback, Error &error) NfsConnection::Stat(struct nfsfh *fh, NfsCallback &callback, Error &error)
{ {
assert(GetEventLoop().IsInside());
assert(!callbacks.Contains(callback)); assert(!callbacks.Contains(callback));
auto &c = callbacks.Add(callback, *this, false); auto &c = callbacks.Add(callback, *this, false);
@ -284,6 +320,7 @@ bool
NfsConnection::Read(struct nfsfh *fh, uint64_t offset, size_t size, NfsConnection::Read(struct nfsfh *fh, uint64_t offset, size_t size,
NfsCallback &callback, Error &error) NfsCallback &callback, Error &error)
{ {
assert(GetEventLoop().IsInside());
assert(!callbacks.Contains(callback)); assert(!callbacks.Contains(callback));
auto &c = callbacks.Add(callback, *this, false); auto &c = callbacks.Add(callback, *this, false);
@ -307,10 +344,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 void
NfsConnection::Close(struct nfsfh *fh) NfsConnection::Close(struct nfsfh *fh)
{ {
nfs_close_async(context, fh, DummyCallback, nullptr); assert(GetEventLoop().IsInside());
InternalClose(fh);
ScheduleSocket(); ScheduleSocket();
} }
@ -327,12 +376,26 @@ NfsConnection::DestroyContext()
assert(GetEventLoop().IsInside()); assert(GetEventLoop().IsInside());
assert(context != nullptr); assert(context != nullptr);
#ifndef NDEBUG
assert(!in_destroy);
in_destroy = true;
#endif
if (!mount_finished) {
assert(TimeoutMonitor::IsActive());
TimeoutMonitor::Cancel();
}
/* cancel pending DeferredMonitor that was scheduled to notify /* cancel pending DeferredMonitor that was scheduled to notify
new leases */ new leases */
DeferredMonitor::Cancel(); DeferredMonitor::Cancel();
if (SocketMonitor::IsDefined()) if (SocketMonitor::IsDefined())
SocketMonitor::Cancel(); SocketMonitor::Steal();
callbacks.ForEach([](CancellableCallback &c){
c.PrepareDestroyContext();
});
nfs_destroy_context(context); nfs_destroy_context(context);
context = nullptr; context = nullptr;
@ -341,8 +404,11 @@ NfsConnection::DestroyContext()
inline void inline void
NfsConnection::DeferClose(struct nfsfh *fh) NfsConnection::DeferClose(struct nfsfh *fh)
{ {
assert(GetEventLoop().IsInside());
assert(in_event); assert(in_event);
assert(in_service); assert(in_service);
assert(context != nullptr);
assert(fh != nullptr);
deferred_close.push_front(fh); deferred_close.push_front(fh);
} }
@ -350,6 +416,7 @@ NfsConnection::DeferClose(struct nfsfh *fh)
void void
NfsConnection::ScheduleSocket() NfsConnection::ScheduleSocket()
{ {
assert(GetEventLoop().IsInside());
assert(context != nullptr); assert(context != nullptr);
if (!SocketMonitor::IsDefined()) { if (!SocketMonitor::IsDefined()) {
@ -364,9 +431,35 @@ NfsConnection::ScheduleSocket()
SocketMonitor::Schedule(libnfs_to_events(nfs_which_events(context))); SocketMonitor::Schedule(libnfs_to_events(nfs_which_events(context)));
} }
inline int
NfsConnection::Service(unsigned flags)
{
assert(GetEventLoop().IsInside());
assert(context != nullptr);
#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
return result;
}
bool bool
NfsConnection::OnSocketReady(unsigned flags) NfsConnection::OnSocketReady(unsigned flags)
{ {
assert(GetEventLoop().IsInside());
assert(deferred_close.empty()); assert(deferred_close.empty());
bool closed = false; bool closed = false;
@ -378,21 +471,10 @@ NfsConnection::OnSocketReady(unsigned flags)
re-register it each time */ re-register it each time */
SocketMonitor::Steal(); SocketMonitor::Steal();
assert(!in_event); const int result = Service(flags);
in_event = true;
assert(!in_service);
in_service = true;
int result = nfs_service(context, events_to_libnfs(flags));
assert(context != nullptr);
assert(in_service);
in_service = false;
while (!deferred_close.empty()) { while (!deferred_close.empty()) {
nfs_close_async(context, deferred_close.front(), InternalClose(deferred_close.front());
DummyCallback, nullptr);
deferred_close.pop_front(); deferred_close.pop_front();
} }
@ -413,9 +495,9 @@ NfsConnection::OnSocketReady(unsigned flags)
DestroyContext(); DestroyContext();
closed = true; 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 /* 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 */ disabled - nfs_service() returns 0 */
Error error; Error error;
const char *msg = nfs_get_error(context); const char *msg = nfs_get_error(context);
@ -431,8 +513,12 @@ NfsConnection::OnSocketReady(unsigned flags)
closed = true; closed = true;
} }
assert(context == nullptr || nfs_get_fd(context) >= 0);
#ifndef NDEBUG
assert(in_event); assert(in_event);
in_event = false; in_event = false;
#endif
if (context != nullptr) if (context != nullptr)
ScheduleSocket(); ScheduleSocket();
@ -444,10 +530,14 @@ inline void
NfsConnection::MountCallback(int status, gcc_unused nfs_context *nfs, NfsConnection::MountCallback(int status, gcc_unused nfs_context *nfs,
gcc_unused void *data) gcc_unused void *data)
{ {
assert(GetEventLoop().IsInside());
assert(context == nfs); assert(context == nfs);
mount_finished = true; mount_finished = true;
assert(TimeoutMonitor::IsActive() || in_destroy);
TimeoutMonitor::Cancel();
if (status < 0) { if (status < 0) {
postponed_mount_error.Format(nfs_domain, status, postponed_mount_error.Format(nfs_domain, status,
"nfs_mount_async() failed: %s", "nfs_mount_async() failed: %s",
@ -468,6 +558,7 @@ NfsConnection::MountCallback(int status, nfs_context *nfs, void *data,
inline bool inline bool
NfsConnection::MountInternal(Error &error) NfsConnection::MountInternal(Error &error)
{ {
assert(GetEventLoop().IsInside());
assert(context == nullptr); assert(context == nullptr);
context = nfs_init_context(); context = nfs_init_context();
@ -478,8 +569,14 @@ NfsConnection::MountInternal(Error &error)
postponed_mount_error.Clear(); postponed_mount_error.Clear();
mount_finished = false; mount_finished = false;
TimeoutMonitor::ScheduleSeconds(NFS_MOUNT_TIMEOUT);
#ifndef NDEBUG
in_service = false; in_service = false;
in_event = false; in_event = false;
in_destroy = false;
#endif
if (nfs_mount_async(context, server.c_str(), export_name.c_str(), if (nfs_mount_async(context, server.c_str(), export_name.c_str(),
MountCallback, this) != 0) { MountCallback, this) != 0) {
@ -535,9 +632,23 @@ NfsConnection::BroadcastError(Error &&error)
BroadcastMountError(std::move(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 void
NfsConnection::RunDeferred() NfsConnection::RunDeferred()
{ {
assert(GetEventLoop().IsInside());
if (context == nullptr) { if (context == nullptr) {
Error error; Error error;
if (!MountInternal(error)) { if (!MountInternal(error)) {

View File

@ -23,6 +23,7 @@
#include "Lease.hxx" #include "Lease.hxx"
#include "Cancellable.hxx" #include "Cancellable.hxx"
#include "event/SocketMonitor.hxx" #include "event/SocketMonitor.hxx"
#include "event/TimeoutMonitor.hxx"
#include "event/DeferredMonitor.hxx" #include "event/DeferredMonitor.hxx"
#include "util/Error.hxx" #include "util/Error.hxx"
@ -40,7 +41,7 @@ class NfsCallback;
/** /**
* An asynchronous connection to a NFS server. * An asynchronous connection to a NFS server.
*/ */
class NfsConnection : SocketMonitor, DeferredMonitor { class NfsConnection : SocketMonitor, TimeoutMonitor, DeferredMonitor {
class CancellableCallback : public CancellablePointer<NfsCallback> { class CancellableCallback : public CancellablePointer<NfsCallback> {
NfsConnection &connection; NfsConnection &connection;
@ -84,6 +85,13 @@ class NfsConnection : SocketMonitor, DeferredMonitor {
*/ */
void CancelAndScheduleClose(struct nfsfh *fh); 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: private:
static void Callback(int err, struct nfs_context *nfs, static void Callback(int err, struct nfs_context *nfs,
void *data, void *private_data); void *data, void *private_data);
@ -111,6 +119,7 @@ class NfsConnection : SocketMonitor, DeferredMonitor {
Error postponed_mount_error; Error postponed_mount_error;
#ifndef NDEBUG
/** /**
* True when nfs_service() is being called. * True when nfs_service() is being called.
*/ */
@ -122,13 +131,20 @@ class NfsConnection : SocketMonitor, DeferredMonitor {
*/ */
bool in_event; bool in_event;
/**
* True when DestroyContext() is being called.
*/
bool in_destroy;
#endif
bool mount_finished; bool mount_finished;
public: public:
gcc_nonnull_all gcc_nonnull_all
NfsConnection(EventLoop &_loop, NfsConnection(EventLoop &_loop,
const char *_server, const char *_export_name) const char *_server, const char *_export_name)
:SocketMonitor(_loop), DeferredMonitor(_loop), :SocketMonitor(_loop), TimeoutMonitor(_loop),
DeferredMonitor(_loop),
server(_server), export_name(_export_name), server(_server), export_name(_export_name),
context(nullptr) {} context(nullptr) {}
@ -184,6 +200,11 @@ protected:
private: private:
void DestroyContext(); void DestroyContext();
/**
* Wrapper for nfs_close_async().
*/
void InternalClose(struct nfsfh *fh);
/** /**
* Invoke nfs_close_async() after nfs_service() returns. * Invoke nfs_close_async() after nfs_service() returns.
*/ */
@ -200,9 +221,17 @@ private:
void ScheduleSocket(); void ScheduleSocket();
/**
* Wrapper for nfs_service().
*/
int Service(unsigned flags);
/* virtual methods from SocketMonitor */ /* virtual methods from SocketMonitor */
virtual bool OnSocketReady(unsigned flags) override; virtual bool OnSocketReady(unsigned flags) override;
/* virtual methods from TimeoutMonitor */
void OnTimeout() final;
/* virtual methods from DeferredMonitor */ /* virtual methods from DeferredMonitor */
virtual void RunDeferred() override; virtual void RunDeferred() override;
}; };

View File

@ -146,6 +146,7 @@ private:
const ScopeLock protect(mutex); const ScopeLock protect(mutex);
state = _state; state = _state;
last_error.Clear();
last_error.Set(error); last_error.Set(error);
cond.broadcast(); cond.broadcast();
} }