From: Jason Dillaman Date: Wed, 13 Dec 2017 21:30:53 +0000 (-0500) Subject: rbd-mirror: integrate trash watcher within image deleter X-Git-Tag: v13.0.2~714^2~4 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=e6330a41ef4d3c3d3f0f0cd5d78a31884771c6c1;p=ceph.git rbd-mirror: integrate trash watcher within image deleter Signed-off-by: Jason Dillaman --- diff --git a/src/test/rbd_mirror/test_ImageDeleter.cc b/src/test/rbd_mirror/test_ImageDeleter.cc index 3325297155b..d2e36793aa4 100644 --- a/src/test/rbd_mirror/test_ImageDeleter.cc +++ b/src/test/rbd_mirror/test_ImageDeleter.cc @@ -78,12 +78,23 @@ public: void TearDown() override { remove_image(); + + C_SaferCond ctx; + m_deleter->shut_down(&ctx); + ctx.wait(); + delete m_deleter; m_service_daemon.reset(); TestFixture::TearDown(); } + void init_image_deleter() { + C_SaferCond ctx; + m_deleter->init(&ctx); + ASSERT_EQ(0, ctx.wait()); + } + void remove_image(bool force=false) { if (!force) { cls::rbd::MirrorImage mirror_image; @@ -203,7 +214,6 @@ public: &mirror_image)); } - librbd::RBD rbd; std::string m_local_image_id; std::unique_ptr> m_service_daemon; @@ -211,6 +221,7 @@ public: }; TEST_F(TestImageDeleter, Delete_NonPrimary_Image) { + init_image_deleter(); m_deleter->schedule_image_delete(GLOBAL_IMAGE_ID, false, nullptr); C_SaferCond ctx; @@ -224,6 +235,7 @@ TEST_F(TestImageDeleter, Delete_NonPrimary_Image) { } TEST_F(TestImageDeleter, Delete_Split_Brain_Image) { + init_image_deleter(); promote_image(); demote_image(); @@ -240,6 +252,7 @@ TEST_F(TestImageDeleter, Delete_Split_Brain_Image) { } TEST_F(TestImageDeleter, Fail_Delete_Primary_Image) { + init_image_deleter(); promote_image(); C_SaferCond ctx; @@ -251,6 +264,7 @@ TEST_F(TestImageDeleter, Fail_Delete_Primary_Image) { } TEST_F(TestImageDeleter, Fail_Delete_Orphan_Image) { + init_image_deleter(); promote_image(); demote_image(); @@ -263,6 +277,7 @@ TEST_F(TestImageDeleter, Fail_Delete_Orphan_Image) { } TEST_F(TestImageDeleter, Delete_Image_With_Child) { + init_image_deleter(); create_snapshot(); m_deleter->schedule_image_delete(GLOBAL_IMAGE_ID, false, nullptr); @@ -276,6 +291,7 @@ TEST_F(TestImageDeleter, Delete_Image_With_Child) { } TEST_F(TestImageDeleter, Delete_Image_With_Children) { + init_image_deleter(); create_snapshot("snap1"); create_snapshot("snap2"); @@ -290,6 +306,7 @@ TEST_F(TestImageDeleter, Delete_Image_With_Children) { } TEST_F(TestImageDeleter, Delete_Image_With_ProtectedChild) { + init_image_deleter(); create_snapshot("snap1", true); m_deleter->schedule_image_delete(GLOBAL_IMAGE_ID, false, nullptr); @@ -303,6 +320,7 @@ TEST_F(TestImageDeleter, Delete_Image_With_ProtectedChild) { } TEST_F(TestImageDeleter, Delete_Image_With_ProtectedChildren) { + init_image_deleter(); create_snapshot("snap1", true); create_snapshot("snap2", true); @@ -317,6 +335,7 @@ TEST_F(TestImageDeleter, Delete_Image_With_ProtectedChildren) { } TEST_F(TestImageDeleter, Delete_Image_With_Clone) { + init_image_deleter(); std::string clone_id = create_clone(); C_SaferCond ctx; @@ -337,6 +356,7 @@ TEST_F(TestImageDeleter, Delete_Image_With_Clone) { } TEST_F(TestImageDeleter, Delete_NonExistent_Image) { + init_image_deleter(); remove_image(); cls::rbd::MirrorImage mirror_image(GLOBAL_IMAGE_ID, @@ -357,6 +377,7 @@ TEST_F(TestImageDeleter, Delete_NonExistent_Image) { } TEST_F(TestImageDeleter, Delete_NonExistent_Image_With_MirroringState) { + init_image_deleter(); remove_image(true); cls::rbd::MirrorImage mirror_image(GLOBAL_IMAGE_ID, @@ -380,6 +401,7 @@ TEST_F(TestImageDeleter, Delete_NonExistent_Image_With_MirroringState) { } TEST_F(TestImageDeleter, Delete_NonExistent_Image_Without_MirroringState) { + init_image_deleter(); remove_image(); C_SaferCond ctx; @@ -393,6 +415,7 @@ TEST_F(TestImageDeleter, Delete_NonExistent_Image_Without_MirroringState) { } TEST_F(TestImageDeleter, Fail_Delete_NonPrimary_Image) { + init_image_deleter(); ImageCtx *ictx = new ImageCtx("", m_local_image_id, "", m_local_io_ctx, false); EXPECT_EQ(0, ictx->state->open(false)); @@ -405,6 +428,7 @@ TEST_F(TestImageDeleter, Fail_Delete_NonPrimary_Image) { } TEST_F(TestImageDeleter, Retry_Failed_Deletes) { + init_image_deleter(); EXPECT_EQ(0, g_ceph_context->_conf->set_val("rbd_mirror_delete_retry_interval", "0.1")); ImageCtx *ictx = new ImageCtx("", m_local_image_id, "", m_local_io_ctx, false); @@ -425,4 +449,3 @@ TEST_F(TestImageDeleter, Retry_Failed_Deletes) { check_image_deleted(); } - diff --git a/src/test/rbd_mirror/test_ImageReplayer.cc b/src/test/rbd_mirror/test_ImageReplayer.cc index 945418bce62..b1616195ca3 100644 --- a/src/test/rbd_mirror/test_ImageReplayer.cc +++ b/src/test/rbd_mirror/test_ImageReplayer.cc @@ -125,6 +125,11 @@ public: m_threads.get())); m_image_deleter.reset(new rbd::mirror::ImageDeleter<>( m_local_ioctx, m_threads.get(), m_service_daemon.get())); + + C_SaferCond ctx; + m_image_deleter->init(&ctx); + EXPECT_EQ(0, ctx.wait()); + m_instance_watcher = rbd::mirror::InstanceWatcher<>::create( m_local_ioctx, m_threads->work_queue, nullptr); m_instance_watcher->handle_acquire_leader(); diff --git a/src/test/rbd_mirror/test_mock_ImageReplayer.cc b/src/test/rbd_mirror/test_mock_ImageReplayer.cc index 63919514b64..869b55f795b 100644 --- a/src/test/rbd_mirror/test_mock_ImageReplayer.cc +++ b/src/test/rbd_mirror/test_mock_ImageReplayer.cc @@ -87,8 +87,8 @@ template <> struct ImageDeleter { MOCK_METHOD3(schedule_image_delete, void(const std::string&, bool, Context*)); - MOCK_METHOD3(wait_for_scheduled_deletion, - void(const std::string&, Context*, bool)); + MOCK_METHOD2(wait_for_scheduled_deletion, + void(const std::string&, Context*)); MOCK_METHOD1(cancel_waiter, void(const std::string&)); }; @@ -385,7 +385,7 @@ public: const std::string& global_image_id, int r) { EXPECT_CALL(mock_image_deleter, - wait_for_scheduled_deletion(global_image_id, _, false)) + wait_for_scheduled_deletion(global_image_id, _)) .WillOnce(WithArg<1>(Invoke([this, r](Context *ctx) { m_threads->work_queue->queue(ctx, r); }))); diff --git a/src/tools/rbd_mirror/ImageDeleter.cc b/src/tools/rbd_mirror/ImageDeleter.cc index 94168ad6166..46a4bdc78c3 100644 --- a/src/tools/rbd_mirror/ImageDeleter.cc +++ b/src/tools/rbd_mirror/ImageDeleter.cc @@ -32,14 +32,13 @@ #include "ImageDeleter.h" #include "tools/rbd_mirror/Threads.h" #include "tools/rbd_mirror/image_deleter/RemoveRequest.h" +#include "tools/rbd_mirror/image_deleter/TrashMoveRequest.h" +#include "tools/rbd_mirror/image_deleter/TrashWatcher.h" #include #include #define dout_context g_ceph_context #define dout_subsys ceph_subsys_rbd_mirror -#undef dout_prefix -#define dout_prefix *_dout << "rbd::mirror::ImageDeleter: " << this << " " \ - << __func__ << ": " using std::string; using std::stringstream; @@ -126,13 +125,33 @@ template ImageDeleter::ImageDeleter(librados::IoCtx& local_io_ctx, Threads* threads, ServiceDaemon* service_daemon) - : m_local_io_ctx(local_io_ctx), m_work_queue(threads->work_queue), - m_timer(threads->timer), m_timer_lock(&threads->timer_lock), - m_service_daemon(service_daemon), + : m_local_io_ctx(local_io_ctx), m_threads(threads), + m_service_daemon(service_daemon), m_trash_listener(this), m_lock(librbd::util::unique_lock_name("rbd::mirror::ImageDeleter::m_lock", this)) { } +#undef dout_prefix +#define dout_prefix *_dout << "rbd::mirror::ImageDeleter: " << " " \ + << __func__ << ": " + +template +void ImageDeleter::trash_move(librados::IoCtx& local_io_ctx, + const std::string& global_image_id, + bool resync, + ContextWQ* work_queue, Context* on_finish) { + dout(10) << "global_image_id=" << global_image_id << ", " + << "resync=" << resync << dendl; + + auto req = rbd::mirror::image_deleter::TrashMoveRequest<>::create( + local_io_ctx, global_image_id, resync, work_queue, on_finish); + req->send(); +} + +#undef dout_prefix +#define dout_prefix *_dout << "rbd::mirror::ImageDeleter: " << this << " " \ + << __func__ << ": " + template void ImageDeleter::init(Context* on_finish) { dout(10) << dendl; @@ -140,7 +159,10 @@ void ImageDeleter::init(Context* on_finish) { m_asok_hook = new ImageDeleterAdminSocketHook( g_ceph_context, m_local_io_ctx.get_pool_name(), this); - on_finish->complete(0); + m_trash_watcher = image_deleter::TrashWatcher::create(m_local_io_ctx, + m_threads, + m_trash_listener); + m_trash_watcher->init(on_finish); } template @@ -150,18 +172,31 @@ void ImageDeleter::shut_down(Context* on_finish) { delete m_asok_hook; m_asok_hook = nullptr; + shut_down_trash_watcher(on_finish); +} + +template +void ImageDeleter::shut_down_trash_watcher(Context* on_finish) { + dout(10) << dendl; + assert(m_trash_watcher); + auto ctx = new FunctionContext([this, on_finish](int r) { + delete m_trash_watcher; + m_trash_watcher = nullptr; + + wait_for_ops(on_finish); + }); + m_trash_watcher->shut_down(ctx); +} + +template +void ImageDeleter::wait_for_ops(Context* on_finish) { { - Mutex::Locker timer_locker(*m_timer_lock); + Mutex::Locker timer_locker(m_threads->timer_lock); Mutex::Locker locker(m_lock); m_running = false; cancel_retry_timer(); } - wait_for_ops(on_finish); -} - -template -void ImageDeleter::wait_for_ops(Context* on_finish) { auto ctx = new FunctionContext([this, on_finish](int) { cancel_all_deletions(on_finish); }); @@ -176,9 +211,7 @@ void ImageDeleter::cancel_all_deletions(Context* on_finish) { assert(m_in_flight_delete_queue.empty()); for (auto& queue : {&m_delete_queue, &m_retry_delete_queue}) { for (auto& info : *queue) { - if (info->on_delete != nullptr) { - info->on_delete->complete(-ECANCELED); - } + notify_on_delete(info->global_image_id, -ECANCELED); } queue->clear(); } @@ -194,12 +227,17 @@ void ImageDeleter::schedule_image_delete(const std::string& global_image_id, if (on_delete != nullptr) { on_delete = new FunctionContext([this, on_delete](int r) { - m_work_queue->queue(on_delete, r); + m_threads->work_queue->queue(on_delete, r); }); } { Mutex::Locker locker(m_lock); + notify_on_delete(global_image_id, -ESTALE); + if (on_delete != nullptr) { + m_on_delete_contexts[global_image_id] = on_delete; + } + auto del_info = find_delete_info(global_image_id); if (del_info != nullptr) { dout(20) << "image " << global_image_id << " " @@ -207,43 +245,35 @@ void ImageDeleter::schedule_image_delete(const std::string& global_image_id, if (ignore_orphaned) { del_info->ignore_orphaned = true; } - - if (del_info->on_delete != nullptr) { - del_info->on_delete->complete(-ESTALE); - } - del_info->on_delete = on_delete; return; } - m_delete_queue.emplace_back(new DeleteInfo(global_image_id, ignore_orphaned, - on_delete)); + m_delete_queue.emplace_back(new DeleteInfo(global_image_id, + ignore_orphaned)); } remove_images(); } template -void ImageDeleter::wait_for_scheduled_deletion(const std::string &global_image_id, - Context *ctx, - bool notify_on_failed_retry) { +void ImageDeleter::wait_for_deletion(const std::string& global_image_id, + bool scheduled_only, + Context* on_finish) { dout(5) << "global_image_id=" << global_image_id << dendl; - ctx = new FunctionContext([this, ctx](int r) { - m_work_queue->queue(ctx, r); + on_finish = new FunctionContext([this, on_finish](int r) { + m_threads->work_queue->queue(on_finish, r); }); Mutex::Locker locker(m_lock); auto del_info = find_delete_info(global_image_id); - if (!del_info) { + if (!del_info && scheduled_only) { // image not scheduled for deletion - ctx->complete(0); + on_finish->complete(0); return; } - if (del_info->on_delete != nullptr) { - del_info->on_delete->complete(-ESTALE); - } - del_info->on_delete = ctx; - del_info->notify_on_failed_retry = notify_on_failed_retry; + notify_on_delete(global_image_id, -ESTALE); + m_on_delete_contexts[global_image_id] = on_finish; } template @@ -251,15 +281,7 @@ void ImageDeleter::cancel_waiter(const std::string &global_image_id) { dout(5) << "global_image_id=" << global_image_id << dendl; Mutex::Locker locker(m_lock); - auto del_info = find_delete_info(global_image_id); - if (!del_info) { - return; - } - - if (del_info->on_delete != nullptr) { - del_info->on_delete->complete(-ECANCELED); - del_info->on_delete = nullptr; - } + notify_on_delete(global_image_id, -ECANCELED); } template @@ -267,7 +289,7 @@ void ImageDeleter::complete_active_delete(DeleteInfoRef* delete_info, int r) { dout(20) << "info=" << *delete_info << ", r=" << r << dendl; Mutex::Locker locker(m_lock); - (*delete_info)->notify(r); + notify_on_delete((*delete_info)->global_image_id, r); delete_info->reset(); } @@ -283,13 +305,12 @@ void ImageDeleter::enqueue_failed_delete(DeleteInfoRef* delete_info, return; } - Mutex::Locker timer_locker(*m_timer_lock); + Mutex::Locker timer_locker(m_threads->timer_lock); Mutex::Locker locker(m_lock); auto& delete_info_ref = *delete_info; - if (delete_info_ref->notify_on_failed_retry) { - delete_info_ref->notify(error_code); - } + notify_on_delete(delete_info_ref->global_image_id, error_code); delete_info_ref->error_code = error_code; + ++delete_info_ref->retries; delete_info_ref->retry_time = ceph_clock_now(); delete_info_ref->retry_time += retry_delay; m_retry_delete_queue.push_back(delete_info_ref); @@ -410,7 +431,7 @@ void ImageDeleter::remove_image(DeleteInfoRef delete_info) { auto req = image_deleter::RemoveRequest::create( m_local_io_ctx, delete_info->global_image_id, - delete_info->ignore_orphaned, &delete_info->error_result, m_work_queue, + delete_info->ignore_orphaned, &delete_info->error_result, m_threads->work_queue, ctx); req->send(); } @@ -450,7 +471,7 @@ void ImageDeleter::handle_remove_image(DeleteInfoRef delete_info, template void ImageDeleter::schedule_retry_timer() { - assert(m_timer_lock->is_locked()); + assert(m_threads->timer_lock.is_locked()); assert(m_lock.is_locked()); if (!m_running || m_timer_ctx != nullptr || m_retry_delete_queue.empty()) { return; @@ -461,15 +482,15 @@ void ImageDeleter::schedule_retry_timer() { m_timer_ctx = new FunctionContext([this](int r) { handle_retry_timer(); }); - m_timer->add_event_at(delete_info->retry_time, m_timer_ctx); + m_threads->timer->add_event_at(delete_info->retry_time, m_timer_ctx); } template void ImageDeleter::cancel_retry_timer() { dout(10) << dendl; - assert(m_timer_lock->is_locked()); + assert(m_threads->timer_lock.is_locked()); if (m_timer_ctx != nullptr) { - bool canceled = m_timer->cancel_event(m_timer_ctx); + bool canceled = m_threads->timer->cancel_event(m_timer_ctx); m_timer_ctx = nullptr; assert(canceled); } @@ -478,7 +499,7 @@ void ImageDeleter::cancel_retry_timer() { template void ImageDeleter::handle_retry_timer() { dout(10) << dendl; - assert(m_timer_lock->is_locked()); + assert(m_threads->timer_lock.is_locked()); Mutex::Locker locker(m_lock); assert(m_timer_ctx != nullptr); @@ -508,18 +529,44 @@ void ImageDeleter::handle_retry_timer() { remove_images(); m_async_op_tracker.finish_op(); }); - m_work_queue->queue(ctx, 0); + m_threads->work_queue->queue(ctx, 0); } template -void ImageDeleter::DeleteInfo::notify(int r) { - if (on_delete) { - dout(20) << "executing image deletion handler r=" << r << dendl; +void ImageDeleter::handle_trash_image(const std::string& image_id, + const utime_t& deferment_end_time) { + Mutex::Locker timer_locker(m_threads->timer_lock); + Mutex::Locker locker(m_lock); - Context *ctx = on_delete; - on_delete = nullptr; - ctx->complete(r); + // TODO + auto del_info = find_delete_info(image_id); + if (del_info != nullptr) { + dout(20) << "image " << image_id << " " + << "was already scheduled for deletion" << dendl; + return; } + + dout(10) << "image_id=" << image_id << ", " + << "deferment_end_time=" << deferment_end_time << dendl; + + del_info.reset(new DeleteInfo(image_id, false)); + del_info->retry_time = deferment_end_time; + m_retry_delete_queue.push_back(del_info); + + schedule_retry_timer(); +} + +template +void ImageDeleter::notify_on_delete(const std::string& global_image_id, + int r) { + dout(10) << "global_image_id=" << global_image_id << ", r=" << r << dendl; + auto it = m_on_delete_contexts.find(global_image_id); + if (it == m_on_delete_contexts.end()) { + return; + } + + it->second->complete(r); + m_on_delete_contexts.erase(it); } template diff --git a/src/tools/rbd_mirror/ImageDeleter.h b/src/tools/rbd_mirror/ImageDeleter.h index 56920ee812d..f3fc953d35b 100644 --- a/src/tools/rbd_mirror/ImageDeleter.h +++ b/src/tools/rbd_mirror/ImageDeleter.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -38,6 +39,8 @@ namespace mirror { template class ServiceDaemon; template class Threads; +namespace image_deleter { template struct TrashWatcher; } + /** * Manage deletion of non-primary images. */ @@ -51,20 +54,29 @@ public: ImageDeleter(const ImageDeleter&) = delete; ImageDeleter& operator=(const ImageDeleter&) = delete; + static void trash_move(librados::IoCtx& local_io_ctx, + const std::string& global_image_id, bool resync, + ContextWQ* work_queue, Context* on_finish); + void init(Context* on_finish); void shut_down(Context* on_finish); void schedule_image_delete(const std::string& global_image_id, bool ignore_orphaned, Context *on_finish); + void wait_for_deletion(const std::string &global_image_id, + bool scheduled_only, Context* on_finish); void wait_for_scheduled_deletion(const std::string &global_image_id, - Context *ctx, - bool notify_on_failed_retry=true); + Context* on_finish) { + wait_for_deletion(global_image_id, true, on_finish); + } + void cancel_waiter(const std::string &global_image_id); void print_status(Formatter *f, std::stringstream *ss); // for testing purposes + std::vector get_delete_queue_items(); std::vector > get_failed_queue_items(); @@ -73,26 +85,33 @@ public: } private: + struct TrashListener : public image_deleter::TrashListener { + ImageDeleter *image_deleter; + + TrashListener(ImageDeleter *image_deleter) : image_deleter(image_deleter) { + } + + void handle_trash_image(const std::string& image_id, + const utime_t& deferment_end_time) override { + image_deleter->handle_trash_image(image_id, deferment_end_time); + } + }; struct DeleteInfo { std::string global_image_id; bool ignore_orphaned = false; - Context *on_delete = nullptr; image_deleter::ErrorResult error_result = {}; int error_code = 0; utime_t retry_time = {}; int retries = 0; - bool notify_on_failed_retry = true; DeleteInfo(const std::string& global_image_id) : global_image_id(global_image_id) { } - DeleteInfo(const std::string& global_image_id, - bool ignore_orphaned, Context *on_delete) - : global_image_id(global_image_id), ignore_orphaned(ignore_orphaned), - on_delete(on_delete) { + DeleteInfo(const std::string& global_image_id, bool ignore_orphaned) + : global_image_id(global_image_id), ignore_orphaned(ignore_orphaned) { } inline bool operator==(const DeleteInfo& delete_info) const { @@ -104,19 +123,20 @@ private: return os; } - void notify(int r); void print_status(Formatter *f, std::stringstream *ss, bool print_failure_info=false); }; typedef std::shared_ptr DeleteInfoRef; typedef std::deque DeleteQueue; + typedef std::map OnDeleteContexts; librados::IoCtx& m_local_io_ctx; - ContextWQ *m_work_queue; - SafeTimer *m_timer; - Mutex *m_timer_lock; + Threads* m_threads; ServiceDaemon* m_service_daemon; + image_deleter::TrashWatcher* m_trash_watcher = nullptr; + TrashListener m_trash_listener; + std::atomic m_running { 1 }; double m_busy_interval = 1; @@ -128,6 +148,8 @@ private: DeleteQueue m_retry_delete_queue; DeleteQueue m_in_flight_delete_queue; + OnDeleteContexts m_on_delete_contexts; + AdminSocketHook *m_asok_hook = nullptr; Context *m_timer_ctx = nullptr; @@ -148,9 +170,15 @@ private: void cancel_retry_timer(); void handle_retry_timer(); + void handle_trash_image(const std::string& image_id, + const utime_t& deferment_end_time); + + void shut_down_trash_watcher(Context* on_finish); void wait_for_ops(Context* on_finish); void cancel_all_deletions(Context* on_finish); + void notify_on_delete(const std::string& global_image_id, int r); + }; } // namespace mirror diff --git a/src/tools/rbd_mirror/ImageReplayer.cc b/src/tools/rbd_mirror/ImageReplayer.cc index 692dfe617ac..a8137d44843 100644 --- a/src/tools/rbd_mirror/ImageReplayer.cc +++ b/src/tools/rbd_mirror/ImageReplayer.cc @@ -400,8 +400,7 @@ void ImageReplayer::wait_for_deletion() { Context *ctx = create_context_callback< ImageReplayer, &ImageReplayer::handle_wait_for_deletion>(this); - m_image_deleter->wait_for_scheduled_deletion( - m_global_image_id, ctx, false); + m_image_deleter->wait_for_scheduled_deletion(m_global_image_id, ctx); } template diff --git a/src/tools/rbd_mirror/image_deleter/TrashWatcher.h b/src/tools/rbd_mirror/image_deleter/TrashWatcher.h index 8d9ff09e565..b6f698331aa 100644 --- a/src/tools/rbd_mirror/image_deleter/TrashWatcher.h +++ b/src/tools/rbd_mirror/image_deleter/TrashWatcher.h @@ -26,6 +26,12 @@ struct TrashListener; template class TrashWatcher : public librbd::TrashWatcher { public: + static TrashWatcher* create(librados::IoCtx &io_ctx, + Threads *threads, + TrashListener& trash_listener) { + return new TrashWatcher(io_ctx, threads, trash_listener); + } + TrashWatcher(librados::IoCtx &io_ctx, Threads *threads, TrashListener& trash_listener); TrashWatcher(const TrashWatcher&) = delete;