Merge tag 'v0.21.18'

release v0.21.18
This commit is contained in:
Max Kellermann
2019-12-24 16:17:10 +01:00
20 changed files with 375 additions and 77 deletions

View File

@@ -137,7 +137,8 @@ static constexpr int
ExportTimeoutMS(std::chrono::steady_clock::duration timeout)
{
return timeout >= timeout.zero()
? int(std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count())
/* round up (+1) to avoid unnecessary wakeups */
? int(std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count()) + 1
: -1;
}
@@ -220,7 +221,6 @@ EventLoop::Run() noexcept
} while (!quit);
#ifndef NDEBUG
assert(busy);
assert(thread.IsInside());
#endif
}

View File

@@ -22,6 +22,10 @@
#include <algorithm>
#ifdef USE_EPOLL
#include <errno.h>
#endif
#ifndef _WIN32
#include <poll.h>
#endif
@@ -37,17 +41,42 @@ MultiSocketMonitor::Reset() noexcept
assert(GetEventLoop().IsInside());
fds.clear();
#ifdef USE_EPOLL
always_ready_fds.clear();
#endif
IdleMonitor::Cancel();
timeout_event.Cancel();
ready = refresh = false;
}
bool
MultiSocketMonitor::AddSocket(SocketDescriptor fd, unsigned events) noexcept
{
fds.emplace_front(*this, fd);
bool success = fds.front().Schedule(events);
if (!success) {
fds.pop_front();
#ifdef USE_EPOLL
if (errno == EPERM)
/* not supported by epoll (e.g. "/dev/null"):
add it to the "always ready" list */
always_ready_fds.push_front({fd, events});
#endif
}
return success;
}
void
MultiSocketMonitor::ClearSocketList() noexcept
{
assert(GetEventLoop().IsInside());
fds.clear();
#ifdef USE_EPOLL
always_ready_fds.clear();
#endif
}
#ifndef _WIN32
@@ -55,6 +84,10 @@ MultiSocketMonitor::ClearSocketList() noexcept
void
MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
{
#ifdef USE_EPOLL
always_ready_fds.clear();
#endif
pollfd *const end = pfds + n;
UpdateSocketList([pfds, end](SocketDescriptor fd) -> unsigned {
@@ -64,9 +97,7 @@ MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
if (i == end)
return 0;
auto events = i->events;
i->events = 0;
return events;
return std::exchange(i->events, 0);
});
for (auto i = pfds; i != end; ++i)
@@ -79,7 +110,20 @@ MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
void
MultiSocketMonitor::Prepare() noexcept
{
const auto timeout = PrepareSockets();
auto timeout = PrepareSockets();
#ifdef USE_EPOLL
if (!always_ready_fds.empty()) {
/* if there was at least one file descriptor not
supported by epoll, install a very short timeout
because we assume it's always ready */
constexpr std::chrono::steady_clock::duration ready_timeout =
std::chrono::milliseconds(1);
if (timeout < timeout.zero() || timeout > ready_timeout)
timeout = ready_timeout;
}
#endif
if (timeout >= timeout.zero())
timeout_event.Schedule(timeout);
else

View File

@@ -49,12 +49,10 @@ class MultiSocketMonitor : IdleMonitor
unsigned revents;
public:
SingleFD(MultiSocketMonitor &_multi, SocketDescriptor _fd,
unsigned events) noexcept
SingleFD(MultiSocketMonitor &_multi,
SocketDescriptor _fd) noexcept
:SocketMonitor(_fd, _multi.GetEventLoop()),
multi(_multi), revents(0) {
Schedule(events);
}
multi(_multi), revents(0) {}
SocketDescriptor GetSocket() const noexcept {
return SocketMonitor::GetSocket();
@@ -85,8 +83,6 @@ class MultiSocketMonitor : IdleMonitor
}
};
friend class SingleFD;
TimerEvent timeout_event;
/**
@@ -105,6 +101,21 @@ class MultiSocketMonitor : IdleMonitor
std::forward_list<SingleFD> fds;
#ifdef USE_EPOLL
struct AlwaysReady {
const SocketDescriptor fd;
const unsigned revents;
};
/**
* A list of file descriptors which are always ready. This is
* a kludge needed because the ALSA output plugin gives us a
* file descriptor to /dev/null, which is incompatible with
* epoll (epoll_ctl() returns -EPERM).
*/
std::forward_list<AlwaysReady> always_ready_fds;
#endif
public:
static constexpr unsigned READ = SocketMonitor::READ;
static constexpr unsigned WRITE = SocketMonitor::WRITE;
@@ -146,9 +157,7 @@ public:
*
* May only be called from PrepareSockets().
*/
void AddSocket(SocketDescriptor fd, unsigned events) noexcept {
fds.emplace_front(*this, fd, events);
}
bool AddSocket(SocketDescriptor fd, unsigned events) noexcept;
/**
* Remove all sockets.
@@ -203,6 +212,11 @@ public:
i.ClearReturnedEvents();
}
}
#ifdef USE_EPOLL
for (const auto &i : always_ready_fds)
f(i.fd, i.revents);
#endif
}
protected:
@@ -231,7 +245,6 @@ private:
void OnTimeout() noexcept {
SetReady();
IdleMonitor::Schedule();
}
virtual void OnIdle() noexcept final;

View File

@@ -64,20 +64,24 @@ SocketMonitor::Close() noexcept
Steal().Close();
}
void
bool
SocketMonitor::Schedule(unsigned flags) noexcept
{
assert(IsDefined());
if (flags == GetScheduledFlags())
return;
return true;
bool success;
if (scheduled_flags == 0)
loop.AddFD(fd.Get(), flags, *this);
success = loop.AddFD(fd.Get(), flags, *this);
else if (flags == 0)
loop.RemoveFD(fd.Get(), *this);
success = loop.RemoveFD(fd.Get(), *this);
else
loop.ModifyFD(fd.Get(), flags, *this);
success = loop.ModifyFD(fd.Get(), flags, *this);
scheduled_flags = flags;
if (success)
scheduled_flags = flags;
return success;
}

View File

@@ -98,18 +98,22 @@ public:
return scheduled_flags;
}
void Schedule(unsigned flags) noexcept;
/**
* @return true on success, false on error (with errno set if
* USE_EPOLL is defined)
*/
bool Schedule(unsigned flags) noexcept;
void Cancel() noexcept {
Schedule(0);
}
void ScheduleRead() noexcept {
Schedule(GetScheduledFlags() | READ | HANGUP | ERROR);
bool ScheduleRead() noexcept {
return Schedule(GetScheduledFlags() | READ | HANGUP | ERROR);
}
void ScheduleWrite() noexcept {
Schedule(GetScheduledFlags() | WRITE);
bool ScheduleWrite() noexcept {
return Schedule(GetScheduledFlags() | WRITE);
}
void CancelRead() noexcept {

View File

@@ -180,7 +180,6 @@ CurlInputStream::FreeEasyIndirect() noexcept
{
BlockingCall(GetEventLoop(), [this](){
FreeEasy();
(*curl_init)->InvalidateSockets();
});
}

View File

@@ -162,7 +162,6 @@ CurlGlobal::Remove(CurlRequest &r) noexcept
assert(GetEventLoop().IsInside());
curl_multi_remove_handle(multi.Get(), r.Get());
InvalidateSockets();
}
/**
@@ -220,12 +219,12 @@ CurlGlobal::UpdateTimeout(long timeout_ms) noexcept
return;
}
if (timeout_ms < 10)
/* CURL 7.21.1 likes to report "timeout=0", which
if (timeout_ms < 1)
/* CURL's threaded resolver sets a timeout of 0ms, which
means we're running in a busy loop. Quite a bad
idea to waste so much CPU. Let's use a lower limit
of 10ms. */
timeout_ms = 10;
of 1ms. */
timeout_ms = 1;
timeout_event.Schedule(std::chrono::milliseconds(timeout_ms));
}

View File

@@ -67,16 +67,6 @@ public:
SocketAction(CURL_SOCKET_TIMEOUT, 0);
}
/**
* This is a kludge to allow pausing/resuming a stream with
* libcurl < 7.32.0. Read the curl_easy_pause manpage for
* more information.
*/
void ResumeSockets() {
int running_handles;
curl_multi_socket_all(multi.Get(), &running_handles);
}
private:
/**
* Check for finished HTTP responses.

View File

@@ -30,7 +30,6 @@
#include "config.h"
#include "Request.hxx"
#include "Global.hxx"
#include "Version.hxx"
#include "Handler.hxx"
#include "event/Call.hxx"
#include "util/RuntimeError.hxx"
@@ -122,12 +121,6 @@ CurlRequest::Resume() noexcept
easy.Unpause();
if (IsCurlOlderThan(0x072000))
/* libcurl older than 7.32.0 does not update
its sockets after curl_easy_pause(); force
libcurl to do it now */
global.ResumeSockets();
global.InvalidateSockets();
}

