mirror of
https://github.com/Zygo/bees.git
synced 2025-08-02 05:43:29 +02:00
Compare commits
106 Commits
v0.5
...
subvol-thr
Author | SHA1 | Date | |
---|---|---|---|
|
68d48b3d63 | ||
|
6d6aedd8ec | ||
|
025b14f38f | ||
|
dd6d8caaa2 | ||
|
58e53d7e0c | ||
|
4bfb637b0e | ||
|
addb18354e | ||
|
54d30485a7 | ||
|
93fb29a461 | ||
|
4aa5978a89 | ||
|
365a913a26 | ||
|
634a1d0bf6 | ||
|
3a24cd3010 | ||
|
3391593cb9 | ||
|
fdd8350239 | ||
|
60cd9c6165 | ||
|
f0e02478ef | ||
|
421641e242 | ||
|
0fce10991b | ||
|
a465d997bd | ||
|
361ef0bebf | ||
|
1fcf07cc2a | ||
|
333b2f7746 | ||
|
ff9e0e3571 | ||
|
2d49d98bd2 | ||
|
92aa13a6ae | ||
|
f0c516f33b | ||
|
8e2139d6ed | ||
|
b959af1a15 | ||
|
78f96a9fbd | ||
|
953c158868 | ||
|
f7f99f52b5 | ||
|
abeb6e74b2 | ||
|
6c67ae0d5e | ||
|
c39b72b4a7 | ||
|
ba981c133a | ||
|
53f8fc506a | ||
|
2a2632a101 | ||
|
28f7299f8f | ||
|
02181956d2 | ||
|
9b0e8c56c2 | ||
|
b978a5dea6 | ||
|
3024e43355 | ||
|
270a91cf17 | ||
|
93ba0f48de | ||
|
d930136484 | ||
|
f7320baa56 | ||
|
21212cd3e3 | ||
|
0c6a4d00c8 | ||
|
52997936d5 | ||
|
755f16a948 | ||
|
66fd28830d | ||
|
85106bd9a9 | ||
|
bb273770c5 | ||
|
b7e316b005 | ||
|
1aa1decd1d | ||
|
8ea92202fc | ||
|
a3cd3ca07f | ||
|
5a8c655fc4 | ||
|
16432d0bb7 | ||
|
b9dc4792bc | ||
|
b2f000ad7a | ||
|
415756fb99 | ||
|
175d7fc10e | ||
|
1f668d1055 | ||
|
802d5faf46 | ||
|
552e74066d | ||
|
1052119a53 | ||
|
917fc8c412 | ||
|
59fe9f4617 | ||
|
b631986218 | ||
|
9f8bdcfd8c | ||
|
5eaf3d0aeb | ||
|
d6f328ce76 | ||
|
7defaf9751 | ||
|
9ba9a8e9fa | ||
|
2775058aee | ||
|
2dc027c701 | ||
|
cc7b4f22b5 | ||
|
a3d7032eda | ||
|
f01c20f972 | ||
|
59660cfc00 | ||
|
f56f736d28 | ||
|
8a932a632f | ||
|
5c91045557 | ||
|
3023b7f57a | ||
|
c1dbd30d82 | ||
|
d43199e3d6 | ||
|
9daa51edaa | ||
|
e509210428 | ||
|
235a3b6877 | ||
|
aa0b22d445 | ||
|
44fedfc928 | ||
|
b004b22e47 | ||
|
5a7f4f7899 | ||
|
dc975f1fa4 | ||
|
99fe452101 | ||
|
9cb48c35b9 | ||
|
be1aa049c6 | ||
|
e46b96d23c | ||
|
e7fddcbc04 | ||
|
920cfbc1f6 | ||
|
4f9c2c0310 | ||
|
4604f5bc96 | ||
|
09ab0778e8 | ||
|
b22b4ed427 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,3 +12,4 @@ make.log
|
||||
make.log.new
|
||||
localconf
|
||||
scripts/beesd
|
||||
scripts/beesd@.service
|
||||
|
40
Makefile
40
Makefile
@@ -1,16 +1,19 @@
|
||||
PREFIX ?= /
|
||||
LIBEXEC_PREFIX ?= $(PREFIX)/usr/lib/bees
|
||||
LIBDIR ?= lib
|
||||
USR_PREFIX ?= $(PREFIX)/usr
|
||||
USRLIB_PREFIX ?= $(USR_PREFIX)/$(LIBDIR)
|
||||
SYSTEMD_LIB_PREFIX ?= $(PREFIX)/lib/systemd
|
||||
LIBEXEC_PREFIX ?= $(USRLIB_PREFIX)/bees
|
||||
|
||||
MARKDOWN := $(firstword $(shell which markdown markdown2 markdown_py 2>/dev/null))
|
||||
MARKDOWN ?= markdown
|
||||
MARKDOWN := $(firstword $(shell which markdown markdown2 markdown_py 2>/dev/null || echo markdown))
|
||||
|
||||
# allow local configuration to override above variables
|
||||
-include localconf
|
||||
|
||||
default all: lib src test README.html
|
||||
default all: lib src scripts test README.html
|
||||
|
||||
clean: ## Cleanup
|
||||
git clean -dfx
|
||||
git clean -dfx -e localconf
|
||||
|
||||
.PHONY: lib src test
|
||||
|
||||
@@ -25,10 +28,7 @@ test: ## Run tests
|
||||
test: lib src
|
||||
$(MAKE) -C test
|
||||
|
||||
scripts/beesd: scripts/beesd.in
|
||||
sed -e's#@LIBEXEC_PREFIX@#$(LIBEXEC_PREFIX)#' -e's#@PREFIX@#$(PREFIX)#' "$<" >"$@"
|
||||
|
||||
scripts/beesd@.service: scripts/beesd@.service.in
|
||||
scripts/%: scripts/%.in
|
||||
sed -e's#@LIBEXEC_PREFIX@#$(LIBEXEC_PREFIX)#' -e's#@PREFIX@#$(PREFIX)#' "$<" >"$@"
|
||||
|
||||
scripts: scripts/beesd scripts/beesd@.service
|
||||
@@ -37,16 +37,22 @@ README.html: README.md
|
||||
$(MARKDOWN) README.md > README.html.new
|
||||
mv -f README.html.new README.html
|
||||
|
||||
install: ## Install bees + libs
|
||||
install: lib src test
|
||||
install -Dm644 lib/libcrucible.so $(PREFIX)/usr/lib/libcrucible.so
|
||||
install -Dm755 bin/bees $(LIBEXEC_PREFIX)/bees
|
||||
install_bees: ## Install bees + libs
|
||||
install_bees: lib src test
|
||||
install -Dm644 lib/libcrucible.so $(DESTDIR)$(USRLIB_PREFIX)/libcrucible.so
|
||||
install -Dm755 bin/bees $(DESTDIR)$(LIBEXEC_PREFIX)/bees
|
||||
|
||||
install_scripts: ## Install scipts
|
||||
install_scripts:
|
||||
install -Dm755 scripts/beesd $(PREFIX)/usr/sbin/beesd
|
||||
install -Dm644 scripts/beesd.conf.sample $(PREFIX)/etc/bees/beesd.conf.sample
|
||||
install -Dm644 scripts/beesd@.service $(PREFIX)/lib/systemd/system/beesd@.service
|
||||
install_scripts: scripts
|
||||
install -Dm755 scripts/beesd $(DESTDIR)$(USR_PREFIX)/sbin/beesd
|
||||
install -Dm644 scripts/beesd.conf.sample $(DESTDIR)$(PREFIX)/etc/bees/beesd.conf.sample
|
||||
install -Dm644 scripts/beesd@.service $(DESTDIR)$(SYSTEMD_LIB_PREFIX)/system/beesd@.service
|
||||
|
||||
install: ## Install distribution
|
||||
install: install_bees install_scripts
|
||||
|
||||
help: ## Show help
|
||||
@fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##/\t/'
|
||||
|
||||
bees: all
|
||||
fly: install
|
||||
|
37
README.md
37
README.md
@@ -322,13 +322,41 @@ Not really a bug, but a gotcha nonetheless:
|
||||
To limit this delay, Bees closes all FDs in its file FD cache every
|
||||
15 minutes.
|
||||
|
||||
Build
|
||||
-----
|
||||
Installation
|
||||
============
|
||||
|
||||
Bees can be installed by following one these instructions:
|
||||
|
||||
Arch package
|
||||
------------
|
||||
|
||||
Bees is availabe in Arch Linux AUR. Install with:
|
||||
|
||||
`$ pacaur -S bees-git`
|
||||
|
||||
Gentoo ebuild
|
||||
-------------
|
||||
|
||||
Bees is available as a Gentoo ebuild. Just copy `bees-9999.ebuild` from
|
||||
`contrib/gentoo` including the `files` subdirectory to your local
|
||||
overlay category `sys-fs`.
|
||||
|
||||
You can copy the ebuild to match a Bees version number, and it will
|
||||
build that tagged version. It is partly supported since v0.5,
|
||||
previous versions won't work.
|
||||
|
||||
Build from source
|
||||
-----------------
|
||||
|
||||
Build with `make`. The build produces `bin/bees` and `lib/libcrucible.so`,
|
||||
which must be copied to somewhere in `$PATH` and `$LD_LIBRARY_PATH`
|
||||
on the target system respectively.
|
||||
|
||||
It will also generate `scripts/beesd@.service` for systemd users. This
|
||||
service makes use of a helper script `scripts/beesd` to boot the service.
|
||||
Both of the latter use the filesystem UUID to mount the root subvolume
|
||||
within a temporary runtime directory.
|
||||
|
||||
### Ubuntu 16.04 - 17.04:
|
||||
`$ apt -y install build-essential btrfs-tools uuid-dev markdown && make`
|
||||
|
||||
@@ -360,10 +388,15 @@ Dependencies
|
||||
|
||||
* markdown
|
||||
|
||||
* util-linux version that provides `blkid` command for the helper
|
||||
script `scripts/beesd` to work
|
||||
|
||||
Setup
|
||||
-----
|
||||
|
||||
If you don't want to use the helper script `scripts/beesd` to setup and
|
||||
configure bees, here's how you manually setup bees.
|
||||
|
||||
Create a directory for bees state files:
|
||||
|
||||
export BEESHOME=/some/path
|
||||
|
44
contrib/gentoo/bees-9999.ebuild
Normal file
44
contrib/gentoo/bees-9999.ebuild
Normal file
@@ -0,0 +1,44 @@
|
||||
# Copyright 1999-2018 Gentoo Foundation
|
||||
# Distributed under the terms of the GNU General Public License v2
|
||||
|
||||
EAPI=6
|
||||
|
||||
inherit git-r3 eutils multilib
|
||||
|
||||
DESCRIPTION="Best-Effort Extent-Same, a btrfs dedup agent"
|
||||
HOMEPAGE="https://github.com/Zygo/bees"
|
||||
|
||||
if [[ ${PV} == "9999" ]] ; then
|
||||
EGIT_REPO_URI="https://github.com/kakra/bees.git"
|
||||
EGIT_BRANCH="integration"
|
||||
else
|
||||
SRC_URI="https://github.com/Zygo/bees/archive/v${PV}.tar.gz -> ${P}.tar.gz"
|
||||
fi
|
||||
|
||||
PATCHES="
|
||||
${FILESDIR}/v0.5-gentoo_build.patch
|
||||
"
|
||||
|
||||
LICENSE="GPL-3"
|
||||
SLOT="0"
|
||||
KEYWORDS=""
|
||||
IUSE=""
|
||||
|
||||
COMMON_DEPEND="
|
||||
>=sys-apps/util-linux-2.30.2
|
||||
>=sys-devel/gcc-4.9
|
||||
>=sys-fs/btrfs-progs-4.1
|
||||
"
|
||||
DEPEND="
|
||||
${COMMON_DEPEND}
|
||||
|| ( dev-python/markdown dev-python/markdown2 )
|
||||
"
|
||||
RDEPEND="${COMMON_DEPEND}"
|
||||
|
||||
DOCS="README.md COPYING"
|
||||
HTML_DOCS="README.html"
|
||||
|
||||
src_prepare() {
|
||||
default
|
||||
echo LIBDIR=$(get_libdir) >>${S}/localconf
|
||||
}
|
20
contrib/gentoo/files/v0.5-gentoo_build.patch
Normal file
20
contrib/gentoo/files/v0.5-gentoo_build.patch
Normal file
@@ -0,0 +1,20 @@
|
||||
diff --git a/localconf b/localconf
|
||||
new file mode 100644
|
||||
index 0000000..7705cbb
|
||||
--- /dev/null
|
||||
+++ b/localconf
|
||||
@@ -0,0 +1,2 @@
|
||||
+PREFIX=/
|
||||
+LIBEXEC_PREFIX=/usr/libexec
|
||||
diff --git a/makeflags b/makeflags
|
||||
index f5983cb..0348623 100644
|
||||
--- a/makeflags
|
||||
+++ b/makeflags
|
||||
@@ -1,4 +1,3 @@
|
||||
-CCFLAGS = -Wall -Wextra -Werror -O3 -march=native -I../include -ggdb -D_FILE_OFFSET_BITS=64
|
||||
-# CCFLAGS = -Wall -Wextra -Werror -O0 -I../include -ggdb -fpic -D_FILE_OFFSET_BITS=64
|
||||
-CFLAGS = $(CCFLAGS) -std=c99
|
||||
-CXXFLAGS = $(CCFLAGS) -std=c++11 -Wold-style-cast
|
||||
+CCFLAGS = -O3 -I../include -fpic -D_FILE_OFFSET_BITS=64
|
||||
+CFLAGS += $(CCFLAGS) -std=c99
|
||||
+CXXFLAGS += $(CCFLAGS) -std=c++11 -Wold-style-cast
|
18
include/crucible/cleanup.h
Normal file
18
include/crucible/cleanup.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef CRUCIBLE_CLEANUP_H
|
||||
#define CRUCIBLE_CLEANUP_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace crucible {
|
||||
using namespace std;
|
||||
|
||||
class Cleanup {
|
||||
function<void()> m_cleaner;
|
||||
public:
|
||||
Cleanup(function<void()> func);
|
||||
~Cleanup();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // CRUCIBLE_CLEANUP_H
|
@@ -57,6 +57,10 @@ namespace crucible {
|
||||
|
||||
typedef ResourceHandle<int, IOHandle> Fd;
|
||||
|
||||
static string __relative_path;
|
||||
void set_relative_path(string path);
|
||||
string relative_path();
|
||||
|
||||
// Functions named "foo_or_die" throw exceptions on failure.
|
||||
|
||||
// Attempt to open the file with the given mode
|
||||
|
@@ -1,6 +1,7 @@
|
||||
#ifndef CRUCIBLE_LOCKSET_H
|
||||
#define CRUCIBLE_LOCKSET_H
|
||||
|
||||
#include <crucible/cleanup.h>
|
||||
#include <crucible/error.h>
|
||||
#include <crucible/process.h>
|
||||
|
||||
@@ -12,6 +13,8 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
|
||||
namespace crucible {
|
||||
using namespace std;
|
||||
@@ -29,8 +32,11 @@ namespace crucible {
|
||||
mutex m_mutex;
|
||||
condition_variable m_condvar;
|
||||
size_t m_max_size = numeric_limits<size_t>::max();
|
||||
set<uint64_t> m_priorities;
|
||||
uint64_t m_priority_counter;
|
||||
|
||||
bool full();
|
||||
bool first_in_priority(uint64_t my_priority);
|
||||
bool locked(const key_type &name);
|
||||
|
||||
class Lock {
|
||||
@@ -61,7 +67,6 @@ namespace crucible {
|
||||
size_t size();
|
||||
bool empty();
|
||||
set_type copy();
|
||||
void wait_unlock(double interval);
|
||||
|
||||
void max_size(size_t max);
|
||||
|
||||
@@ -96,6 +101,26 @@ namespace crucible {
|
||||
return m_set.size() >= m_max_size;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool
|
||||
LockSet<T>::first_in_priority(uint64_t my_priority)
|
||||
{
|
||||
#if 1
|
||||
auto counter = m_max_size;
|
||||
for (auto i : m_priorities) {
|
||||
if (i == my_priority) {
|
||||
return true;
|
||||
}
|
||||
if (++counter > m_max_size) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
THROW_ERROR(runtime_error, "my_priority " << my_priority << " not in m_priorities (size " << m_priorities.size() << ")");
|
||||
#else
|
||||
return *m_priorities.begin() == my_priority;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool
|
||||
LockSet<T>::locked(const key_type &name)
|
||||
@@ -105,9 +130,10 @@ namespace crucible {
|
||||
|
||||
template <class T>
|
||||
void
|
||||
LockSet<T>::max_size(size_t s)
|
||||
LockSet<T>::max_size(size_t new_max_size)
|
||||
{
|
||||
m_max_size = s;
|
||||
THROW_CHECK1(out_of_range, new_max_size, new_max_size > 0);
|
||||
m_max_size = new_max_size;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@@ -115,11 +141,18 @@ namespace crucible {
|
||||
LockSet<T>::lock(const key_type &name)
|
||||
{
|
||||
unique_lock<mutex> lock(m_mutex);
|
||||
while (full() || locked(name)) {
|
||||
auto my_priority = m_priority_counter++;
|
||||
Cleanup cleanup([&]() {
|
||||
m_priorities.erase(my_priority);
|
||||
});
|
||||
m_priorities.insert(my_priority);
|
||||
while (full() || locked(name) || !first_in_priority(my_priority)) {
|
||||
m_condvar.wait(lock);
|
||||
}
|
||||
auto rv = m_set.insert(make_pair(name, gettid()));
|
||||
THROW_CHECK0(runtime_error, rv.second);
|
||||
// We removed our priority slot so other threads have to check again
|
||||
m_condvar.notify_all();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@@ -142,18 +175,11 @@ namespace crucible {
|
||||
unique_lock<mutex> lock(m_mutex);
|
||||
auto erase_count = m_set.erase(name);
|
||||
m_condvar.notify_all();
|
||||
lock.unlock();
|
||||
this_thread::yield();
|
||||
THROW_CHECK1(invalid_argument, erase_count, erase_count == 1);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void
|
||||
LockSet<T>::wait_unlock(double interval)
|
||||
{
|
||||
unique_lock<mutex> lock(m_mutex);
|
||||
if (m_set.empty()) return;
|
||||
m_condvar.wait_for(lock, chrono::duration<double>(interval));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
size_t
|
||||
LockSet<T>::size()
|
||||
|
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "crucible/error.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -52,7 +53,6 @@ namespace crucible {
|
||||
|
||||
// A bunch of static variables and functions
|
||||
static mutex s_map_mutex;
|
||||
static mutex s_ptr_mutex;
|
||||
static map_type s_map;
|
||||
static resource_ptr_type insert(const key_type &key);
|
||||
static resource_ptr_type insert(const resource_ptr_type &res);
|
||||
@@ -83,14 +83,14 @@ namespace crucible {
|
||||
ResourceHandle(const resource_ptr_type &res);
|
||||
ResourceHandle& operator=(const resource_ptr_type &res);
|
||||
|
||||
// default constructor is public and mostly harmless
|
||||
// default construct/assign/move is public and mostly harmless
|
||||
ResourceHandle() = default;
|
||||
ResourceHandle(const ResourceHandle &that) = default;
|
||||
ResourceHandle(ResourceHandle &&that) = default;
|
||||
ResourceHandle& operator=(const ResourceHandle &that) = default;
|
||||
ResourceHandle& operator=(ResourceHandle &&that) = default;
|
||||
|
||||
// copy/assign/move/move-assign - with a mutex to help shared_ptr be atomic
|
||||
ResourceHandle(const ResourceHandle &that);
|
||||
ResourceHandle(ResourceHandle &&that);
|
||||
ResourceHandle& operator=(const ResourceHandle &that);
|
||||
ResourceHandle& operator=(ResourceHandle &&that);
|
||||
// Nontrivial destructor
|
||||
~ResourceHandle();
|
||||
|
||||
// forward anything else to the Resource constructor
|
||||
@@ -239,7 +239,6 @@ namespace crucible {
|
||||
template <class Key, class Resource>
|
||||
ResourceHandle<Key, Resource>::ResourceHandle(const key_type &key)
|
||||
{
|
||||
unique_lock<mutex> lock(s_ptr_mutex);
|
||||
m_ptr = insert(key);
|
||||
}
|
||||
|
||||
@@ -247,7 +246,6 @@ namespace crucible {
|
||||
ResourceHandle<Key, Resource>&
|
||||
ResourceHandle<Key, Resource>::operator=(const key_type &key)
|
||||
{
|
||||
unique_lock<mutex> lock(s_ptr_mutex);
|
||||
m_ptr = insert(key);
|
||||
return *this;
|
||||
}
|
||||
@@ -255,7 +253,6 @@ namespace crucible {
|
||||
template <class Key, class Resource>
|
||||
ResourceHandle<Key, Resource>::ResourceHandle(const resource_ptr_type &res)
|
||||
{
|
||||
unique_lock<mutex> lock(s_ptr_mutex);
|
||||
m_ptr = insert(res);
|
||||
}
|
||||
|
||||
@@ -263,65 +260,32 @@ namespace crucible {
|
||||
ResourceHandle<Key, Resource>&
|
||||
ResourceHandle<Key, Resource>::operator=(const resource_ptr_type &res)
|
||||
{
|
||||
unique_lock<mutex> lock(s_ptr_mutex);
|
||||
m_ptr = insert(res);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class Key, class Resource>
|
||||
ResourceHandle<Key, Resource>::ResourceHandle(const ResourceHandle &that)
|
||||
{
|
||||
unique_lock<mutex> lock(s_ptr_mutex);
|
||||
m_ptr = that.m_ptr;
|
||||
}
|
||||
|
||||
template <class Key, class Resource>
|
||||
ResourceHandle<Key, Resource>::ResourceHandle(ResourceHandle &&that)
|
||||
{
|
||||
unique_lock<mutex> lock(s_ptr_mutex);
|
||||
swap(m_ptr, that.m_ptr);
|
||||
}
|
||||
|
||||
template <class Key, class Resource>
|
||||
ResourceHandle<Key, Resource> &
|
||||
ResourceHandle<Key, Resource>::operator=(ResourceHandle &&that)
|
||||
{
|
||||
unique_lock<mutex> lock(s_ptr_mutex);
|
||||
m_ptr = that.m_ptr;
|
||||
that.m_ptr.reset();
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class Key, class Resource>
|
||||
ResourceHandle<Key, Resource> &
|
||||
ResourceHandle<Key, Resource>::operator=(const ResourceHandle &that)
|
||||
{
|
||||
unique_lock<mutex> lock(s_ptr_mutex);
|
||||
m_ptr = that.m_ptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class Key, class Resource>
|
||||
ResourceHandle<Key, Resource>::~ResourceHandle()
|
||||
{
|
||||
unique_lock<mutex> lock_ptr(s_ptr_mutex);
|
||||
// No pointer, nothing to do
|
||||
if (!m_ptr) {
|
||||
return;
|
||||
}
|
||||
// Save key so we can clean the map
|
||||
auto key = s_traits.get_key(*m_ptr);
|
||||
// Save pointer so we can release lock before deleting
|
||||
auto ptr_copy = m_ptr;
|
||||
// Save a weak_ptr so we can tell if we need to clean the map
|
||||
weak_ptr_type wp = m_ptr;
|
||||
// Drop shared_ptr
|
||||
m_ptr.reset();
|
||||
// Release lock
|
||||
lock_ptr.unlock();
|
||||
// Delete our (possibly last) reference to pointer
|
||||
ptr_copy.reset();
|
||||
// If there are still other references to the shared_ptr, we can stop now
|
||||
if (!wp.expired()) {
|
||||
return;
|
||||
}
|
||||
// Remove weak_ptr from map if it has expired
|
||||
// (and not been replaced in the meantime)
|
||||
unique_lock<mutex> lock_map(s_map_mutex);
|
||||
auto found = s_map.find(key);
|
||||
// Map entry may have been replaced, so check for expiry again
|
||||
if (found != s_map.end() && found->second.expired()) {
|
||||
s_map.erase(key);
|
||||
}
|
||||
@@ -331,23 +295,17 @@ namespace crucible {
|
||||
typename ResourceHandle<Key, Resource>::resource_ptr_type
|
||||
ResourceHandle<Key, Resource>::get_resource_ptr() const
|
||||
{
|
||||
unique_lock<mutex> lock(s_ptr_mutex);
|
||||
// Make isolated copy of pointer with lock held, and return the copy
|
||||
auto rv = m_ptr;
|
||||
return rv;
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
template <class Key, class Resource>
|
||||
typename ResourceHandle<Key, Resource>::resource_ptr_type
|
||||
ResourceHandle<Key, Resource>::operator->() const
|
||||
{
|
||||
unique_lock<mutex> lock(s_ptr_mutex);
|
||||
if (!m_ptr) {
|
||||
THROW_ERROR(out_of_range, __PRETTY_FUNCTION__ << " called on null Resource");
|
||||
}
|
||||
// Make isolated copy of pointer with lock held, and return the copy
|
||||
auto rv = m_ptr;
|
||||
return rv;
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
template <class Key, class Resource>
|
||||
@@ -355,7 +313,6 @@ namespace crucible {
|
||||
shared_ptr<T>
|
||||
ResourceHandle<Key, Resource>::cast() const
|
||||
{
|
||||
unique_lock<mutex> lock(s_ptr_mutex);
|
||||
shared_ptr<T> dp;
|
||||
if (!m_ptr) {
|
||||
return dp;
|
||||
@@ -371,7 +328,6 @@ namespace crucible {
|
||||
typename ResourceHandle<Key, Resource>::key_type
|
||||
ResourceHandle<Key, Resource>::get_key() const
|
||||
{
|
||||
unique_lock<mutex> lock(s_ptr_mutex);
|
||||
if (!m_ptr) {
|
||||
return s_traits.get_null_key();
|
||||
} else {
|
||||
@@ -399,13 +355,9 @@ namespace crucible {
|
||||
template <class Key, class Resource>
|
||||
mutex ResourceHandle<Key, Resource>::s_map_mutex;
|
||||
|
||||
template <class Key, class Resource>
|
||||
mutex ResourceHandle<Key, Resource>::s_ptr_mutex;
|
||||
|
||||
template <class Key, class Resource>
|
||||
typename ResourceHandle<Key, Resource>::map_type ResourceHandle<Key, Resource>::s_map;
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif // RESOURCE_H
|
||||
|
11
lib/Makefile
11
lib/Makefile
@@ -1,8 +1,11 @@
|
||||
TAG := $(shell git describe --always --dirty || echo UNKNOWN)
|
||||
|
||||
default: libcrucible.so
|
||||
|
||||
OBJS = \
|
||||
crc64.o \
|
||||
chatter.o \
|
||||
cleanup.o \
|
||||
crc64.o \
|
||||
error.o \
|
||||
extentwalker.o \
|
||||
fd.o \
|
||||
@@ -22,13 +25,13 @@ depends.mk: *.cc
|
||||
mv -fv depends.mk.new depends.mk
|
||||
|
||||
.version.cc: Makefile ../makeflags *.cc ../include/crucible/*.h
|
||||
echo "namespace crucible { const char *VERSION = \"$(shell git describe --always --dirty || echo UNKNOWN)\"; }" > .version.new.cc
|
||||
echo "namespace crucible { const char *VERSION = \"$(TAG)\"; }" > .version.new.cc
|
||||
mv -f .version.new.cc .version.cc
|
||||
|
||||
-include depends.mk
|
||||
|
||||
%.o: %.cc ../include/crucible/%.h
|
||||
$(CXX) $(CXXFLAGS) -o $@ -c $<
|
||||
$(CXX) $(CXXFLAGS) -fPIC -o $@ -c $<
|
||||
|
||||
libcrucible.so: $(OBJS) Makefile
|
||||
$(CXX) $(LDFLAGS) -o $@ $(OBJS) -shared -luuid
|
||||
$(CXX) $(LDFLAGS) -fPIC -o $@ $(OBJS) -shared -Wl,-soname,$@ -luuid
|
||||
|
17
lib/cleanup.cc
Normal file
17
lib/cleanup.cc
Normal file
@@ -0,0 +1,17 @@
|
||||
#include <crucible/cleanup.h>
|
||||
|
||||
namespace crucible {
|
||||
|
||||
Cleanup::Cleanup(function<void()> func) :
|
||||
m_cleaner(func)
|
||||
{
|
||||
}
|
||||
|
||||
Cleanup::~Cleanup()
|
||||
{
|
||||
if (m_cleaner) {
|
||||
m_cleaner();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
23
lib/fd.cc
23
lib/fd.cc
@@ -527,6 +527,22 @@ namespace crucible {
|
||||
THROW_ERROR(runtime_error, "readlink: maximum buffer size exceeded");
|
||||
}
|
||||
|
||||
string
|
||||
relative_path()
|
||||
{
|
||||
return __relative_path;
|
||||
}
|
||||
|
||||
void
|
||||
set_relative_path(string path)
|
||||
{
|
||||
path = path + "/";
|
||||
for (string::size_type i = path.find("//"); i != string::npos; i = path.find("//")) {
|
||||
path.erase(i, 1);
|
||||
}
|
||||
__relative_path = path;
|
||||
}
|
||||
|
||||
// Turn a FD into a human-recognizable filename OR an error message.
|
||||
string
|
||||
name_fd(int fd)
|
||||
@@ -534,7 +550,12 @@ namespace crucible {
|
||||
try {
|
||||
ostringstream oss;
|
||||
oss << "/proc/self/fd/" << fd;
|
||||
return readlink_or_die(oss.str());
|
||||
string path = readlink_or_die(oss.str());
|
||||
if (!__relative_path.empty() && 0 == path.find(__relative_path))
|
||||
{
|
||||
path.erase(0, __relative_path.length());
|
||||
}
|
||||
return path;
|
||||
} catch (exception &e) {
|
||||
return string(e.what());
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
CCFLAGS = -Wall -Wextra -Werror -O3 -march=native -I../include -ggdb -fpic -D_FILE_OFFSET_BITS=64
|
||||
CCFLAGS = -Wall -Wextra -Werror -O3 -march=native -I../include -ggdb -D_FILE_OFFSET_BITS=64
|
||||
# CCFLAGS = -Wall -Wextra -Werror -O0 -I../include -ggdb -fpic -D_FILE_OFFSET_BITS=64
|
||||
CFLAGS = $(CCFLAGS) -std=c99
|
||||
CXXFLAGS = $(CCFLAGS) -std=c++11 -Wold-style-cast
|
||||
|
@@ -15,11 +15,8 @@ UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
# BEESHOME="$MNT_DIR/.beeshome"
|
||||
# BEESSTATUS="$WORK_DIR/$UUID.status"
|
||||
|
||||
## Make path shorter in logs
|
||||
# LOG_SHORT_PATH=N
|
||||
|
||||
## Remove timestamp from bees output
|
||||
# LOG_FILTER_TIME=N
|
||||
## Default options to apply, see --help for details
|
||||
# OPTIONS="--relative-paths --notimestamps"
|
||||
|
||||
## Bees DB size
|
||||
# Hash Table Sizing
|
||||
|
@@ -64,7 +64,6 @@ MNT_DIR="${MNT_DIR:-$WORK_DIR/mnt/$UUID}"
|
||||
BEESHOME="${BEESHOME:-$MNT_DIR/.beeshome}"
|
||||
BEESSTATUS="${BEESSTATUS:-$WORK_DIR/$UUID.status}"
|
||||
DB_SIZE="${DB_SIZE:-$((64*AL16M))}"
|
||||
LOG_SHORT_PATH="${LOG_SHORT_PATH:-N}"
|
||||
|
||||
INFO "Check: Disk exists"
|
||||
if [ ! -b "/dev/disk/by-uuid/$UUID" ]; then
|
||||
@@ -116,14 +115,5 @@ fi
|
||||
|
||||
MNT_DIR="${MNT_DIR//\/\//\/}"
|
||||
|
||||
filter_path(){
|
||||
if YN $LOG_SHORT_PATH; then
|
||||
sed -e "s#$MNT_DIR##g"
|
||||
else
|
||||
cat
|
||||
fi
|
||||
}
|
||||
|
||||
@LIBEXEC_PREFIX@/bees ${ARGUMENTS[@]} $OPTIONS "$MNT_DIR" 3>&1 2>&1 | filter_path
|
||||
|
||||
exit 0
|
||||
cd "$MNT_DIR"
|
||||
@LIBEXEC_PREFIX@/bees "${ARGUMENTS[@]}" $OPTIONS "$MNT_DIR"
|
||||
|
@@ -1,6 +1,7 @@
|
||||
[Unit]
|
||||
Description=Bees - Best-Effort Extent-Same, a btrfs deduplicator daemon: %i
|
||||
After=local-fs.target
|
||||
Description=Bees (%i)
|
||||
Documentation=https://github.com/Zygo/bees
|
||||
After=sysinit.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
@@ -21,4 +22,4 @@ CPUAccounting=true
|
||||
MemoryAccounting=true
|
||||
|
||||
[Install]
|
||||
WantedBy=local-fs.target
|
||||
WantedBy=basic.target
|
||||
|
@@ -8,7 +8,7 @@ all: $(PROGRAMS) depends.mk
|
||||
include ../makeflags
|
||||
|
||||
LIBS = -lcrucible -lpthread
|
||||
LDFLAGS = -L../lib -Wl,-rpath=$(shell realpath ../lib)
|
||||
LDFLAGS = -L../lib
|
||||
|
||||
depends.mk: Makefile *.cc
|
||||
for x in *.cc; do $(CXX) $(CXXFLAGS) -M "$$x"; done > depends.mk.new
|
||||
|
@@ -1,6 +1,7 @@
|
||||
#include "bees.h"
|
||||
|
||||
#include "crucible/limits.h"
|
||||
#include "crucible/process.h"
|
||||
#include "crucible/string.h"
|
||||
|
||||
#include <fstream>
|
||||
@@ -29,13 +30,14 @@ BeesFdCache::BeesFdCache()
|
||||
BEESCOUNTADD(open_root_ms, open_timer.age() * 1000);
|
||||
return rv;
|
||||
});
|
||||
m_root_cache.max_size(BEES_ROOT_FD_CACHE_SIZE);
|
||||
m_file_cache.func([&](shared_ptr<BeesContext> ctx, uint64_t root, uint64_t ino) -> Fd {
|
||||
Timer open_timer;
|
||||
auto rv = ctx->roots()->open_root_ino_nocache(root, ino);
|
||||
BEESCOUNTADD(open_ino_ms, open_timer.age() * 1000);
|
||||
return rv;
|
||||
});
|
||||
m_file_cache.max_size(BEES_FD_CACHE_SIZE);
|
||||
m_file_cache.max_size(BEES_FILE_FD_CACHE_SIZE);
|
||||
}
|
||||
|
||||
Fd
|
||||
@@ -158,6 +160,7 @@ BeesContext::home_fd()
|
||||
BeesContext::BeesContext(shared_ptr<BeesContext> parent) :
|
||||
m_parent_ctx(parent)
|
||||
{
|
||||
// m_extent_lock_set.max_size(bees_worker_thread_count());;
|
||||
if (m_parent_ctx) {
|
||||
m_fd_cache = m_parent_ctx->fd_cache();
|
||||
}
|
||||
@@ -166,19 +169,11 @@ BeesContext::BeesContext(shared_ptr<BeesContext> parent) :
|
||||
bool
|
||||
BeesContext::dedup(const BeesRangePair &brp)
|
||||
{
|
||||
// TOOLONG and NOTE can retroactively fill in the filename details, but LOG can't
|
||||
BEESNOTE("dedup " << brp);
|
||||
|
||||
// Open the files
|
||||
brp.first.fd(shared_from_this());
|
||||
brp.second.fd(shared_from_this());
|
||||
|
||||
#if 0
|
||||
// This avoids some sort of kernel race condition;
|
||||
// however, it also doubles our dedup times.
|
||||
// Is avoiding a crash every few weeks worth it?
|
||||
bees_sync(brp.first.fd());
|
||||
#endif
|
||||
|
||||
BEESNOTE("dedup " << brp);
|
||||
BEESTOOLONG("dedup " << brp);
|
||||
|
||||
BeesAddress first_addr(brp.first.fd(), brp.first.begin());
|
||||
@@ -725,6 +720,9 @@ BeesContext::scan_forward(const BeesFileRange &bfr)
|
||||
e = ew.current();
|
||||
|
||||
catch_all([&]() {
|
||||
uint64_t extent_bytenr = e.bytenr();
|
||||
BEESNOTE("waiting for extent bytenr " << to_hex(extent_bytenr));
|
||||
auto extent_lock = m_extent_lock_set.make_lock(extent_bytenr);
|
||||
Timer one_extent_timer;
|
||||
return_bfr = scan_one_extent(bfr, e);
|
||||
BEESCOUNTADD(scanf_extent_ms, one_extent_timer.age() * 1000);
|
||||
@@ -756,12 +754,19 @@ BeesContext::resolve_addr_uncached(BeesAddress addr)
|
||||
{
|
||||
THROW_CHECK1(invalid_argument, addr, !addr.is_magic());
|
||||
THROW_CHECK0(invalid_argument, !!root_fd());
|
||||
|
||||
// To avoid hammering all the cores with long-running ioctls,
|
||||
// only do one resolve at any given time.
|
||||
BEESNOTE("waiting to resolve addr " << addr);
|
||||
auto lock = bees_ioctl_lock_set.make_lock(gettid());
|
||||
|
||||
Timer resolve_timer;
|
||||
|
||||
// There is no performance benefit if we restrict the buffer size.
|
||||
BtrfsIoctlLogicalInoArgs log_ino(addr.get_physical_or_zero());
|
||||
|
||||
{
|
||||
BEESNOTE("resolving addr " << addr);
|
||||
BEESTOOLONG("Resolving addr " << addr << " in " << root_path() << " refs " << log_ino.m_iors.size());
|
||||
if (log_ino.do_ioctl_nothrow(root_fd())) {
|
||||
BEESCOUNT(resolve_ok);
|
||||
@@ -816,8 +821,9 @@ BeesContext::set_root_fd(Fd fd)
|
||||
m_root_uuid = fsinfo.uuid();
|
||||
BEESLOG("Filesystem UUID is " << m_root_uuid);
|
||||
|
||||
// 65536 is big enough for two max-sized extents
|
||||
m_resolve_cache.max_size(65536);
|
||||
// 65536 is big enough for two max-sized extents.
|
||||
// Need enough total space in the cache for the maximum number of active threads.
|
||||
m_resolve_cache.max_size(65536 * bees_worker_thread_count());
|
||||
m_resolve_cache.func([&](BeesAddress addr) -> BeesResolveAddrResult {
|
||||
return resolve_addr_uncached(addr);
|
||||
});
|
||||
|
223
src/bees-hash.cc
223
src/bees-hash.cc
@@ -24,14 +24,16 @@ operator<<(ostream &os, const BeesHashTable::Cell &bhte)
|
||||
<< BeesAddress(bhte.e_addr) << " }";
|
||||
}
|
||||
|
||||
#if 0
|
||||
static
|
||||
void
|
||||
dump_bucket(BeesHashTable::Cell *p, BeesHashTable::Cell *q)
|
||||
dump_bucket_locked(BeesHashTable::Cell *p, BeesHashTable::Cell *q)
|
||||
{
|
||||
// Must be called while holding m_bucket_mutex
|
||||
for (auto i = p; i < q; ++i) {
|
||||
BEESLOG("Entry " << i - p << " " << *i);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
const bool VERIFY_CLEARS_BUGS = false;
|
||||
|
||||
@@ -91,52 +93,74 @@ BeesHashTable::get_extent_range(HashType hash)
|
||||
return make_pair(bp, ep);
|
||||
}
|
||||
|
||||
bool
|
||||
BeesHashTable::flush_dirty_extent(uint64_t extent_index)
|
||||
{
|
||||
BEESNOTE("flushing extent #" << extent_index << " of " << m_extents << " extents");
|
||||
|
||||
auto lock = lock_extent_by_index(extent_index);
|
||||
|
||||
// Not dirty, nothing to do
|
||||
if (!m_extent_metadata.at(extent_index).m_dirty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool wrote_extent = false;
|
||||
|
||||
catch_all([&]() {
|
||||
uint8_t *dirty_extent = m_extent_ptr[extent_index].p_byte;
|
||||
uint8_t *dirty_extent_end = m_extent_ptr[extent_index + 1].p_byte;
|
||||
THROW_CHECK1(out_of_range, dirty_extent, dirty_extent >= m_byte_ptr);
|
||||
THROW_CHECK1(out_of_range, dirty_extent_end, dirty_extent_end <= m_byte_ptr_end);
|
||||
THROW_CHECK2(out_of_range, dirty_extent_end, dirty_extent, dirty_extent_end - dirty_extent == BLOCK_SIZE_HASHTAB_EXTENT);
|
||||
BEESTOOLONG("pwrite(fd " << m_fd << " '" << name_fd(m_fd)<< "', length " << to_hex(dirty_extent_end - dirty_extent) << ", offset " << to_hex(dirty_extent - m_byte_ptr) << ")");
|
||||
// Copy the extent because we might be stuck writing for a while
|
||||
vector<uint8_t> extent_copy(dirty_extent, dirty_extent_end);
|
||||
|
||||
// Mark extent non-dirty while we still hold the lock
|
||||
m_extent_metadata.at(extent_index).m_dirty = false;
|
||||
|
||||
// Release the lock
|
||||
lock.unlock();
|
||||
|
||||
// Write the extent (or not)
|
||||
pwrite_or_die(m_fd, extent_copy, dirty_extent - m_byte_ptr);
|
||||
BEESCOUNT(hash_extent_out);
|
||||
|
||||
wrote_extent = true;
|
||||
});
|
||||
|
||||
BEESNOTE("flush rate limited after extent #" << extent_index << " of " << m_extents << " extents");
|
||||
m_flush_rate_limit.sleep_for(BLOCK_SIZE_HASHTAB_EXTENT);
|
||||
return wrote_extent;
|
||||
}
|
||||
|
||||
void
|
||||
BeesHashTable::flush_dirty_extents()
|
||||
{
|
||||
THROW_CHECK1(runtime_error, m_buckets, m_buckets > 0);
|
||||
|
||||
unique_lock<mutex> lock(m_extent_mutex);
|
||||
auto dirty_extent_copy = m_buckets_dirty;
|
||||
m_buckets_dirty.clear();
|
||||
if (dirty_extent_copy.empty()) {
|
||||
BEESNOTE("idle");
|
||||
m_condvar.wait(lock);
|
||||
return; // please call later, i.e. immediately
|
||||
uint64_t wrote_extents = 0;
|
||||
for (size_t extent_index = 0; extent_index < m_extents; ++extent_index) {
|
||||
if (flush_dirty_extent(extent_index)) {
|
||||
++wrote_extents;
|
||||
}
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
size_t extent_counter = 0;
|
||||
for (auto extent_number : dirty_extent_copy) {
|
||||
++extent_counter;
|
||||
BEESNOTE("flush extent #" << extent_number << " (" << extent_counter << " of " << dirty_extent_copy.size() << ")");
|
||||
catch_all([&]() {
|
||||
uint8_t *dirty_extent = m_extent_ptr[extent_number].p_byte;
|
||||
uint8_t *dirty_extent_end = m_extent_ptr[extent_number + 1].p_byte;
|
||||
THROW_CHECK1(out_of_range, dirty_extent, dirty_extent >= m_byte_ptr);
|
||||
THROW_CHECK1(out_of_range, dirty_extent_end, dirty_extent_end <= m_byte_ptr_end);
|
||||
THROW_CHECK2(out_of_range, dirty_extent_end, dirty_extent, dirty_extent_end - dirty_extent == BLOCK_SIZE_HASHTAB_EXTENT);
|
||||
BEESTOOLONG("pwrite(fd " << m_fd << " '" << name_fd(m_fd)<< "', length " << to_hex(dirty_extent_end - dirty_extent) << ", offset " << to_hex(dirty_extent - m_byte_ptr) << ")");
|
||||
// Page locks slow us down more than copying the data does
|
||||
vector<uint8_t> extent_copy(dirty_extent, dirty_extent_end);
|
||||
pwrite_or_die(m_fd, extent_copy, dirty_extent - m_byte_ptr);
|
||||
BEESCOUNT(hash_extent_out);
|
||||
});
|
||||
BEESNOTE("flush rate limited at extent #" << extent_number << " (" << extent_counter << " of " << dirty_extent_copy.size() << ")");
|
||||
m_flush_rate_limit.sleep_for(BLOCK_SIZE_HASHTAB_EXTENT);
|
||||
}
|
||||
BEESNOTE("idle after writing " << wrote_extents << " of " << m_extents << " extents");
|
||||
unique_lock<mutex> lock(m_dirty_mutex);
|
||||
m_dirty_condvar.wait(lock);
|
||||
}
|
||||
|
||||
void
|
||||
BeesHashTable::set_extent_dirty(HashType hash)
|
||||
BeesHashTable::set_extent_dirty_locked(uint64_t extent_index)
|
||||
{
|
||||
THROW_CHECK1(runtime_error, m_buckets, m_buckets > 0);
|
||||
auto pr = get_extent_range(hash);
|
||||
uint64_t extent_number = reinterpret_cast<Extent *>(pr.first) - m_extent_ptr;
|
||||
THROW_CHECK1(runtime_error, extent_number, extent_number < m_extents);
|
||||
unique_lock<mutex> lock(m_extent_mutex);
|
||||
m_buckets_dirty.insert(extent_number);
|
||||
m_condvar.notify_one();
|
||||
// Must already be locked
|
||||
m_extent_metadata.at(extent_index).m_dirty = true;
|
||||
|
||||
// Signal writeback thread
|
||||
unique_lock<mutex> dirty_lock(m_dirty_mutex);
|
||||
m_dirty_condvar.notify_one();
|
||||
}
|
||||
|
||||
void
|
||||
@@ -179,13 +203,13 @@ BeesHashTable::prefetch_loop()
|
||||
size_t unaligned_eof_count = 0;
|
||||
|
||||
for (uint64_t ext = 0; ext < m_extents; ++ext) {
|
||||
BEESNOTE("prefetching hash table extent " << ext << " of " << m_extent_ptr_end - m_extent_ptr);
|
||||
BEESNOTE("prefetching hash table extent " << ext << " of " << m_extents);
|
||||
catch_all([&]() {
|
||||
fetch_missing_extent(ext * c_buckets_per_extent);
|
||||
fetch_missing_extent_by_index(ext);
|
||||
|
||||
BEESNOTE("analyzing hash table extent " << ext << " of " << m_extent_ptr_end - m_extent_ptr);
|
||||
BEESNOTE("analyzing hash table extent " << ext << " of " << m_extents);
|
||||
bool duplicate_bugs_found = false;
|
||||
unique_lock<mutex> lock(m_bucket_mutex);
|
||||
auto lock = lock_extent_by_index(ext);
|
||||
for (Bucket *bucket = m_extent_ptr[ext].p_buckets; bucket < m_extent_ptr[ext + 1].p_buckets; ++bucket) {
|
||||
if (verify_cell_range(bucket[0].p_cells, bucket[1].p_cells)) {
|
||||
duplicate_bugs_found = true;
|
||||
@@ -214,9 +238,8 @@ BeesHashTable::prefetch_loop()
|
||||
// Count these instead of calculating the number so we get better stats in case of exceptions
|
||||
occupied_count += this_bucket_occupied_count;
|
||||
}
|
||||
lock.unlock();
|
||||
if (duplicate_bugs_found) {
|
||||
set_extent_dirty(ext);
|
||||
set_extent_dirty_locked(ext);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -291,52 +314,70 @@ BeesHashTable::prefetch_loop()
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
BeesHashTable::fetch_missing_extent(HashType hash)
|
||||
size_t
|
||||
BeesHashTable::hash_to_extent_index(HashType hash)
|
||||
{
|
||||
auto pr = get_extent_range(hash);
|
||||
uint64_t extent_index = reinterpret_cast<const Extent *>(pr.first) - m_extent_ptr;
|
||||
THROW_CHECK2(runtime_error, extent_index, m_extents, extent_index < m_extents);
|
||||
return extent_index;
|
||||
}
|
||||
|
||||
BeesHashTable::ExtentMetaData::ExtentMetaData() :
|
||||
m_mutex_ptr(make_shared<mutex>())
|
||||
{
|
||||
}
|
||||
|
||||
unique_lock<mutex>
|
||||
BeesHashTable::lock_extent_by_index(uint64_t extent_index)
|
||||
{
|
||||
THROW_CHECK2(out_of_range, extent_index, m_extents, extent_index < m_extents);
|
||||
return unique_lock<mutex>(*m_extent_metadata.at(extent_index).m_mutex_ptr);
|
||||
}
|
||||
|
||||
unique_lock<mutex>
|
||||
BeesHashTable::lock_extent_by_hash(HashType hash)
|
||||
{
|
||||
BEESTOOLONG("fetch_missing_extent for hash " << to_hex(hash));
|
||||
THROW_CHECK1(runtime_error, m_buckets, m_buckets > 0);
|
||||
auto pr = get_extent_range(hash);
|
||||
uint64_t extent_number = reinterpret_cast<Extent *>(pr.first) - m_extent_ptr;
|
||||
THROW_CHECK1(runtime_error, extent_number, extent_number < m_extents);
|
||||
return lock_extent_by_index(hash_to_extent_index(hash));
|
||||
}
|
||||
|
||||
unique_lock<mutex> lock(m_extent_mutex);
|
||||
if (!m_buckets_missing.count(extent_number)) {
|
||||
void
|
||||
BeesHashTable::fetch_missing_extent_by_index(uint64_t extent_index)
|
||||
{
|
||||
BEESNOTE("checking hash extent #" << extent_index << " of " << m_extents << " extents");
|
||||
auto lock = lock_extent_by_index(extent_index);
|
||||
if (!m_extent_metadata.at(extent_index).m_missing) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t missing_buckets = m_buckets_missing.size();
|
||||
lock.unlock();
|
||||
|
||||
BEESNOTE("waiting to fetch hash extent #" << extent_number << ", " << missing_buckets << " left to fetch");
|
||||
|
||||
// Acquire blocking lock on this extent only
|
||||
auto extent_lock = m_extent_lock_set.make_lock(extent_number);
|
||||
|
||||
// Check missing again because someone else might have fetched this
|
||||
// extent for us while we didn't hold any locks
|
||||
lock.lock();
|
||||
if (!m_buckets_missing.count(extent_number)) {
|
||||
BEESCOUNT(hash_extent_in_twice);
|
||||
return;
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
// OK we have to read this extent
|
||||
BEESNOTE("fetching hash extent #" << extent_number << ", " << missing_buckets << " left to fetch");
|
||||
BEESNOTE("fetching hash extent #" << extent_index << " of " << m_extents << " extents");
|
||||
BEESTRACE("Fetching hash extent #" << extent_index << " of " << m_extents << " extents");
|
||||
BEESTOOLONG("Fetching hash extent #" << extent_index << " of " << m_extents << " extents");
|
||||
|
||||
BEESTRACE("Fetching missing hash extent " << extent_number);
|
||||
uint8_t *dirty_extent = m_extent_ptr[extent_number].p_byte;
|
||||
uint8_t *dirty_extent_end = m_extent_ptr[extent_number + 1].p_byte;
|
||||
uint8_t *dirty_extent = m_extent_ptr[extent_index].p_byte;
|
||||
uint8_t *dirty_extent_end = m_extent_ptr[extent_index + 1].p_byte;
|
||||
|
||||
{
|
||||
// If the read fails don't retry, just go with whatever data we have
|
||||
m_extent_metadata.at(extent_index).m_missing = false;
|
||||
|
||||
catch_all([&]() {
|
||||
BEESTOOLONG("pread(fd " << m_fd << " '" << name_fd(m_fd)<< "', length " << to_hex(dirty_extent_end - dirty_extent) << ", offset " << to_hex(dirty_extent - m_byte_ptr) << ")");
|
||||
pread_or_die(m_fd, dirty_extent, dirty_extent_end - dirty_extent, dirty_extent - m_byte_ptr);
|
||||
}
|
||||
});
|
||||
|
||||
// Only count extents successfully read
|
||||
BEESCOUNT(hash_extent_in);
|
||||
lock.lock();
|
||||
m_buckets_missing.erase(extent_number);
|
||||
}
|
||||
|
||||
void
|
||||
BeesHashTable::fetch_missing_extent_by_hash(HashType hash)
|
||||
{
|
||||
uint64_t extent_index = hash_to_extent_index(hash);
|
||||
BEESNOTE("waiting to fetch hash extent #" << extent_index << " of " << m_extents << " extents");
|
||||
|
||||
fetch_missing_extent_by_index(extent_index);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -358,10 +399,10 @@ BeesHashTable::find_cell(HashType hash)
|
||||
rv.push_back(toxic_cell);
|
||||
return rv;
|
||||
}
|
||||
fetch_missing_extent(hash);
|
||||
fetch_missing_extent_by_hash(hash);
|
||||
BEESTOOLONG("find_cell hash " << BeesHash(hash));
|
||||
vector<Cell> rv;
|
||||
unique_lock<mutex> lock(m_bucket_mutex);
|
||||
auto lock = lock_extent_by_hash(hash);
|
||||
auto er = get_cell_range(hash);
|
||||
// FIXME: Weed out zero addresses in the table due to earlier bugs
|
||||
copy_if(er.first, er.second, back_inserter(rv), [=](const Cell &ip) { return ip.e_hash == hash && ip.e_addr >= 0x1000; });
|
||||
@@ -377,9 +418,9 @@ BeesHashTable::find_cell(HashType hash)
|
||||
void
|
||||
BeesHashTable::erase_hash_addr(HashType hash, AddrType addr)
|
||||
{
|
||||
fetch_missing_extent(hash);
|
||||
fetch_missing_extent_by_hash(hash);
|
||||
BEESTOOLONG("erase hash " << to_hex(hash) << " addr " << addr);
|
||||
unique_lock<mutex> lock(m_bucket_mutex);
|
||||
auto lock = lock_extent_by_hash(hash);
|
||||
auto er = get_cell_range(hash);
|
||||
Cell mv(hash, addr);
|
||||
Cell *ip = find(er.first, er.second, mv);
|
||||
@@ -387,7 +428,7 @@ BeesHashTable::erase_hash_addr(HashType hash, AddrType addr)
|
||||
if (found) {
|
||||
// Lookups on invalid addresses really hurt us. Kill it with fire!
|
||||
*ip = Cell(0, 0);
|
||||
set_extent_dirty(hash);
|
||||
set_extent_dirty_locked(hash_to_extent_index(hash));
|
||||
BEESCOUNT(hash_erase);
|
||||
#if 0
|
||||
if (verify_cell_range(er.first, er.second)) {
|
||||
@@ -405,9 +446,9 @@ BeesHashTable::erase_hash_addr(HashType hash, AddrType addr)
|
||||
bool
|
||||
BeesHashTable::push_front_hash_addr(HashType hash, AddrType addr)
|
||||
{
|
||||
fetch_missing_extent(hash);
|
||||
fetch_missing_extent_by_hash(hash);
|
||||
BEESTOOLONG("push_front_hash_addr hash " << BeesHash(hash) <<" addr " << BeesAddress(addr));
|
||||
unique_lock<mutex> lock(m_bucket_mutex);
|
||||
auto lock = lock_extent_by_hash(hash);
|
||||
auto er = get_cell_range(hash);
|
||||
Cell mv(hash, addr);
|
||||
Cell *ip = find(er.first, er.second, mv);
|
||||
@@ -437,7 +478,7 @@ BeesHashTable::push_front_hash_addr(HashType hash, AddrType addr)
|
||||
// There is now a space at the front, insert there if different
|
||||
if (er.first[0] != mv) {
|
||||
er.first[0] = mv;
|
||||
set_extent_dirty(hash);
|
||||
set_extent_dirty_locked(hash_to_extent_index(hash));
|
||||
BEESCOUNT(hash_front);
|
||||
}
|
||||
#if 0
|
||||
@@ -456,9 +497,9 @@ BeesHashTable::push_front_hash_addr(HashType hash, AddrType addr)
|
||||
bool
|
||||
BeesHashTable::push_random_hash_addr(HashType hash, AddrType addr)
|
||||
{
|
||||
fetch_missing_extent(hash);
|
||||
fetch_missing_extent_by_hash(hash);
|
||||
BEESTOOLONG("push_random_hash_addr hash " << BeesHash(hash) << " addr " << BeesAddress(addr));
|
||||
unique_lock<mutex> lock(m_bucket_mutex);
|
||||
auto lock = lock_extent_by_hash(hash);
|
||||
auto er = get_cell_range(hash);
|
||||
Cell mv(hash, addr);
|
||||
Cell *ip = find(er.first, er.second, mv);
|
||||
@@ -521,14 +562,14 @@ BeesHashTable::push_random_hash_addr(HashType hash, AddrType addr)
|
||||
case_cond = 5;
|
||||
ret_dirty:
|
||||
BEESCOUNT(hash_insert);
|
||||
set_extent_dirty(hash);
|
||||
set_extent_dirty_locked(hash_to_extent_index(hash));
|
||||
ret:
|
||||
#if 0
|
||||
if (verify_cell_range(er.first, er.second, false)) {
|
||||
BEESLOG("while push_randoming (case " << case_cond << ") pos " << pos
|
||||
<< " ip " << (ip - er.first) << " " << mv);
|
||||
// dump_bucket(saved.data(), saved.data() + saved.size());
|
||||
// dump_bucket(er.first, er.second);
|
||||
// dump_bucket_locked(saved.data(), saved.data() + saved.size());
|
||||
// dump_bucket_locked(er.first, er.second);
|
||||
}
|
||||
#else
|
||||
(void)case_cond;
|
||||
@@ -652,9 +693,7 @@ BeesHashTable::BeesHashTable(shared_ptr<BeesContext> ctx, string filename, off_t
|
||||
}
|
||||
}
|
||||
|
||||
for (uint64_t i = 0; i < m_size / sizeof(Extent); ++i) {
|
||||
m_buckets_missing.insert(i);
|
||||
}
|
||||
m_extent_metadata.resize(m_extents);
|
||||
|
||||
m_writeback_thread.exec([&]() {
|
||||
writeback_loop();
|
||||
|
@@ -1,11 +1,14 @@
|
||||
#include "bees.h"
|
||||
|
||||
#include "crucible/cache.h"
|
||||
#include "crucible/process.h"
|
||||
#include "crucible/string.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <tuple>
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
using namespace crucible;
|
||||
using namespace std;
|
||||
|
||||
@@ -150,9 +153,12 @@ BeesRoots::crawl_state_erase(const BeesCrawlState &bcs)
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_root_crawl_map.count(bcs.m_root)) {
|
||||
m_root_crawl_map.erase(bcs.m_root);
|
||||
auto found = m_root_crawl_map.find(bcs.m_root);
|
||||
if (found != m_root_crawl_map.end()) {
|
||||
auto hold_this_until_unlocked = found->second;
|
||||
m_root_crawl_map.erase(found);
|
||||
m_crawl_dirty = true;
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,97 +197,12 @@ BeesRoots::transid_max()
|
||||
return rv;
|
||||
}
|
||||
|
||||
void
|
||||
BeesRoots::crawl_roots()
|
||||
{
|
||||
BEESNOTE("Crawling roots");
|
||||
|
||||
unique_lock<mutex> lock(m_mutex);
|
||||
if (m_root_crawl_map.empty()) {
|
||||
BEESNOTE("idle, crawl map is empty");
|
||||
m_condvar.wait(lock);
|
||||
// Don't count the time we were waiting as part of the crawl time
|
||||
m_crawl_timer.reset();
|
||||
}
|
||||
|
||||
// Work from a copy because BeesCrawl might change the world under us
|
||||
auto crawl_map_copy = m_root_crawl_map;
|
||||
lock.unlock();
|
||||
|
||||
#if 0
|
||||
// Scan the same inode/offset tuple in each subvol (good for snapshots)
|
||||
BeesFileRange first_range;
|
||||
shared_ptr<BeesCrawl> first_crawl;
|
||||
for (auto i : crawl_map_copy) {
|
||||
auto this_crawl = i.second;
|
||||
auto this_range = this_crawl->peek_front();
|
||||
if (this_range) {
|
||||
if (!first_range || this_range < first_range) {
|
||||
first_crawl = this_crawl;
|
||||
first_range = this_range;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (first_range) {
|
||||
catch_all([&]() {
|
||||
// BEESINFO("scan_forward " << first_range);
|
||||
m_ctx->scan_forward(first_range);
|
||||
});
|
||||
BEESCOUNT(crawl_scan);
|
||||
m_crawl_current = first_crawl->get_state();
|
||||
auto first_range_popped = first_crawl->pop_front();
|
||||
THROW_CHECK2(runtime_error, first_range, first_range_popped, first_range == first_range_popped);
|
||||
return;
|
||||
}
|
||||
#else
|
||||
// Scan each subvol one extent at a time (good for continuous forward progress)
|
||||
bool crawled = false;
|
||||
for (auto i : crawl_map_copy) {
|
||||
auto this_crawl = i.second;
|
||||
auto this_range = this_crawl->peek_front();
|
||||
if (this_range) {
|
||||
catch_all([&]() {
|
||||
// BEESINFO("scan_forward " << this_range);
|
||||
m_ctx->scan_forward(this_range);
|
||||
});
|
||||
crawled = true;
|
||||
BEESCOUNT(crawl_scan);
|
||||
m_crawl_current = this_crawl->get_state();
|
||||
auto this_range_popped = this_crawl->pop_front();
|
||||
THROW_CHECK2(runtime_error, this_range, this_range_popped, this_range == this_range_popped);
|
||||
}
|
||||
}
|
||||
|
||||
if (crawled) return;
|
||||
#endif
|
||||
|
||||
BEESLOG("Crawl ran out of data after " << m_crawl_timer.lap() << "s, waiting for more...");
|
||||
BEESCOUNT(crawl_done);
|
||||
BEESNOTE("idle, waiting for more data");
|
||||
lock.lock();
|
||||
m_condvar.wait(lock);
|
||||
|
||||
// Don't count the time we were waiting as part of the crawl time
|
||||
m_crawl_timer.reset();
|
||||
}
|
||||
|
||||
void
|
||||
BeesRoots::crawl_thread()
|
||||
{
|
||||
BEESNOTE("crawling");
|
||||
while (1) {
|
||||
catch_all([&]() {
|
||||
crawl_roots();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
BeesRoots::writeback_thread()
|
||||
{
|
||||
while (1) {
|
||||
BEESNOTE(m_crawl_current << (m_crawl_dirty ? " (dirty)" : ""));
|
||||
while (true) {
|
||||
// BEESNOTE(m_crawl_current << (m_crawl_dirty ? " (dirty)" : ""));
|
||||
BEESNOTE((m_crawl_dirty ? "dirty" : "clean") << ", interval " << BEES_WRITEBACK_INTERVAL << "s");
|
||||
|
||||
catch_all([&]() {
|
||||
BEESNOTE("saving crawler state");
|
||||
@@ -379,17 +300,16 @@ BeesRoots::state_load()
|
||||
BeesRoots::BeesRoots(shared_ptr<BeesContext> ctx) :
|
||||
m_ctx(ctx),
|
||||
m_crawl_state_file(ctx->home_fd(), crawl_state_filename()),
|
||||
m_crawl_thread("crawl"),
|
||||
m_writeback_thread("crawl_writeback")
|
||||
{
|
||||
m_crawl_thread.exec([&]() {
|
||||
catch_all([&]() {
|
||||
state_load();
|
||||
});
|
||||
m_writeback_thread.exec([&]() {
|
||||
writeback_thread();
|
||||
});
|
||||
crawl_thread();
|
||||
// This is a sanity check to prevent us from running out of FDs
|
||||
m_lock_set.max_size(BEES_WORKER_THREAD_LIMIT);
|
||||
|
||||
catch_all([&]() {
|
||||
state_load();
|
||||
});
|
||||
m_writeback_thread.exec([&]() {
|
||||
writeback_thread();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -397,6 +317,7 @@ Fd
|
||||
BeesRoots::open_root_nocache(uint64_t rootid)
|
||||
{
|
||||
BEESTRACE("open_root_nocache " << rootid);
|
||||
BEESNOTE("open_root_nocache " << rootid);
|
||||
|
||||
// Stop recursion at the root of the filesystem tree
|
||||
if (rootid == BTRFS_FS_TREE_OBJECTID) {
|
||||
@@ -469,7 +390,7 @@ BeesRoots::open_root_nocache(uint64_t rootid)
|
||||
THROW_CHECK2(runtime_error, new_root_id, rootid, new_root_id == rootid);
|
||||
Stat st(rv);
|
||||
THROW_CHECK1(runtime_error, st.st_ino, st.st_ino == BTRFS_FIRST_FREE_OBJECTID);
|
||||
BEESINFO("open_root_nocache " << rootid << ": " << name_fd(rv));
|
||||
// BEESINFO("open_root_nocache " << rootid << ": " << name_fd(rv));
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
@@ -632,8 +553,54 @@ BeesRoots::open_root_ino(uint64_t root, uint64_t ino)
|
||||
|
||||
BeesCrawl::BeesCrawl(shared_ptr<BeesContext> ctx, BeesCrawlState initial_state) :
|
||||
m_ctx(ctx),
|
||||
m_state(initial_state)
|
||||
m_state(initial_state),
|
||||
m_thread(astringprintf("crawl_%" PRIu64, m_state.m_root))
|
||||
{
|
||||
m_thread.exec([&]() {
|
||||
crawl_thread();
|
||||
});
|
||||
}
|
||||
|
||||
BeesCrawl::~BeesCrawl()
|
||||
{
|
||||
BEESLOGNOTE("Stopping crawl thread " << m_state);
|
||||
unique_lock<mutex> lock(m_mutex);
|
||||
m_stopped = true;
|
||||
m_cond_stopped.notify_all();
|
||||
lock.unlock();
|
||||
BEESLOGNOTE("Joining crawl thread " << m_state);
|
||||
m_thread.join();
|
||||
BEESLOG("Stopped crawl thread " << m_state);
|
||||
}
|
||||
|
||||
void
|
||||
BeesCrawl::crawl_thread()
|
||||
{
|
||||
Timer crawl_timer;
|
||||
while (!m_stopped) {
|
||||
BEESNOTE("pop_front " << m_state);
|
||||
auto this_range = pop_front();
|
||||
if (this_range) {
|
||||
catch_all([&]() {
|
||||
BEESNOTE("waiting for scan thread limit " << m_state);
|
||||
auto crawl_lock = m_ctx->roots()->lock_set().make_lock(m_state.m_root);
|
||||
|
||||
BEESNOTE("scan_forward " << this_range);
|
||||
m_ctx->scan_forward(this_range);
|
||||
});
|
||||
BEESCOUNT(crawl_scan);
|
||||
} else {
|
||||
auto crawl_time = crawl_timer.age();
|
||||
BEESLOGNOTE("Crawl ran out of data after " << crawl_time << "s, waiting for more...");
|
||||
unique_lock<mutex> lock(m_mutex);
|
||||
if (m_stopped) {
|
||||
break;
|
||||
}
|
||||
m_cond_stopped.wait_for(lock, chrono::duration<double>(BEES_COMMIT_INTERVAL));
|
||||
crawl_timer.reset();
|
||||
}
|
||||
}
|
||||
BEESLOG("Crawl thread stopped");
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -681,7 +648,7 @@ BeesCrawl::fetch_extents()
|
||||
}
|
||||
|
||||
BEESNOTE("crawling " << get_state());
|
||||
BEESLOG("Crawling " << get_state());
|
||||
// BEESLOG("Crawling " << get_state());
|
||||
|
||||
Timer crawl_timer;
|
||||
|
||||
@@ -700,6 +667,11 @@ BeesCrawl::fetch_extents()
|
||||
BEESTRACE("Searching crawl sk " << static_cast<btrfs_ioctl_search_key&>(sk));
|
||||
bool ioctl_ok = false;
|
||||
{
|
||||
#if 0
|
||||
BEESNOTE("waiting to search crawl sk " << static_cast<btrfs_ioctl_search_key&>(sk));
|
||||
auto lock = bees_ioctl_lock_set.make_lock(gettid());
|
||||
#endif
|
||||
|
||||
BEESNOTE("searching crawl sk " << static_cast<btrfs_ioctl_search_key&>(sk));
|
||||
BEESTOOLONG("Searching crawl sk " << static_cast<btrfs_ioctl_search_key&>(sk));
|
||||
Timer crawl_timer;
|
||||
@@ -720,7 +692,7 @@ BeesCrawl::fetch_extents()
|
||||
return next_transid();
|
||||
}
|
||||
|
||||
BEESLOG("Crawling " << sk.m_result.size() << " results from " << get_state());
|
||||
// BEESLOG("Crawling " << sk.m_result.size() << " results from " << get_state());
|
||||
auto results_left = sk.m_result.size();
|
||||
BEESNOTE("crawling " << results_left << " results from " << get_state());
|
||||
size_t count_other = 0;
|
||||
@@ -737,7 +709,6 @@ BeesCrawl::fetch_extents()
|
||||
|
||||
BEESTRACE("i = " << i);
|
||||
|
||||
#if 1
|
||||
// We need the "+ 1" and objectid rollover that next_min does.
|
||||
auto new_state = get_state();
|
||||
new_state.m_objectid = sk.min_objectid;
|
||||
@@ -749,7 +720,6 @@ BeesCrawl::fetch_extents()
|
||||
// is a lot of metadata we can't process. Favor forward
|
||||
// progress over losing search results.
|
||||
set_state(new_state);
|
||||
#endif
|
||||
|
||||
// Ignore things that aren't EXTENT_DATA_KEY
|
||||
if (i.type != BTRFS_EXTENT_DATA_KEY) {
|
||||
@@ -762,13 +732,24 @@ BeesCrawl::fetch_extents()
|
||||
if (gen < get_state().m_min_transid) {
|
||||
BEESCOUNT(crawl_gen_low);
|
||||
++count_low;
|
||||
// We probably want (need?) to scan these anyway.
|
||||
// continue;
|
||||
// We want (need?) to scan these anyway?
|
||||
// The header generation refers to the transid
|
||||
// of the metadata page holding the current ref.
|
||||
// This includes anything else in that page that
|
||||
// happened to be modified, regardless of how
|
||||
// old it is.
|
||||
// The file_extent_generation refers to the
|
||||
// transid of the extent item's page, which is
|
||||
// a different approximation of what we want.
|
||||
// Combine both of these filters to minimize
|
||||
// the number of times we unnecessarily re-read
|
||||
// an extent.
|
||||
continue;
|
||||
}
|
||||
if (gen > get_state().m_max_transid) {
|
||||
BEESCOUNT(crawl_gen_high);
|
||||
++count_high;
|
||||
// This shouldn't ever happen
|
||||
// This shouldn't ever happen...and so far, doesn't.
|
||||
// continue;
|
||||
}
|
||||
|
||||
@@ -818,7 +799,7 @@ BeesCrawl::fetch_extents()
|
||||
}
|
||||
}
|
||||
}
|
||||
BEESLOG("Crawled inline " << count_inline << " data " << count_data << " other " << count_other << " unknown " << count_unknown << " gen_low " << count_low << " gen_high " << count_high << " " << get_state() << " in " << crawl_timer << "s");
|
||||
// BEESLOG("Crawled inline " << count_inline << " data " << count_data << " other " << count_other << " unknown " << count_unknown << " gen_low " << count_low << " gen_high " << count_high << " " << get_state() << " in " << crawl_timer << "s");
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -857,12 +838,6 @@ BeesCrawl::pop_front()
|
||||
}
|
||||
auto rv = *m_extents.begin();
|
||||
m_extents.erase(m_extents.begin());
|
||||
#if 0
|
||||
auto state = get_state();
|
||||
state.m_objectid = rv.fid().ino();
|
||||
state.m_offset = rv.begin();
|
||||
set_state(state);
|
||||
#endif
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
57
src/bees.cc
57
src/bees.cc
@@ -19,6 +19,10 @@
|
||||
#include <linux/fs.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
// setrlimit
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
#include <getopt.h>
|
||||
|
||||
using namespace crucible;
|
||||
@@ -37,6 +41,8 @@ do_cmd_help(char *argv[])
|
||||
"\t-h, --help\t\tShow this help\n"
|
||||
"\t-t, --timestamps\tShow timestamps in log output (default)\n"
|
||||
"\t-T, --notimestamps\tOmit timestamps in log output\n"
|
||||
"\t-p, --absolute-paths\tShow absolute paths (default)\n"
|
||||
"\t-P, --relative-paths\tShow paths relative to $CWD\n"
|
||||
"\n"
|
||||
"Optional environment variables:\n"
|
||||
"\tBEESHOME\tPath to hash table and configuration files\n"
|
||||
@@ -211,6 +217,13 @@ operator<<(ostream &os, const BeesStatTmpl<T> &bs)
|
||||
|
||||
// other ----------------------------------------
|
||||
|
||||
/**
|
||||
* Don't allow two threads to use some btrfs ioctls at the same time.
|
||||
* Some of them consume egregious amounts of kernel CPU time and are
|
||||
* not interruptible, so if we have more threads than cores we will
|
||||
* effectively crash the kernel. */
|
||||
LockSet<pid_t> bees_ioctl_lock_set;
|
||||
|
||||
template <class T>
|
||||
T&
|
||||
BeesStatTmpl<T>::at(string idx)
|
||||
@@ -581,6 +594,13 @@ BeesTempFile::make_copy(const BeesFileRange &src)
|
||||
return rv;
|
||||
}
|
||||
|
||||
unsigned
|
||||
bees_worker_thread_count()
|
||||
{
|
||||
// Maybe # of cores * (scalar from 0.25..4)?
|
||||
return max(1U, thread::hardware_concurrency() * 4);
|
||||
}
|
||||
|
||||
int
|
||||
bees_main(int argc, char *argv[])
|
||||
{
|
||||
@@ -597,6 +617,8 @@ bees_main(int argc, char *argv[])
|
||||
|
||||
THROW_CHECK1(invalid_argument, argc, argc >= 0);
|
||||
|
||||
string cwd(readlink_or_die("/proc/self/cwd"));
|
||||
|
||||
// Defaults
|
||||
bool chatter_prefix_timestamp = true;
|
||||
|
||||
@@ -605,12 +627,14 @@ bees_main(int argc, char *argv[])
|
||||
while (1) {
|
||||
int option_index = 0;
|
||||
static struct option long_options[] = {
|
||||
{ "timestamps", no_argument, NULL, 't' },
|
||||
{ "notimestamps", no_argument, NULL, 'T' },
|
||||
{ "help", no_argument, NULL, 'h' }
|
||||
{ "timestamps", no_argument, NULL, 't' },
|
||||
{ "notimestamps", no_argument, NULL, 'T' },
|
||||
{ "absolute-paths", no_argument, NULL, 'p' },
|
||||
{ "relative-paths", no_argument, NULL, 'P' },
|
||||
{ "help", no_argument, NULL, 'h' }
|
||||
};
|
||||
|
||||
c = getopt_long(argc, argv, "Tth", long_options, &option_index);
|
||||
c = getopt_long(argc, argv, "TtPph", long_options, &option_index);
|
||||
if (-1 == c) {
|
||||
break;
|
||||
}
|
||||
@@ -622,6 +646,12 @@ bees_main(int argc, char *argv[])
|
||||
case 't':
|
||||
chatter_prefix_timestamp = true;
|
||||
break;
|
||||
case 'P':
|
||||
crucible::set_relative_path(cwd);
|
||||
break;
|
||||
case 'p':
|
||||
crucible::set_relative_path("");
|
||||
break;
|
||||
case 'h':
|
||||
do_cmd_help(argv); // fallthrough
|
||||
default:
|
||||
@@ -631,6 +661,25 @@ bees_main(int argc, char *argv[])
|
||||
|
||||
Chatter::enable_timestamp(chatter_prefix_timestamp);
|
||||
|
||||
if (!relative_path().empty()) {
|
||||
BEESLOG("using relative path " << relative_path() << "\n");
|
||||
}
|
||||
|
||||
// There can be only one because we measure running time with it
|
||||
// EXPERIMENT: don't try this on kernels before v4.14
|
||||
// bees_ioctl_lock_set.max_size(1);
|
||||
|
||||
BEESLOG("setting rlimit NOFILE to " << BEES_OPEN_FILE_LIMIT);
|
||||
|
||||
struct rlimit lim = {
|
||||
.rlim_cur = BEES_OPEN_FILE_LIMIT,
|
||||
.rlim_max = BEES_OPEN_FILE_LIMIT,
|
||||
};
|
||||
int rv = setrlimit(RLIMIT_NOFILE, &lim);
|
||||
if (rv) {
|
||||
BEESLOG("setrlimit(RLIMIT_NOFILE, { " << lim.rlim_cur << " }): " << strerror(errno));
|
||||
};
|
||||
|
||||
// Create a context and start crawlers
|
||||
bool did_subscription = false;
|
||||
while (optind < argc) {
|
||||
|
63
src/bees.h
63
src/bees.h
@@ -75,14 +75,25 @@ const int BEES_PROGRESS_INTERVAL = BEES_STATS_INTERVAL;
|
||||
// Status is output every freakin second. Use a ramdisk.
|
||||
const int BEES_STATUS_INTERVAL = 1;
|
||||
|
||||
// Number of FDs to open (not counting 100 roots)
|
||||
const size_t BEES_FD_CACHE_SIZE = 384;
|
||||
// Number of file FDs to cache when not in active use
|
||||
const size_t BEES_FILE_FD_CACHE_SIZE = 4096;
|
||||
|
||||
// Number of root FDs to cache when not in active use
|
||||
const size_t BEES_ROOT_FD_CACHE_SIZE = 1024;
|
||||
|
||||
// Number of FDs to open (rlimit)
|
||||
const size_t BEES_OPEN_FILE_LIMIT = (BEES_FILE_FD_CACHE_SIZE + BEES_ROOT_FD_CACHE_SIZE) * 2 + 100;
|
||||
|
||||
// Worker thread limit (more threads may be created, but only this number will be active concurrently)
|
||||
const size_t BEES_WORKER_THREAD_LIMIT = 128;
|
||||
|
||||
// Log warnings when an operation takes too long
|
||||
const double BEES_TOO_LONG = 2.5;
|
||||
|
||||
// Avoid any extent where LOGICAL_INO takes this long
|
||||
const double BEES_TOXIC_DURATION = 9.9;
|
||||
// const double BEES_TOXIC_DURATION = 9.9;
|
||||
// EXPERIMENT: Kernel v4.14+ may let us ignore toxicity
|
||||
const double BEES_TOXIC_DURATION = BEES_COMMIT_INTERVAL;
|
||||
|
||||
// How long between hash table histograms
|
||||
const double BEES_HASH_TABLE_ANALYZE_INTERVAL = BEES_STATS_INTERVAL;
|
||||
@@ -95,7 +106,7 @@ const double BEES_INFO_BURST = 1.0;
|
||||
const size_t BEES_MAX_QUEUE_SIZE = 1024;
|
||||
|
||||
// Read this many items at a time in SEARCHv2
|
||||
const size_t BEES_MAX_CRAWL_SIZE = 4096;
|
||||
const size_t BEES_MAX_CRAWL_SIZE = 1024;
|
||||
|
||||
// If an extent has this many refs, pretend it does not exist
|
||||
// to avoid a crippling btrfs performance bug
|
||||
@@ -432,18 +443,24 @@ private:
|
||||
uint64_t m_buckets;
|
||||
uint64_t m_extents;
|
||||
uint64_t m_cells;
|
||||
set<uint64_t> m_buckets_dirty;
|
||||
set<uint64_t> m_buckets_missing;
|
||||
BeesThread m_writeback_thread;
|
||||
BeesThread m_prefetch_thread;
|
||||
RateLimiter m_flush_rate_limit;
|
||||
mutex m_extent_mutex;
|
||||
mutex m_bucket_mutex;
|
||||
condition_variable m_condvar;
|
||||
set<HashType> m_toxic_hashes;
|
||||
BeesStringFile m_stats_file;
|
||||
|
||||
LockSet<uint64_t> m_extent_lock_set;
|
||||
// Mutex/condvar for the writeback thread
|
||||
mutex m_dirty_mutex;
|
||||
condition_variable m_dirty_condvar;
|
||||
|
||||
// Per-extent structures
|
||||
struct ExtentMetaData {
|
||||
shared_ptr<mutex> m_mutex_ptr; // Access serializer
|
||||
bool m_dirty = false; // Needs to be written back to disk
|
||||
bool m_missing = true; // Needs to be read from disk
|
||||
ExtentMetaData();
|
||||
};
|
||||
vector<ExtentMetaData> m_extent_metadata;
|
||||
|
||||
void open_file();
|
||||
void writeback_loop();
|
||||
@@ -451,11 +468,17 @@ private:
|
||||
void try_mmap_flags(int flags);
|
||||
pair<Cell *, Cell *> get_cell_range(HashType hash);
|
||||
pair<uint8_t *, uint8_t *> get_extent_range(HashType hash);
|
||||
void fetch_missing_extent(HashType hash);
|
||||
void set_extent_dirty(HashType hash);
|
||||
void fetch_missing_extent_by_hash(HashType hash);
|
||||
void fetch_missing_extent_by_index(uint64_t extent_index);
|
||||
void set_extent_dirty_locked(uint64_t extent_index);
|
||||
void flush_dirty_extents();
|
||||
bool flush_dirty_extent(uint64_t extent_index);
|
||||
bool is_toxic_hash(HashType h) const;
|
||||
|
||||
size_t hash_to_extent_index(HashType ht);
|
||||
unique_lock<mutex> lock_extent_by_hash(HashType ht);
|
||||
unique_lock<mutex> lock_extent_by_index(uint64_t extent_index);
|
||||
|
||||
BeesHashTable(const BeesHashTable &) = delete;
|
||||
BeesHashTable &operator=(const BeesHashTable &) = delete;
|
||||
};
|
||||
@@ -483,30 +506,35 @@ class BeesCrawl {
|
||||
mutex m_state_mutex;
|
||||
BeesCrawlState m_state;
|
||||
|
||||
BeesThread m_thread;
|
||||
bool m_stopped = false;
|
||||
condition_variable m_cond_stopped;
|
||||
|
||||
bool fetch_extents();
|
||||
void fetch_extents_harder();
|
||||
bool next_transid();
|
||||
|
||||
public:
|
||||
~BeesCrawl();
|
||||
BeesCrawl(shared_ptr<BeesContext> ctx, BeesCrawlState initial_state);
|
||||
BeesFileRange peek_front();
|
||||
BeesFileRange pop_front();
|
||||
BeesCrawlState get_state();
|
||||
void set_state(const BeesCrawlState &bcs);
|
||||
void crawl_thread();
|
||||
};
|
||||
|
||||
class BeesRoots {
|
||||
shared_ptr<BeesContext> m_ctx;
|
||||
|
||||
BeesStringFile m_crawl_state_file;
|
||||
BeesCrawlState m_crawl_current;
|
||||
map<uint64_t, shared_ptr<BeesCrawl>> m_root_crawl_map;
|
||||
mutex m_mutex;
|
||||
condition_variable m_condvar;
|
||||
bool m_crawl_dirty = false;
|
||||
Timer m_crawl_timer;
|
||||
BeesThread m_crawl_thread;
|
||||
BeesThread m_writeback_thread;
|
||||
LockSet<uint64_t> m_lock_set;
|
||||
|
||||
void insert_new_crawl();
|
||||
void insert_root(const BeesCrawlState &bcs);
|
||||
@@ -521,7 +549,6 @@ class BeesRoots {
|
||||
BeesCrawlState crawl_state_get(uint64_t root);
|
||||
void crawl_state_set_dirty();
|
||||
void crawl_state_erase(const BeesCrawlState &bcs);
|
||||
void crawl_thread();
|
||||
void writeback_thread();
|
||||
uint64_t next_root(uint64_t root = 0);
|
||||
void current_state_set(const BeesCrawlState &bcs);
|
||||
@@ -534,6 +561,7 @@ public:
|
||||
Fd open_root(uint64_t root);
|
||||
Fd open_root_ino(uint64_t root, uint64_t ino);
|
||||
Fd open_root_ino(const BeesFileId &bfi) { return open_root_ino(bfi.root(), bfi.ino()); }
|
||||
LockSet<uint64_t> &lock_set() { return m_lock_set; }
|
||||
};
|
||||
|
||||
struct BeesHash {
|
||||
@@ -656,6 +684,8 @@ class BeesContext : public enable_shared_from_this<BeesContext> {
|
||||
|
||||
Timer m_total_timer;
|
||||
|
||||
LockSet<uint64_t> m_extent_lock_set;
|
||||
|
||||
void set_root_fd(Fd fd);
|
||||
|
||||
BeesResolveAddrResult resolve_addr_uncached(BeesAddress addr);
|
||||
@@ -693,6 +723,7 @@ public:
|
||||
shared_ptr<BeesTempFile> tmpfile();
|
||||
|
||||
const Timer &total_timer() const { return m_total_timer; }
|
||||
LockSet<uint64_t> &extent_lock_set() { return m_extent_lock_set; }
|
||||
|
||||
// TODO: move the rest of the FD cache methods here
|
||||
void insert_root_ino(Fd fd);
|
||||
@@ -780,5 +811,7 @@ string pretty(double d);
|
||||
extern RateLimiter bees_info_rate_limit;
|
||||
void bees_sync(int fd);
|
||||
string format_time(time_t t);
|
||||
extern LockSet<pid_t> bees_ioctl_lock_set;
|
||||
extern unsigned bees_worker_thread_count();
|
||||
|
||||
#endif
|
||||
|
Reference in New Issue
Block a user