input/cache: first draft of the file cache

This commit is contained in:
Max Kellermann 2019-05-08 18:39:00 +02:00
parent e8a0ce643a
commit 5d74b5cee1
27 changed files with 880 additions and 6 deletions

View File

@ -334,6 +334,39 @@ The following table lists the input options valid for all plugins:
More information can be found in the :ref:`input_plugins` reference.
Configuring the Input Cache
^^^^^^^^^^^^^^^^^^^^^^^^^^^
The input cache prefetches queued song files before they are going to
be played. This has several advantages:
- risk of buffer underruns during playback is reduced because this
decouples playback from disk (or network) I/O
- bulk transfers may be faster and more energy efficient than loading
small chunks on-the-fly
- by prefetching several songs at a time, the hard disk can spin down
for longer periods of time
This comes at a cost:
- memory usage
- bulk transfers may reduce the performance of other applications
which also want to access the disk (if the kernel's I/O scheduler
isn't doing its job properly)
To enable the input cache, add an ``input_cache`` block to the
configuration file:
.. code-block:: none
input_cache {
size "1 GB"
}
This allocates a cache of 1 GB. If the cache grows larger than that,
older files will be evicted.
Configuring decoder plugins
---------------------------

View File

@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@ -23,6 +23,7 @@
#include "Idle.hxx"
#include "Stats.hxx"
#include "client/List.hxx"
#include "input/cache/Manager.hxx"
#ifdef ENABLE_CURL
#include "RemoteTagCache.hxx"

View File

@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@ -54,6 +54,7 @@ struct Partition;
class StateFile;
class RemoteTagCache;
class StickerDatabase;
class InputCacheManager;
/**
* A utility class which, when used as the first base class, ensures
@ -98,6 +99,8 @@ struct Instance final
Systemd::Watchdog systemd_watchdog;
#endif
std::unique_ptr<InputCacheManager> input_cache;
MaskMonitor idle_monitor;
#ifdef ENABLE_NEIGHBOR_PLUGINS

View File

@ -38,6 +38,8 @@
#include "Log.hxx"
#include "LogInit.hxx"
#include "input/Init.hxx"
#include "input/cache/Config.hxx"
#include "input/cache/Manager.hxx"
#include "event/Loop.hxx"
#include "fs/AllocatedPath.hxx"
#include "fs/Config.hxx"
@ -414,6 +416,12 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
raw_config.GetPositive(ConfigOption::MAX_CONN, 10);
instance.client_list = std::make_unique<ClientList>(max_clients);
const auto *input_cache_config = raw_config.GetBlock(ConfigBlockOption::INPUT_CACHE);
if (input_cache_config != nullptr) {
const InputCacheConfig c(*input_cache_config);
instance.input_cache = std::make_unique<InputCacheManager>(c);
}
initialize_decoder_and_player(instance,
raw_config, config.replay_gain);
@ -461,6 +469,7 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
client_manager_init(raw_config);
const ScopeInputPluginsInit input_plugins_init(raw_config,
instance.io_thread.GetEventLoop());
const ScopePlaylistPluginsInit playlist_plugins_init(raw_config);
#ifdef ENABLE_DAEMON

View File

@ -20,10 +20,15 @@
#include "config.h"
#include "Partition.hxx"
#include "Instance.hxx"
#include "Log.hxx"
#include "song/DetachedSong.hxx"
#include "mixer/Volume.hxx"
#include "IdleFlags.hxx"
#include "client/Listener.hxx"
#include "input/cache/Manager.hxx"
#include "util/Domain.hxx"
static constexpr Domain cache_domain("cache");
Partition::Partition(Instance &_instance,
const char *_name,
@ -37,7 +42,9 @@ Partition::Partition(Instance &_instance,
global_events(instance.event_loop, BIND_THIS_METHOD(OnGlobalEvent)),
playlist(max_length, *this),
outputs(*this),
pc(*this, outputs, buffer_chunks,
pc(*this, outputs,
instance.input_cache.get(),
buffer_chunks,
configured_audio_format, replay_gain_config)
{
UpdateEffectiveReplayGainMode();
@ -51,6 +58,43 @@ Partition::EmitIdle(unsigned mask) noexcept
instance.EmitIdle(mask);
}
static void
PrefetchSong(InputCacheManager &cache, const char *uri) noexcept
{
if (cache.Contains(uri))
return;
FormatDebug(cache_domain, "Prefetch '%s'", uri);
try {
cache.Prefetch(uri);
} catch (...) {
FormatError(std::current_exception(),
"Prefetch '%s' failed", uri);
}
}
static void
PrefetchSong(InputCacheManager &cache, const DetachedSong &song) noexcept
{
PrefetchSong(cache, song.GetURI());
}
inline void
Partition::PrefetchQueue() noexcept
{
if (!instance.input_cache)
return;
auto &cache = *instance.input_cache;
int next = playlist.GetNextPosition();
if (next >= 0)
PrefetchSong(cache, playlist.queue.Get(next));
// TODO: prefetch more songs
}
void
Partition::UpdateEffectiveReplayGainMode() noexcept
{
@ -106,6 +150,10 @@ void
Partition::SyncWithPlayer() noexcept
{
playlist.SyncWithPlayer(pc);
/* TODO: invoke this function in batches, to let the hard disk
spin down in between */
PrefetchQueue();
}
void