View File

@@ -46,6 +46,7 @@
#include "CrossFade.hxx"
#include "tag/Tag.hxx"
#include "Idle.hxx"
#include "util/Compiler.h"
#include "util/Domain.hxx"
#include "thread/Name.hxx"
#include "Log.hxx"
@@ -1175,6 +1176,7 @@ try {
}
/* fall through */
gcc_fallthrough;
case PlayerCommand::PAUSE:
next_song.reset();

View File

@@ -105,7 +105,9 @@ public:
BIND_THIS_METHOD(OnDeferredStart)),
request(curl, uri, *this) {
// TODO: use CurlInputStream's configuration
}
void DeferStart() noexcept {
/* start the transfer inside the IOThread */
defer_start.Schedule();
}
@@ -278,6 +280,7 @@ public:
}
using BlockingHttpRequest::GetEasy;
using BlockingHttpRequest::DeferStart;
using BlockingHttpRequest::Wait;
protected:
@@ -425,6 +428,7 @@ public:
}
const StorageFileInfo &Perform() {
DeferStart();
Wait();
return info;
}
@@ -476,6 +480,7 @@ public:
base_path(UriPathOrSlash(uri)) {}
std::unique_ptr<StorageDirectoryReader> Perform() {
DeferStart();
Wait();
return ToReader();
}

