diff --git a/core/sched.cc b/core/sched.cc
index a301591ee493e50e85fcef3f84d806eda147cdaa..35b4174cb82d7e8ef55e9dda24d2f5ce4d029412 100644
--- a/core/sched.cc
+++ b/core/sched.cc
@@ -652,6 +652,14 @@ thread::thread(std::function<void ()> func, attr attr, bool main)
     }
 }
 
+static std::list<std::function<void (thread *)>> exit_notifiers;
+void thread::register_exit_notifier(std::function<void (thread *)> &&n)
+{
+    WITH_LOCK(thread_map_mutex) {
+        exit_notifiers.push_front(std::move(n));
+    }
+}
+
 thread::~thread()
 {
     cancel_this_thread_alarm();
@@ -662,6 +670,10 @@ thread::~thread()
     WITH_LOCK(thread_map_mutex) {
         thread_map.erase(_id);
         total_app_time_exited += _total_cpu_time;
+
+        for (auto& notifier : exit_notifiers) {
+            notifier(this);
+        }
     }
     if (_attr._stack.deleter) {
         _attr._stack.deleter(_attr._stack);
diff --git a/include/osv/sched.hh b/include/osv/sched.hh
index c51981aaf00d5ae447f99d6d4d18267a729f8cc8..d8079f128096cd5b3e4c610e444daee5060fdb95 100644
--- a/include/osv/sched.hh
+++ b/include/osv/sched.hh
@@ -585,6 +585,18 @@ public:
     static thread *find_by_id(unsigned int id);
 
     static int numthreads();
+    /**
+     * Registers an std::function that will be called when a thread is torn
+     * down.  This is useful, for example, to run code that needs to cleanup
+     * resources acquired by a given thread, about which the thread has no
+     * knowledge about
+     *
+     * In general, this will not run in the same context as the dying thread,
+     * but rather from special scheduler methods. Therefore, one needs to be
+     * careful about stack usage in here. Do not register notifiers that use a
+     * lot of stack
+     */
+    static void register_exit_notifier(std::function<void (thread *)> &&n);
 private:
     class reaper;
     friend class reaper;