redrop

Shutting Down Cleanly

There's one line we've mentioned in passing a few times now, and it deserves its own moment:

redrop::shutdown().await;

Why you still need this, even with the background daemon

It's tempting to think that with the tokio feature's background daemon draining the queue continuously, you don't need to do anything else — cleanup just happens. Mostly true! But nothing has told your program to wait before it exits. If main returns right after a burst of drops, the process can end before the daemon has finished flushing everything, especially if the cleanup work takes longer than the rest of main has left to run.

shutdown() is that wait. It closes the queue to new entries and then waits — respecting your concurrency limit the whole time — until every cleanup that was queued or already running has actually finished. Put it last:

#[tokio::main]
async fn main() {
    // ... set up, do work, values drop along the way ...

    redrop::shutdown().await; // last thing before main returns
}

What "last" really means

Once shutdown() returns, redrop's queue is permanently closed. Any value dropped after that point will not have its async_drop() run — its cleanup future is simply discarded (redrop prints a warning to stderr about this in debug builds, so it's not silent, but it also isn't run). This isn't a bug so much as a hard boundary: once you've told the program "I'm finished waiting for cleanup," there's nothing left to wait with.

In practice this means: don't drop anything you care about after calling shutdown(). If you're not sure something might outlive that point — for instance, something held by a background task you haven't joined yet — join or drop it before the call, not after.

Recap

Over this tutorial, we:

  1. Saw why Drop::drop being synchronous is a real constraint, not just an inconvenience.
  2. Wrote #[redrop::async_drop] on an inherent impl block, and looked at what it expands to — mem::take to get ownership, redrop::enqueue to hand off the future.
  3. Learned that a shared, bounded-concurrency pump is what keeps a burst of drops from overwhelming anything, and that the tokio feature adds a background task that drains it continuously.
  4. Pulled the concurrency limit out of code entirely, into redrop.toml, loaded with a path that itself can be overridden by an environment variable.
  5. Landed on shutdown() as the one call that actually guarantees everything finished.

Where to go from here

Thanks for reading slowly. That's genuinely the best way through this kind of material.

← Keeping Configuration Out of Your Code