View File

@@ -58,6 +58,8 @@ FormatISO8601(std::chrono::system_clock::time_point tp)
return FormatISO8601(GmTime(tp));
}
#ifndef _WIN32
static std::pair<unsigned, unsigned>
ParseTimeZoneOffsetRaw(const char *&s)
{
@@ -108,6 +110,67 @@ ParseTimeZoneOffset(const char *&s)
return d;
}
static const char *
ParseTimeOfDay(const char *s, struct tm &tm,
std::chrono::system_clock::duration &precision) noexcept
{
/* this function always checks "end==s" to work around a
strptime() bug on OS X: if nothing could be parsed,
strptime() returns the input string (indicating success)
instead of nullptr (indicating error) */
const char *end = strptime(s, "%H", &tm);
if (end == nullptr || end == s)
return end;
s = end;
precision = std::chrono::hours(1);
if (*s == ':') {
/* with field separators: now a minute must follow */
++s;
end = strptime(s, "%M", &tm);
if (end == nullptr || end == s)
return nullptr;
s = end;
precision = std::chrono::minutes(1);
/* the "seconds" field is optional */
if (*s != ':')
return s;
++s;
end = strptime(s, "%S", &tm);
if (end == nullptr || end == s)
return nullptr;
precision = std::chrono::seconds(1);
return end;
}
/* without field separators */
end = strptime(s, "%M", &tm);
if (end == nullptr || end == s)
return s;
s = end;
precision = std::chrono::minutes(1);
end = strptime(s, "%S", &tm);
if (end == nullptr || end == s)
return s;
precision = std::chrono::seconds(1);
return end;
}
#endif
std::pair<std::chrono::system_clock::time_point,
std::chrono::system_clock::duration>
ParseISO8601(const char *s)
@@ -138,22 +201,9 @@ ParseISO8601(const char *s)
if (*s == 'T') {
++s;
if ((end = strptime(s, "%T", &tm)) != nullptr)
precision = std::chrono::seconds(1);
else if ((end = strptime(s, "%H%M%S", &tm)) != nullptr)
/* no field separators */
precision = std::chrono::seconds(1);
else if ((end = strptime(s, "%H%M", &tm)) != nullptr)
/* no field separators */
precision = std::chrono::minutes(1);
else if ((end = strptime(s, "%H:%M", &tm)) != nullptr)
precision = std::chrono::minutes(1);
else if ((end = strptime(s, "%H", &tm)) != nullptr)
precision = std::chrono::hours(1);
else
s = ParseTimeOfDay(s, tm, precision);
if (s == nullptr)
throw std::runtime_error("Failed to parse time of day");
s = end;
}
auto tp = TimeGm(tm);

View File

@@ -143,6 +143,14 @@
#define gcc_flatten
#endif
#if GCC_CHECK_VERSION(7,0)
#define gcc_fallthrough __attribute__((fallthrough))
#elif CLANG_CHECK_VERSION(10,0)
#define gcc_fallthrough [[fallthrough]]
#else
#define gcc_fallthrough
#endif
#ifndef __cplusplus
/* plain C99 has "restrict" */
#define gcc_restrict restrict

View File

@@ -19,6 +19,7 @@
*/
#include "format.h"
#include "util/Compiler.h"
#include <stdbool.h>
#include <stdio.h>
@@ -238,6 +239,7 @@ format_object2(const char *format, const char **last, const void *object,
}
/* fall through */
gcc_fallthrough;
default:
/* pass-through non-escaped portions of the format string */