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

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