View File

@ -81,6 +81,14 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
void EmitIdle(unsigned mask) noexcept;
/**
* Populate the #InputCacheManager with soon-to-be-played song
* files.
*
* Errors will be logged.
*/
void PrefetchQueue() noexcept;
void ClearQueue() noexcept {
playlist.Clear(pc);
}

View File

@ -86,6 +86,7 @@ enum class ConfigBlockOption {
AUDIO_OUTPUT,
DECODER,
INPUT,
INPUT_CACHE,
PLAYLIST_PLUGIN,
RESAMPLER,
AUDIO_FILTER,

View File

@ -86,6 +86,7 @@ const ConfigTemplate config_block_templates[] = {
{ "audio_output", true },
{ "decoder", true },
{ "input", true },
{ "input_cache" },
{ "playlist_plugin", true },
{ "resampler" },
{ "filter", true },

View File

@ -31,6 +31,8 @@
#include "Log.hxx"
#include "input/InputStream.hxx"
#include "input/LocalOpen.hxx"
#include "input/cache/Manager.hxx"
#include "input/cache/Stream.hxx"
#include "fs/Path.hxx"
#include "util/ConstBuffer.hxx"
#include "util/StringBuffer.hxx"
@ -52,8 +54,18 @@ DecoderBridge::~DecoderBridge() noexcept
}
InputStreamPtr
DecoderBridge::OpenLocal(Path path_fs)
DecoderBridge::OpenLocal(Path path_fs, const char *uri_utf8)
{
if (dc.input_cache != nullptr) {
auto lease = dc.input_cache->Get(uri_utf8, true);
if (lease) {
auto is = std::make_unique<CacheInputStream>(std::move(lease),
dc.mutex);
is->SetHandler(&dc);
return is;
}
}
return OpenLocalInputStream(path_fs, dc.mutex);
}

View File

@ -157,7 +157,7 @@ public:
/**
* Open a local file.
*/
InputStreamPtr OpenLocal(Path path_fs);
InputStreamPtr OpenLocal(Path path_fs, const char *uri_utf8);
/* virtual methods from DecoderClient */
void Ready(AudioFormat audio_format,

View File

@ -26,9 +26,11 @@
#include <assert.h>
DecoderControl::DecoderControl(Mutex &_mutex, Cond &_client_cond,
InputCacheManager *_input_cache,
const AudioFormat _configured_audio_format,
const ReplayGainConfig &_replay_gain_config) noexcept
:thread(BIND_THIS_METHOD(RunThread)),
input_cache(_input_cache),
mutex(_mutex), client_cond(_client_cond),
configured_audio_format(_configured_audio_format),
replay_gain_config(_replay_gain_config) {}

View File

@ -46,6 +46,7 @@
class DetachedSong;
class MusicBuffer;
class MusicPipe;
class InputCacheManager;
enum class DecoderState : uint8_t {
STOP = 0,
@ -68,6 +69,8 @@ class DecoderControl final : public InputStreamHandler {
Thread thread;
public:
InputCacheManager *const input_cache;
/**
* This lock protects #state and #command.
*
@ -181,6 +184,7 @@ public:
* @param _client_cond see #client_cond
*/
DecoderControl(Mutex &_mutex, Cond &_client_cond,
InputCacheManager *_input_cache,
const AudioFormat _configured_audio_format,
const ReplayGainConfig &_replay_gain_config) noexcept;
~DecoderControl() noexcept;

View File

@ -346,7 +346,7 @@ decoder_run_file(DecoderBridge &bridge, const char *uri_utf8, Path path_fs)
InputStreamPtr input_stream;
try {
input_stream = bridge.OpenLocal(path_fs);
input_stream = bridge.OpenLocal(path_fs, uri_utf8);
} catch (const std::system_error &e) {
if (IsPathNotFound(e) &&
/* ENOTDIR means this may be a path inside a

View File

@ -152,6 +152,18 @@ BufferingInputStream::RunThreadLocked(std::unique_lock<Mutex> &lock)
continue;
}
/* enforce an upper limit for each
InputStream::Read() call; this is necessary
for plugins which are unable to do partial
reads, e.g. when reading local files, the
read() system call will not return until
all requested bytes have been read from the
hard disk, instead of returning when "some"
data has been read */
constexpr size_t MAX_READ = 64 * 1024;
if (w.size > MAX_READ)
w.size = MAX_READ;
size_t nbytes = input->Read(lock, w.data, w.size);
buffer.Commit(read_offset, read_offset + nbytes);

35
src/input/cache/Config.cxx vendored Normal file
View File

@ -0,0 +1,35 @@
/*
* 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 "Config.hxx"
#include "config/Block.hxx"
#include "config/Parser.hxx"
static constexpr size_t KILOBYTE = 1024;
static constexpr size_t MEGABYTE = 1024 * KILOBYTE;
InputCacheConfig::InputCacheConfig(const ConfigBlock &block)
{
size = 256 * MEGABYTE;
const auto *size_param = block.GetBlockParam("size");
if (size_param != nullptr)
size = size_param->With([](const char *s){
return ParseSize(s);
});
}

33
src/input/cache/Config.hxx vendored Normal file
View File

@ -0,0 +1,33 @@
/*
* 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.
*/
#ifndef MPD_INPUT_CACHE_CONFIG_HXX
#define MPD_INPUT_CACHE_CONFIG_HXX
#include <stddef.h>
struct ConfigBlock;
struct InputCacheConfig {
size_t size;
explicit InputCacheConfig(const ConfigBlock &block);
};
#endif

62
src/input/cache/Item.cxx vendored Normal file
View File

@ -0,0 +1,62 @@
/*
* 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 "Item.hxx"
#include "Lease.hxx"
#include <assert.h>
InputCacheItem::InputCacheItem(InputStreamPtr _input) noexcept
:BufferingInputStream(std::move(_input)),
uri(GetInput().GetURI())
{
}
InputCacheItem::~InputCacheItem() noexcept
{
assert(leases.empty());
}
void
InputCacheItem::AddLease(InputCacheLease &lease) noexcept
{
const std::lock_guard<Mutex> lock(mutex);
leases.push_back(lease);
}
void
InputCacheItem::RemoveLease(InputCacheLease &lease) noexcept
{
const std::lock_guard<Mutex> lock(mutex);
auto i = leases.iterator_to(lease);
if (i == next_lease)
++next_lease;
leases.erase(i);
// TODO: ensure that OnBufferAvailable() isn't currently running
}
void
InputCacheItem::OnBufferAvailable() noexcept
{
for (auto i = leases.begin(); i != leases.end(); i = next_lease) {
next_lease = std::next(i);
i->OnInputCacheAvailable();
}
}

80
src/input/cache/Item.hxx vendored Normal file
View File

@ -0,0 +1,80 @@
/*
* 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.
*/
#ifndef MPD_INPUT_CACHE_ITEM_HXX
#define MPD_INPUT_CACHE_ITEM_HXX
#include "input/BufferingInputStream.hxx"
#include "thread/Mutex.hxx"
#include "util/SparseBuffer.hxx"
#include <boost/intrusive/list.hpp>
#include <boost/intrusive/set_hook.hpp>
#include <memory>
#include <string>
class InputCacheLease;
/**
* An item in the #InputCacheManager. It caches the contents of a
* file, and reading and managing it through the base class
* #BufferingInputStream.
*
* Use the class #CacheInputStream to read from it.
*/
class InputCacheItem final
: public BufferingInputStream,
public boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::auto_unlink>>,
public boost::intrusive::set_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>
{
const std::string uri;
using LeaseList =
boost::intrusive::list<InputCacheLease,
boost::intrusive::base_hook<boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>>,
boost::intrusive::constant_time_size<false>>;
LeaseList leases;
LeaseList::iterator next_lease = leases.end();
public:
explicit InputCacheItem(InputStreamPtr _input) noexcept;
~InputCacheItem() noexcept;
const char *GetUri() const noexcept {
return uri.c_str();
}
using BufferingInputStream::size;
bool IsInUse() const noexcept {
const std::lock_guard<Mutex> lock(mutex);
return !leases.empty();
}
void AddLease(InputCacheLease &lease) noexcept;
void RemoveLease(InputCacheLease &lease) noexcept;
private:
/* virtual methods from class BufferingInputStream */
void OnBufferAvailable() noexcept override;
};
#endif

94
src/input/cache/Lease.hxx vendored Normal file
View File

@ -0,0 +1,94 @@
/*
* 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.
*/
#ifndef MPD_INPUT_CACHE_LEASE_HXX
#define MPD_INPUT_CACHE_LEASE_HXX
#include "Item.hxx"
#include <boost/intrusive/list_hook.hpp>
#include <utility>
/**
* A lease for an #InputCacheItem.
*/
class InputCacheLease
: public boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>
{
InputCacheItem *item = nullptr;
public:
InputCacheLease() = default;
explicit InputCacheLease(InputCacheItem &_item) noexcept
:item(&_item)
{
item->AddLease(*this);
}
InputCacheLease(InputCacheLease &&src) noexcept
:item(std::exchange(src.item, nullptr))
{
if (item != nullptr) {
item->RemoveLease(src);
item->AddLease(*this);
}
}
~InputCacheLease() noexcept {
if (item != nullptr)
item->RemoveLease(*this);
}
InputCacheLease &operator=(InputCacheLease &&src) noexcept {
using std::swap;
swap(item, src.item);
if (item != nullptr) {
item->RemoveLease(src);
item->AddLease(*this);
}
return *this;
}
operator bool() const noexcept {
return item != nullptr;
}
auto &operator*() const noexcept {
return *item;
}
auto *operator->() const noexcept {
return item;
}
auto &GetCacheItem() const noexcept {
return *item;
}
/**
* Caller locks #InputCacheItem::mutex.
*/
virtual void OnInputCacheAvailable() noexcept {}
};
#endif

163
src/input/cache/Manager.cxx vendored Normal file
View File

@ -0,0 +1,163 @@
/*
* 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 "Manager.hxx"
#include "Config.hxx"
#include "Item.hxx"
#include "Lease.hxx"
#include "input/InputStream.hxx"
#include "fs/Traits.hxx"
#include "util/DeleteDisposer.hxx"
#include <string.h>
inline bool
InputCacheManager::ItemCompare::operator()(const InputCacheItem &a,
const char *b) const noexcept
{
return strcmp(a.GetUri(), b) < 0;
}
inline bool
InputCacheManager::ItemCompare::operator()(const char *a,
const InputCacheItem &b) const noexcept
{
return strcmp(a, b.GetUri()) < 0;
}
inline bool
InputCacheManager::ItemCompare::operator()(const InputCacheItem &a,
const InputCacheItem &b) const noexcept
{
return strcmp(a.GetUri(), b.GetUri()) < 0;
}
InputCacheManager::InputCacheManager(const InputCacheConfig &config) noexcept
:max_total_size(config.size)
{
}
InputCacheManager::~InputCacheManager() noexcept
{
items_by_time.clear_and_dispose(DeleteDisposer());
}
bool
InputCacheManager::IsEligible(const InputStream &input) noexcept
{
assert(input.IsReady());
return input.IsSeekable() && input.KnownSize() &&
input.GetSize() > 0 &&
input.GetSize() <= max_total_size / 2;
}
bool
InputCacheManager::Contains(const char *uri) noexcept
{
return Get(uri, false);
}
InputCacheLease
InputCacheManager::Get(const char *uri, bool create)
{
// TODO: allow caching remote files
if (!PathTraitsUTF8::IsAbsolute(uri))
return {};
UriMap::insert_commit_data hint;
auto result = items_by_uri.insert_check(uri, items_by_uri.key_comp(),
hint);
if (!result.second) {
auto &item = *result.first;
/* refresh */
items_by_time.erase(items_by_time.iterator_to(item));
items_by_time.push_back(item);
// TODO revalidate the cache item using the file's mtime?
// TODO if cache item contains error, retry now?
return InputCacheLease(item);
}
if (!create)
return {};
// TODO: wait for "ready" without blocking here
auto is = InputStream::OpenReady(uri, mutex);
if (!IsEligible(*is))
return {};
const size_t size = is->GetSize();
total_size += size;
while (total_size > max_total_size && EvictOldestUnused()) {}
auto *item = new InputCacheItem(std::move(is));
items_by_uri.insert_commit(*item, hint);
items_by_time.push_back(*item);
return InputCacheLease(*item);
}
void
InputCacheManager::Prefetch(const char *uri)
{
Get(uri, true);
}
void
InputCacheManager::Remove(InputCacheItem &item) noexcept
{
assert(total_size >= item.size());
total_size -= item.size();
items_by_time.erase(items_by_time.iterator_to(item));
items_by_uri.erase(items_by_uri.iterator_to(item));
}
void
InputCacheManager::Delete(InputCacheItem *item) noexcept
{
Remove(*item);
delete item;
}
InputCacheItem *
InputCacheManager::FindOldestUnused() noexcept
{
for (auto &i : items_by_time)
if (!i.IsInUse())
return &i;
return nullptr;
}
bool
InputCacheManager::EvictOldestUnused() noexcept
{
auto *item = FindOldestUnused();
if (item == nullptr)
return false;
Delete(item);
return true;
}

114
src/input/cache/Manager.hxx vendored Normal file
View File

@ -0,0 +1,114 @@
/*
* 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.
*/
#ifndef MPD_INPUT_CACHE_MANAGER_HXX
#define MPD_INPUT_CACHE_MANAGER_HXX
#include "input/Offset.hxx"
#include "thread/Mutex.hxx"
#include "util/Compiler.h"
#include <boost/intrusive/set.hpp>
#include <boost/intrusive/list.hpp>
class InputStream;
class InputCacheItem;
class InputCacheLease;
struct InputCacheConfig;
/**
* A class which caches files in RAM. It is supposed to prefetch
* files before they are played.
*/
class InputCacheManager {
const size_t max_total_size;
mutable Mutex mutex;
size_t total_size = 0;
struct ItemCompare {
gcc_pure
bool operator()(const InputCacheItem &a,
const char *b) const noexcept;
gcc_pure
bool operator()(const char *a,
const InputCacheItem &b) const noexcept;
gcc_pure
bool operator()(const InputCacheItem &a,
const InputCacheItem &b) const noexcept;
};
boost::intrusive::list<InputCacheItem,
boost::intrusive::base_hook<boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::auto_unlink>>>,
boost::intrusive::constant_time_size<false>> items_by_time;
using UriMap =
boost::intrusive::set<InputCacheItem,
boost::intrusive::base_hook<boost::intrusive::set_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>>,
boost::intrusive::compare<ItemCompare>,
boost::intrusive::constant_time_size<false>>;
UriMap items_by_uri;
public:
explicit InputCacheManager(const InputCacheConfig &config) noexcept;
~InputCacheManager() noexcept;
gcc_pure
bool Contains(const char *uri) noexcept;
/**
* Throws if opening the #InputStream fails.
*
* @param create if true, then the cache item will be created
* if it did not exist
* @return a lease of the new item or nullptr if the file is
* not eligible for caching
*/
InputCacheLease Get(const char *uri, bool create);
/**
* Shortcut for "Get(uri,true)", discarding the returned
* lease.
*/
void Prefetch(const char *uri);
private:
/**
* Check whether the given #InputStream can be stored in this
* cache.
*/
bool IsEligible(const InputStream &input) noexcept;
void Remove(InputCacheItem &item) noexcept;
void Delete(InputCacheItem *item) noexcept;
InputCacheItem *FindOldestUnused() noexcept;
/**
* @return true if one item has been evicted, false if no
* unused item was found
*/
bool EvictOldestUnused() noexcept;
};
#endif

96
src/input/cache/Stream.cxx vendored Normal file
View File

@ -0,0 +1,96 @@
/*
* 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 "Stream.hxx"
CacheInputStream::CacheInputStream(InputCacheLease _lease,
Mutex &_mutex) noexcept
:InputStream(_lease->GetUri(), _mutex),
InputCacheLease(std::move(_lease))
{
auto &i = GetCacheItem();
size = i.size();
seekable = true;
SetReady();
}
void
CacheInputStream::Check()
{
const ScopeUnlock unlock(mutex);
auto &i = GetCacheItem();
const std::lock_guard<Mutex> protect(i.mutex);
i.Check();
}
void
CacheInputStream::Seek(std::unique_lock<Mutex> &, offset_type new_offset)
{
offset = new_offset;
}
bool
CacheInputStream::IsEOF() const noexcept
{
return offset == size;
}
bool
CacheInputStream::IsAvailable() const noexcept
{
const auto _offset = offset;
const ScopeUnlock unlock(mutex);
auto &i = GetCacheItem();
const std::lock_guard<Mutex> protect(i.mutex);
return i.IsAvailable(_offset);
}
size_t
CacheInputStream::Read(std::unique_lock<Mutex> &lock,
void *ptr, size_t read_size)
{
const auto _offset = offset;
auto &i = GetCacheItem();
size_t nbytes;
{
const ScopeUnlock unlock(mutex);
const std::lock_guard<Mutex> protect(i.mutex);
nbytes = i.Read(lock, _offset, ptr, read_size);
}
offset += nbytes;
return nbytes;
}
void
CacheInputStream::OnInputCacheAvailable() noexcept
{
auto &i = GetCacheItem();
const ScopeUnlock unlock(i.mutex);
const std::lock_guard<Mutex> protect(mutex);
InvokeOnAvailable();
}

52
src/input/cache/Stream.hxx vendored Normal file
View File

@ -0,0 +1,52 @@
/*
* 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.
*/
#ifndef MPD_CACHE_INPUT_STREAM_HXX
#define MPD_CACHE_INPUT_STREAM_HXX
#include "Lease.hxx"
#include "input/InputStream.hxx"
/**
* An #InputStream implementation which reads data from an
* #InputCacheItem.
*/
class CacheInputStream final : public InputStream, InputCacheLease {
public:
CacheInputStream(InputCacheLease _lease, Mutex &_mutex) noexcept;
/* virtual methods from class InputStream */
void Check() override;
/* we don't need to implement Update() because all attributes
have been copied already in our constructor */
//void Update() noexcept;
void Seek(std::unique_lock<Mutex> &lock, offset_type offset) override;
bool IsEOF() const noexcept override;
/* we don't support tags */
// std::unique_ptr<Tag> ReadTag() override;
bool IsAvailable() const noexcept override;
size_t Read(std::unique_lock<Mutex> &lock,
void *ptr, size_t size) override;
private:
/* virtual methods from class InputCacheLease */
void OnInputCacheAvailable() noexcept override;
};
#endif

View File

@ -35,6 +35,10 @@ input_glue = static_library(
'BufferingInputStream.cxx',
'BufferedInputStream.cxx',
'MaybeBufferedInputStream.cxx',
'cache/Config.cxx',
'cache/Manager.cxx',
'cache/Item.cxx',
'cache/Stream.cxx',
include_directories: inc,
)

View File

@ -28,10 +28,12 @@
PlayerControl::PlayerControl(PlayerListener &_listener,
PlayerOutputs &_outputs,
InputCacheManager *_input_cache,
unsigned _buffer_chunks,
AudioFormat _configured_audio_format,
const ReplayGainConfig &_replay_gain_config) noexcept
:listener(_listener), outputs(_outputs),
input_cache(_input_cache),
buffer_chunks(_buffer_chunks),
configured_audio_format(_configured_audio_format),
thread(BIND_THIS_METHOD(RunThread)),

View File

@ -39,6 +39,7 @@
struct Tag;
class PlayerListener;
class PlayerOutputs;
class InputCacheManager;
class DetachedSong;
enum class PlayerState : uint8_t {
@ -116,6 +117,8 @@ class PlayerControl final : public AudioOutputClient {
PlayerOutputs &outputs;
InputCacheManager *const input_cache;
const unsigned buffer_chunks;
/**
@ -234,6 +237,7 @@ class PlayerControl final : public AudioOutputClient {
public:
PlayerControl(PlayerListener &_listener,
PlayerOutputs &_outputs,
InputCacheManager *_input_cache,
unsigned buffer_chunks,
AudioFormat _configured_audio_format,
const ReplayGainConfig &_replay_gain_config) noexcept;

View File

@ -1132,6 +1132,7 @@ try {
SetThreadName("player");
DecoderControl dc(mutex, cond,
input_cache,
configured_audio_format,
replay_gain_config);
dc.StartThread();