Set meaningful thread names for debugging purposes

This commit is contained in:
Sebastian Messmer 2019-01-20 03:25:21 -08:00
parent 29f7f06ca9
commit 8d09fb4c46
18 changed files with 84 additions and 31 deletions

View File

@ -45,7 +45,7 @@ void CachingBlockStore2::CachedBlock::write(Data data) {
}
CachingBlockStore2::CachingBlockStore2(cpputils::unique_ref<BlockStore2> baseBlockStore)
: _baseBlockStore(std::move(baseBlockStore)), _cachedBlocksNotInBaseStoreMutex(), _cachedBlocksNotInBaseStore(), _cache() {
: _baseBlockStore(std::move(baseBlockStore)), _cachedBlocksNotInBaseStoreMutex(), _cachedBlocksNotInBaseStore(), _cache("blockstore") {
}
bool CachingBlockStore2::tryCreate(const BlockId &blockId, const Data &data) {

View File

@ -24,7 +24,7 @@ public:
static constexpr double PURGE_INTERVAL = 0.5; // With this interval, we check for entries to purge
static constexpr double MAX_LIFETIME_SEC = PURGE_LIFETIME_SEC + PURGE_INTERVAL; // This is the oldest age an entry can reach (given purging works in an ideal world, i.e. with the ideal interval and in zero time)
Cache();
Cache(const std::string& cacheName);
~Cache();
uint32_t size() const;
@ -56,10 +56,10 @@ template<class Key, class Value, uint32_t MAX_ENTRIES> constexpr double Cache<Ke
template<class Key, class Value, uint32_t MAX_ENTRIES> constexpr double Cache<Key, Value, MAX_ENTRIES>::MAX_LIFETIME_SEC;
template<class Key, class Value, uint32_t MAX_ENTRIES>
Cache<Key, Value, MAX_ENTRIES>::Cache(): _mutex(), _currentlyFlushingEntries(), _cachedBlocks(), _timeoutFlusher(nullptr) {
Cache<Key, Value, MAX_ENTRIES>::Cache(const std::string& cacheName): _mutex(), _currentlyFlushingEntries(), _cachedBlocks(), _timeoutFlusher(nullptr) {
//Don't initialize timeoutFlusher in the initializer list,
//because it then might already call Cache::popOldEntries() before Cache is done constructing.
_timeoutFlusher = std::make_unique<PeriodicTask>(std::bind(&Cache::_deleteOldEntriesParallel, this), PURGE_INTERVAL);
_timeoutFlusher = std::make_unique<PeriodicTask>(std::bind(&Cache::_deleteOldEntriesParallel, this), PURGE_INTERVAL, "flush_" + cacheName);
}
template<class Key, class Value, uint32_t MAX_ENTRIES>

View File

@ -7,10 +7,10 @@ using namespace cpputils::logging;
namespace blockstore {
namespace caching {
PeriodicTask::PeriodicTask(function<void ()> task, double intervalSec) :
PeriodicTask::PeriodicTask(function<void ()> task, double intervalSec, std::string threadName) :
_task(task),
_interval(static_cast<uint64_t>(UINT64_C(1000000000) * intervalSec)),
_thread(std::bind(&PeriodicTask::_loopIteration, this)) {
_thread(std::bind(&PeriodicTask::_loopIteration, this), std::move(threadName)) {
_thread.start();
}

View File

@ -11,7 +11,7 @@ namespace caching {
class PeriodicTask final {
public:
PeriodicTask(std::function<void ()> task, double intervalSec);
PeriodicTask(std::function<void ()> task, double intervalSec, std::string threadName);
private:
bool _loopIteration();

View File

@ -8,7 +8,7 @@ namespace cpputils {
_buffer(buffer),
_minSize(minSize),
_maxSize(maxSize),
_thread(std::bind(&RandomGeneratorThread::_loopIteration, this)) {
_thread(std::bind(&RandomGeneratorThread::_loopIteration, this), "RandomGeneratorThread") {
ASSERT(_maxSize >= _minSize, "Invalid parameters");
}

View File

@ -6,7 +6,8 @@ using boost::none;
namespace cpputils {
LoopThread::LoopThread(function<bool()> loopIteration): _loopIteration(std::move(loopIteration)), _runningHandle(none) {
LoopThread::LoopThread(function<bool()> loopIteration, std::string threadName)
: _loopIteration(std::move(loopIteration)), _runningHandle(none), _threadName(std::move(threadName)) {
}
LoopThread::~LoopThread() {
@ -16,7 +17,7 @@ namespace cpputils {
}
void LoopThread::start() {
_runningHandle = ThreadSystem::singleton().start(_loopIteration);
_runningHandle = ThreadSystem::singleton().start(_loopIteration, _threadName);
}
void LoopThread::stop() {

View File

@ -14,7 +14,7 @@ namespace cpputils {
class LoopThread final {
public:
// The loopIteration callback returns true, if more iterations should be run, and false, if the thread should be terminated.
LoopThread(std::function<bool()> loopIteration);
LoopThread(std::function<bool()> loopIteration, std::string threadName);
~LoopThread();
void start();
void stop();
@ -22,6 +22,7 @@ namespace cpputils {
private:
std::function<bool()> _loopIteration;
boost::optional<ThreadSystem::Handle> _runningHandle;
std::string _threadName;
DISALLOW_COPY_AND_ASSIGN(LoopThread);
};

View File

@ -1,7 +1,9 @@
#include "ThreadSystem.h"
#include "../logging/logging.h"
#include "debugging.h"
using std::function;
using std::string;
using namespace cpputils::logging;
namespace cpputils {
@ -21,10 +23,10 @@ namespace cpputils {
#endif
}
ThreadSystem::Handle ThreadSystem::start(function<bool()> loopIteration) {
ThreadSystem::Handle ThreadSystem::start(function<bool()> loopIteration, string threadName) {
boost::unique_lock<boost::mutex> lock(_mutex);
auto thread = _startThread(loopIteration);
_runningThreads.push_back(RunningThread{std::move(loopIteration), std::move(thread)});
auto thread = _startThread(loopIteration, threadName);
_runningThreads.push_back(RunningThread{std::move(threadName), std::move(loopIteration), std::move(thread)});
return std::prev(_runningThreads.end());
}
@ -59,13 +61,14 @@ namespace cpputils {
void ThreadSystem::_restartAllThreads() {
for (RunningThread &thread : _runningThreads) {
thread.thread = _startThread(thread.loopIteration);
thread.thread = _startThread(thread.loopIteration, thread.threadName);
}
_mutex.unlock(); // Was locked in the before-fork handler
}
boost::thread ThreadSystem::_startThread(function<bool()> loopIteration) {
return boost::thread([loopIteration = std::move(loopIteration)] {
boost::thread ThreadSystem::_startThread(function<bool()> loopIteration, const string& threadName) {
return boost::thread([loopIteration = std::move(loopIteration), threadName] {
cpputils::set_thread_name(threadName.c_str());
ThreadSystem::_runThread(loopIteration);
});
}

View File

@ -13,6 +13,7 @@ namespace cpputils {
class ThreadSystem final {
private:
struct RunningThread {
std::string threadName;
std::function<bool()> loopIteration; // The loopIteration callback returns true, if more iterations should be run, and false, if the thread should be terminated.
boost::thread thread; // boost::thread because we need it to be interruptible.
};
@ -21,7 +22,7 @@ namespace cpputils {
static ThreadSystem &singleton();
Handle start(std::function<bool()> loopIteration);
Handle start(std::function<bool()> loopIteration, std::string threadName);
void stop(Handle handle);
private:
@ -34,7 +35,7 @@ namespace cpputils {
//TODO Rename to _doOnBeforeFork and _doAfterFork or similar, because they also handle locking _mutex for fork().
void _stopAllThreadsForRestart();
void _restartAllThreads();
boost::thread _startThread(std::function<bool()> loopIteration);
boost::thread _startThread(std::function<bool()> loopIteration, const std::string& threadName);
std::list<RunningThread> _runningThreads; // std::list, because we give out iterators as handles
boost::mutex _mutex;

View File

@ -9,7 +9,7 @@
namespace cryfs_cli {
class CallAfterTimeout final {
public:
CallAfterTimeout(boost::chrono::milliseconds timeout, std::function<void()> callback);
CallAfterTimeout(boost::chrono::milliseconds timeout, std::function<void()> callback, const std::string& timeoutName);
void resetTimer();
private:
bool _checkTimeoutThreadIteration();
@ -25,8 +25,8 @@ namespace cryfs_cli {
DISALLOW_COPY_AND_ASSIGN(CallAfterTimeout);
};
inline CallAfterTimeout::CallAfterTimeout(boost::chrono::milliseconds timeout, std::function<void()> callback)
:_callback(std::move(callback)), _timeout(timeout), _start(), _checkTimeoutThread(std::bind(&CallAfterTimeout::_checkTimeoutThreadIteration, this)) {
inline CallAfterTimeout::CallAfterTimeout(boost::chrono::milliseconds timeout, std::function<void()> callback, const std::string& timeoutName)
:_callback(std::move(callback)), _timeout(timeout), _start(), _checkTimeoutThread(std::bind(&CallAfterTimeout::_checkTimeoutThreadIteration, this), "timeout_" + timeoutName) {
resetTimer();
_checkTimeoutThread.start();
}

View File

@ -26,6 +26,7 @@
#include <cryfs/localstate/BasedirMetadata.h>
#include "Environment.h"
#include <cryfs/CryfsException.h>
#include <cpp-utils/thread/debugging.h>
//TODO Many functions accessing the ProgramOptions object. Factor out into class that stores it as a member.
//TODO Factor out class handling askPassword
@ -297,7 +298,7 @@ namespace cryfs_cli {
return none;
}
uint64_t millis = std::llround(60000 * (*minutes));
return make_unique_ref<CallAfterTimeout>(milliseconds(millis), callback);
return make_unique_ref<CallAfterTimeout>(milliseconds(millis), callback, "idlecallback");
}
void Cli::_initLogfile(const ProgramOptions &options) {
@ -396,6 +397,7 @@ namespace cryfs_cli {
int Cli::main(int argc, const char *argv[], unique_ref<HttpClient> httpClient, std::function<void()> onMounted) {
cpputils::showBacktraceOnCrash();
cpputils::set_thread_name("cryfs");
try {
_showVersion(std::move(httpClient));

View File

@ -50,7 +50,7 @@ namespace cryfs {
inline CachingFsBlobStore::CachingFsBlobStore(cpputils::unique_ref<fsblobstore::FsBlobStore> baseBlobStore)
: _baseBlobStore(std::move(baseBlobStore)), _cache() {
: _baseBlobStore(std::move(baseBlobStore)), _cache("fsblobstore") {
}
inline CachingFsBlobStore::~CachingFsBlobStore() {

View File

@ -8,6 +8,7 @@
#include <cpp-utils/assert/assert.h>
#include <cpp-utils/logging/logging.h>
#include <cpp-utils/process/subprocess.h>
#include <cpp-utils/thread/debugging.h>
#include <csignal>
#include "InvalidFilesystem.h"
@ -23,7 +24,9 @@ namespace bf = boost::filesystem;
using namespace cpputils::logging;
using std::make_shared;
using std::shared_ptr;
using std::string;
using namespace fspp::fuse;
using cpputils::set_thread_name;
namespace {
bool is_valid_fspp_path(const bf::path& path) {
@ -32,6 +35,18 @@ bool is_valid_fspp_path(const bf::path& path) {
&& !path.has_root_name() // on Windows, it shouldn't have a device specifier (i.e. no "C:")
&& (path.string() == path.generic_string()); // must use portable '/' as directory separator
}
class ThreadNameForDebugging final {
public:
ThreadNameForDebugging(const string& threadName) {
std::string name = "fspp_" + threadName;
set_thread_name(name.c_str());
}
~ThreadNameForDebugging() {
set_thread_name("fspp_idle");
}
};
}
#define FUSE_OBJ (static_cast<Fuse *>(fuse_get_context()->private_data))
@ -335,6 +350,7 @@ void Fuse::unmount(const bf::path& mountdir, bool force) {
}
int Fuse::getattr(const bf::path &path, fspp::fuse::STAT *stbuf) {
ThreadNameForDebugging _threadName("getattr");
#ifdef FSPP_LOG
LOG(DEBUG, "getattr({}, _, _)", path);
#endif
@ -357,6 +373,7 @@ int Fuse::getattr(const bf::path &path, fspp::fuse::STAT *stbuf) {
}
int Fuse::fgetattr(const bf::path &path, fspp::fuse::STAT *stbuf, fuse_file_info *fileinfo) {
ThreadNameForDebugging _threadName("fgetattr");
#ifdef FSPP_LOG
LOG(DEBUG, "fgetattr({}, _, _)\n", path);
#endif
@ -389,6 +406,7 @@ int Fuse::fgetattr(const bf::path &path, fspp::fuse::STAT *stbuf, fuse_file_info
}
int Fuse::readlink(const bf::path &path, char *buf, size_t size) {
ThreadNameForDebugging _threadName("readlink");
#ifdef FSPP_LOG
LOG(DEBUG, "readlink({}, _, {})", path, size);
#endif
@ -414,11 +432,13 @@ int Fuse::mknod(const bf::path &path, ::mode_t mode, dev_t rdev) {
UNUSED(rdev);
UNUSED(mode);
UNUSED(path);
ThreadNameForDebugging _threadName("mknod");
LOG(WARN, "Called non-implemented mknod({}, {}, _)", path, mode);
return ENOSYS;
}
int Fuse::mkdir(const bf::path &path, ::mode_t mode) {
ThreadNameForDebugging _threadName("mkdir");
#ifdef FSPP_LOG
LOG(DEBUG, "mkdir({}, {})", path, mode);
#endif
@ -442,6 +462,7 @@ int Fuse::mkdir(const bf::path &path, ::mode_t mode) {
}
int Fuse::unlink(const bf::path &path) {
ThreadNameForDebugging _threadName("unlink");
#ifdef FSPP_LOG
LOG(DEBUG, "unlink({})", path);
#endif
@ -464,6 +485,7 @@ int Fuse::unlink(const bf::path &path) {
}
int Fuse::rmdir(const bf::path &path) {
ThreadNameForDebugging _threadName("rmdir");
#ifdef FSPP_LOG
LOG(DEBUG, "rmdir({})", path);
#endif
@ -486,6 +508,7 @@ int Fuse::rmdir(const bf::path &path) {
}
int Fuse::symlink(const bf::path &to, const bf::path &from) {
ThreadNameForDebugging _threadName("symlink");
#ifdef FSPP_LOG
LOG(DEBUG, "symlink({}, {})", to, from);
#endif
@ -509,6 +532,7 @@ int Fuse::symlink(const bf::path &to, const bf::path &from) {
}
int Fuse::rename(const bf::path &from, const bf::path &to) {
ThreadNameForDebugging _threadName("rename");
#ifdef FSPP_LOG
LOG(DEBUG, "rename({}, {})", from, to);
#endif
@ -533,6 +557,7 @@ int Fuse::rename(const bf::path &from, const bf::path &to) {
//TODO
int Fuse::link(const bf::path &from, const bf::path &to) {
ThreadNameForDebugging _threadName("link");
LOG(WARN, "NOT IMPLEMENTED: link({}, {})", from, to);
//auto real_from = _impl->RootDir() / from;
//auto real_to = _impl->RootDir() / to;
@ -542,6 +567,7 @@ int Fuse::link(const bf::path &from, const bf::path &to) {
}
int Fuse::chmod(const bf::path &path, ::mode_t mode) {
ThreadNameForDebugging _threadName("chmod");
#ifdef FSPP_LOG
LOG(DEBUG, "chmod({}, {})", path, mode);
#endif
@ -564,6 +590,7 @@ int Fuse::chmod(const bf::path &path, ::mode_t mode) {
}
int Fuse::chown(const bf::path &path, ::uid_t uid, ::gid_t gid) {
ThreadNameForDebugging _threadName("chown");
#ifdef FSPP_LOG
LOG(DEBUG, "chown({}, {}, {})", path, uid, gid);
#endif
@ -586,6 +613,7 @@ int Fuse::chown(const bf::path &path, ::uid_t uid, ::gid_t gid) {
}
int Fuse::truncate(const bf::path &path, int64_t size) {
ThreadNameForDebugging _threadName("truncate");
#ifdef FSPP_LOG
LOG(DEBUG, "truncate({}, {})", path, size);
#endif
@ -608,6 +636,7 @@ int Fuse::truncate(const bf::path &path, int64_t size) {
}
int Fuse::ftruncate(const bf::path &path, int64_t size, fuse_file_info *fileinfo) {
ThreadNameForDebugging _threadName("ftruncate");
#ifdef FSPP_LOG
LOG(DEBUG, "ftruncate({}, {})", path, size);
#endif
@ -630,6 +659,7 @@ int Fuse::ftruncate(const bf::path &path, int64_t size, fuse_file_info *fileinfo
}
int Fuse::utimens(const bf::path &path, const timespec times[2]) {
ThreadNameForDebugging _threadName("utimens");
#ifdef FSPP_LOG
LOG(DEBUG, "utimens({}, _)", path);
#endif
@ -652,6 +682,7 @@ int Fuse::utimens(const bf::path &path, const timespec times[2]) {
}
int Fuse::open(const bf::path &path, fuse_file_info *fileinfo) {
ThreadNameForDebugging _threadName("open");
#ifdef FSPP_LOG
LOG(DEBUG, "open({}, _)", path);
#endif
@ -674,6 +705,7 @@ int Fuse::open(const bf::path &path, fuse_file_info *fileinfo) {
}
int Fuse::release(const bf::path &path, fuse_file_info *fileinfo) {
ThreadNameForDebugging _threadName("release");
#ifdef FSPP_LOG
LOG(DEBUG, "release({}, _)", path);
#endif
@ -696,6 +728,7 @@ int Fuse::release(const bf::path &path, fuse_file_info *fileinfo) {
}
int Fuse::read(const bf::path &path, char *buf, size_t size, int64_t offset, fuse_file_info *fileinfo) {
ThreadNameForDebugging _threadName("read");
#ifdef FSPP_LOG
LOG(DEBUG, "read({}, _, {}, {}, _)", path, size, offset);
#endif
@ -717,6 +750,7 @@ int Fuse::read(const bf::path &path, char *buf, size_t size, int64_t offset, fus
}
int Fuse::write(const bf::path &path, const char *buf, size_t size, int64_t offset, fuse_file_info *fileinfo) {
ThreadNameForDebugging _threadName("write");
#ifdef FSPP_LOG
LOG(DEBUG, "write({}, _, {}, {}, _)", path, size, offsset);
#endif
@ -739,6 +773,7 @@ int Fuse::write(const bf::path &path, const char *buf, size_t size, int64_t offs
}
int Fuse::statfs(const bf::path &path, struct ::statvfs *fsstat) {
ThreadNameForDebugging _threadName("statfs");
#ifdef FSPP_LOG
LOG(DEBUG, "statfs({}, _)", path);
#endif
@ -762,6 +797,7 @@ int Fuse::statfs(const bf::path &path, struct ::statvfs *fsstat) {
}
int Fuse::flush(const bf::path &path, fuse_file_info *fileinfo) {
ThreadNameForDebugging _threadName("flush");
#ifdef FSPP_LOG
LOG(WARN, "flush({}, _)", path);
#endif
@ -784,6 +820,7 @@ int Fuse::flush(const bf::path &path, fuse_file_info *fileinfo) {
}
int Fuse::fsync(const bf::path &path, int datasync, fuse_file_info *fileinfo) {
ThreadNameForDebugging _threadName("fsync");
#ifdef FSPP_LOG
LOG(DEBUG, "fsync({}, {}, _)", path, datasync);
#endif
@ -812,12 +849,14 @@ int Fuse::fsync(const bf::path &path, int datasync, fuse_file_info *fileinfo) {
int Fuse::opendir(const bf::path &path, fuse_file_info *fileinfo) {
UNUSED(path);
UNUSED(fileinfo);
ThreadNameForDebugging _threadName("opendir");
//LOG(DEBUG, "opendir({}, _)", path);
//We don't need opendir, because readdir works directly on the path
return 0;
}
int Fuse::readdir(const bf::path &path, void *buf, fuse_fill_dir_t filler, int64_t offset, fuse_file_info *fileinfo) {
ThreadNameForDebugging _threadName("readdir");
#ifdef FSPP_LOG
LOG(DEBUG, "readdir({}, _, _, {}, _)", path, offest);
#endif
@ -863,6 +902,7 @@ int Fuse::readdir(const bf::path &path, void *buf, fuse_fill_dir_t filler, int64
int Fuse::releasedir(const bf::path &path, fuse_file_info *fileinfo) {
UNUSED(path);
UNUSED(fileinfo);
ThreadNameForDebugging _threadName("releasedir");
//LOG(DEBUG, "releasedir({}, _)", path);
//We don't need releasedir, because readdir works directly on the path
return 0;
@ -873,12 +913,14 @@ int Fuse::fsyncdir(const bf::path &path, int datasync, fuse_file_info *fileinfo)
UNUSED(fileinfo);
UNUSED(datasync);
UNUSED(path);
ThreadNameForDebugging _threadName("fsyncdir");
//LOG(WARN, "Called non-implemented fsyncdir({}, {}, _)", path, datasync);
return 0;
}
void Fuse::init(fuse_conn_info *conn) {
UNUSED(conn);
ThreadNameForDebugging _threadName("init");
_fs = _init(this);
LOG(INFO, "Filesystem started.");
@ -892,6 +934,7 @@ void Fuse::init(fuse_conn_info *conn) {
}
void Fuse::destroy() {
ThreadNameForDebugging _threadName("destroy");
_fs = make_shared<InvalidFilesystem>();
LOG(INFO, "Filesystem stopped.");
_running = false;
@ -899,6 +942,7 @@ void Fuse::destroy() {
}
int Fuse::access(const bf::path &path, int mask) {
ThreadNameForDebugging _threadName("access");
#ifdef FSPP_LOG
LOG(DEBUG, "access({}, {})", path, mask);
#endif
@ -921,6 +965,7 @@ int Fuse::access(const bf::path &path, int mask) {
}
int Fuse::create(const bf::path &path, ::mode_t mode, fuse_file_info *fileinfo) {
ThreadNameForDebugging _threadName("create");
#ifdef FSPP_LOG
LOG(DEBUG, "create({}, {}, _)", path, mode);
#endif

View File

@ -13,7 +13,7 @@ using ::testing::Test;
//Test that Cache uses a move constructor for Value if possible
class CacheTest_MoveConstructor: public Test {
public:
CacheTest_MoveConstructor(): cache(make_unique_ref<Cache<MinimalKeyType, CopyableMovableValueType, 100>>()) {
CacheTest_MoveConstructor(): cache(make_unique_ref<Cache<MinimalKeyType, CopyableMovableValueType, 100>>("test")) {
CopyableMovableValueType::numCopyConstructorCalled = 0;
}
unique_ref<Cache<MinimalKeyType, CopyableMovableValueType, 100>> cache;

View File

@ -36,7 +36,7 @@ private:
class CacheTest_RaceCondition: public ::testing::Test {
public:
CacheTest_RaceCondition(): cache(), destructorStarted(), destructorFinished(false) {}
CacheTest_RaceCondition(): cache("test"), destructorStarted(), destructorFinished(false) {}
static constexpr unsigned int MAX_ENTRIES = 100;

View File

@ -37,7 +37,7 @@ class PeriodicTaskTest: public Test {
};
TEST_F(PeriodicTaskTest, DoesntDeadlockInDestructorWhenDestructedImmediately) {
PeriodicTask task([](){}, 1);
PeriodicTask task([](){}, 1, "test");
}
TEST_F(PeriodicTaskTest, CallsCallbackAtLeast10Times) {
@ -45,7 +45,7 @@ TEST_F(PeriodicTaskTest, CallsCallbackAtLeast10Times) {
PeriodicTask task([&counter](){
counter.decrease();
}, 0.001);
}, 0.001, "test");
counter.waitForZero();
}
@ -55,7 +55,7 @@ TEST_F(PeriodicTaskTest, DoesntCallCallbackAfterDestruction) {
{
PeriodicTask task([&callCount](){
callCount += 1;
}, 0.001);
}, 0.001, "test");
}
int callCountDirectlyAfterDestruction = callCount;
boost::this_thread::sleep_for(boost::chrono::seconds(1));

View File

@ -13,7 +13,7 @@
// Furthermore, the class checks that there are no memory leaks left after destructing the QueueMap (by counting leftover instances of Keys/Values).
class CacheTest: public ::testing::Test {
public:
CacheTest(): _cache() {}
CacheTest(): _cache("test") {}
void push(int key, int value);
boost::optional<int> pop(int key);

View File

@ -15,7 +15,7 @@ public:
CallAfterTimeoutTest(): called(false) {}
unique_ref<CallAfterTimeout> callAfterTimeout(milliseconds timeout) {
return make_unique_ref<CallAfterTimeout>(timeout, [this] {called = true;});
return make_unique_ref<CallAfterTimeout>(timeout, [this] {called = true;}, "test");
}
std::atomic<bool> called;