Commit 803a48e9 authored by Max Kellermann's avatar Max Kellermann

Merge tag 'v0.21.18'

release v0.21.18
parents 57b8e7f6 bf41d1ad
......@@ -2,6 +2,28 @@ language: cpp
matrix:
include:
# Ubuntu Bionic (18.04) with GCC 7
- os: linux
dist: bionic
addons:
apt:
sources:
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
packages:
- libgtest-dev
- libboost-dev
- python3.6
- python3-urllib3
- ninja-build
before_install:
- wget https://bootstrap.pypa.io/get-pip.py
- /usr/bin/python3.6 get-pip.py --user
install:
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
env:
- MATRIX_EVAL="export PATH=\$HOME/.local/bin:\$PATH"
# Ubuntu Trusty (16.04) with GCC 6
- os: linux
dist: trusty
addons:
......@@ -25,8 +47,9 @@ matrix:
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
env:
# use gold as workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=17068
- MATRIX_EVAL="export CC=gcc-6 CXX=g++-6 LDFLAGS=-fuse-ld=gold PATH=$HOME/.local/bin:$PATH"
- MATRIX_EVAL="export CC='ccache gcc-6' CXX='ccache g++-6' LDFLAGS=-fuse-ld=gold PATH=\$HOME/.local/bin:\$PATH"
# Ubuntu Trusty (16.04) with GCC 8
- os: linux
dist: trusty
addons:
......@@ -50,25 +73,37 @@ matrix:
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
env:
# use gold as workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=17068
- MATRIX_EVAL="export CC=gcc-8 CXX=g++-8 LDFLAGS=-fuse-ld=gold PATH=$HOME/.local/bin:$PATH"
- MATRIX_EVAL="export CC='ccache gcc-8' CXX='ccache g++-8' LDFLAGS=-fuse-ld=gold PATH=\$HOME/.local/bin:\$PATH"
- os: osx
osx_image: xcode9.3beta
osx_image: xcode9.4
addons:
homebrew:
packages:
- ccache
- meson
env:
- MATRIX_EVAL=""
- MATRIX_EVAL="export PATH=/usr/local/opt/ccache/libexec:$PATH HOMEBREW_NO_ANALYTICS=1"
cache:
- apt
- ccache
apt: true
ccache: true
directories:
- $HOME/Library/Caches/Homebrew
before_cache:
- test "$TRAVIS_OS_NAME" != "osx" || brew cleanup
before_install:
- eval "${MATRIX_EVAL}"
# C++14
- test "$TRAVIS_OS_NAME" != "osx" || brew update
install:
# C++14
- test "$TRAVIS_OS_NAME" != "osx" || brew install ccache meson
# Work around "Target /usr/local/lib/libgtest.a is a symlink
# belonging to nss. You can unlink it" during gtest install
- test "$TRAVIS_OS_NAME" != "osx" || brew unlink nss
- test "$TRAVIS_OS_NAME" != "osx" || brew install --HEAD https://gist.githubusercontent.com/Kronuz/96ac10fbd8472eb1e7566d740c4034f8/raw/gtest.rb
before_script:
......
......@@ -30,6 +30,16 @@ ver 0.22 (not yet released)
* switch to C++17
- GCC 7 or clang 4 (or newer) recommended
ver 0.21.18 (2019/12/24)
* protocol
- work around Mac OS X bug in the ISO 8601 parser
* output
- alsa: fix hang bug with ALSA "null" outputs
* storage
- curl: fix crash bug
* drop support for CURL versions older than 7.32.0
* reduce unnecessary CPU wakeups
ver 0.21.17 (2019/12/16)
* protocol
- relax the ISO 8601 parser: allow omitting field separators, the
......
......@@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd"
android:installLocation="auto"
android:versionCode="40"
android:versionName="0.21.17">
android:versionCode="41"
android:versionName="0.21.18">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>
......
......@@ -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
}
......
......@@ -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
......
......@@ -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;
......
......@@ -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);
if (success)
scheduled_flags = flags;
scheduled_flags = flags;
return success;
}
......@@ -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 {
......
......@@ -180,7 +180,6 @@ CurlInputStream::FreeEasyIndirect() noexcept
{
BlockingCall(GetEventLoop(), [this](){
FreeEasy();
(*curl_init)->InvalidateSockets();
});
}
......
......@@ -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));
}
......
......@@ -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.
......
......@@ -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();
}
......
......@@ -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();
......
......@@ -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();
}
......
......@@ -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);
......
......@@ -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
......
......@@ -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 */
......
/*
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "ShutdownHandler.hxx"
#include "lib/curl/Global.hxx"
#include "lib/curl/Request.hxx"
#include "lib/curl/Handler.hxx"
#include "event/Loop.hxx"
#include "util/PrintException.hxx"
#include <stdio.h>
class MyHandler final : public CurlResponseHandler {
EventLoop &event_loop;
std::exception_ptr error;
public:
explicit MyHandler(EventLoop &_event_loop) noexcept
:event_loop(_event_loop) {}
void Finish() {
if (error)
std::rethrow_exception(error);
}
/* virtual methods from CurlResponseHandler */
void OnHeaders(unsigned status,
std::multimap<std::string, std::string> &&headers) override {
fprintf(stderr, "status: %u\n", status);
for (const auto &i : headers)
fprintf(stderr, "%s: %s\n",
i.first.c_str(), i.second.c_str());
}
void OnData(ConstBuffer<void> data) override {
if (fwrite(data.data, data.size, 1, stdout) != 1)
throw std::runtime_error("Failed to write");
}
void OnEnd() override {
event_loop.Break();
}
void OnError(std::exception_ptr e) noexcept override {
error = std::move(e);
event_loop.Break();
}
};
int
main(int argc, char **argv) noexcept
try {
if (argc != 2) {
fprintf(stderr, "Usage: RunCurl URI\n");
return EXIT_FAILURE;
}
const char *const uri = argv[1];
EventLoop event_loop;
const ShutdownHandler shutdown_handler(event_loop);
CurlGlobal curl_global(event_loop);
MyHandler handler(event_loop);
CurlRequest request(curl_global, uri, handler);
request.Start();
event_loop.Run();
handler.Finish();
return EXIT_SUCCESS;
} catch (...) {
PrintException(std::current_exception());
return EXIT_FAILURE;
}
......@@ -342,6 +342,18 @@ executable(
)
if curl_dep.found()
executable(
'RunCurl',
'RunCurl.cxx',
'ShutdownHandler.cxx',
'../src/Log.cxx',
'../src/LogBackend.cxx',
include_directories: inc,
dependencies: [
curl_dep,
],
)
test('test_icy_parser', executable(
'test_icy_parser',
'test_icy_parser.cxx',
......
......@@ -90,6 +90,29 @@ Ls(Storage &storage, const char *path)
return EXIT_SUCCESS;
}
static int
Stat(Storage &storage, const char *path)
{
const auto info = storage.GetInfo(path, false);
switch (info.type) {
case StorageFileInfo::Type::OTHER:
printf("other\n");
break;
case StorageFileInfo::Type::REGULAR:
printf("regular\n");
break;
case StorageFileInfo::Type::DIRECTORY:
printf("directory\n");
break;
}
printf("size: %llu\n", (unsigned long long)info.size);
return EXIT_SUCCESS;
}
int
main(int argc, char **argv)
try {
......@@ -117,6 +140,18 @@ try {
storage_uri);
return Ls(*storage, path);
} else if (strcmp(command, "stat") == 0) {
if (argc != 4) {
fprintf(stderr, "Usage: run_storage stat URI PATH\n");
return EXIT_FAILURE;
}
const char *const path = argv[3];
auto storage = MakeStorage(io_thread.GetEventLoop(),
storage_uri);
return Stat(*storage, path);
} else {
fprintf(stderr, "Unknown command\n");
return EXIT_FAILURE;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment