rust-lang / rust-lang/rust

Guarantee needs_drop thread-local variables are destroyed at a well-defined point

Open
#147,342 4 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-destructors A-thread-locals C-discussion T-lang T-libs
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

First, some terminology. When a thread-local variable gets destroyed there are two possible relevant destructors:

  1. The Drop implementation of the type stored in the thread-local variable. I will call these "user-space" destructors, as they are defined by Rust users.
  2. The cleanup needed by the thread-local implementation to free any related memory. I will call these "internal" destructors, as they are defined by the standard library internals.

I've noticed that on some platforms we leave the moment when TLS variables are destroyed entirely up to the operating system/pthread implementation, for both user-space destructors as well as the internal destructors, whereas on other platforms we have an explicit destructor registry system where user-space destructors are registered to be called during thread clean-up.

I propose we instead always use a registry system for user-space destructors on all platforms, and call them at a well-defined point during thread clean-up. This has several advantages:

  1. It guarantees that if needs_drop::<T>() is false for some thread-local variable, then LocalKey::with can never fail (assuming the constructor doesn't panic), even when user-space TLS destructors are being ran. This is of potential interest to global allocators, which are regularly called in other user-space destructors.
  2. It can simplify the std::thread::current implementation which currently has to handle the case where its thread-local storage has been cleaned up already, meaning it has to synthesize a new Thread handle.
  3. Because of (2) we can simplify the std::thread::Thread handle implementation to have a single shared canonical handle per thread. This allows us to implement extra functionality which was previously impossible/impractical, such as Thread::is_finished to see if a thread is truly finished just from its handle (which can be shared and passed around, unlike JoinHandle).
  4. We can strengthen the guarantee of std::thread::JoinHandle::is_finished such that it only returns true if truly all user-defined Rust code on that thread has finished running - currently it only claims main is done.

@joboet Since this is your area of expertise I would be interested to hear your thoughts.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

No files, tests, or entry points are named in the issue. Start by reviewing thread-local destruction and cleanup behavior across the supported platforms, then determine whether the proposed guarantees and registry approach are feasible; done would require an agreed design and corresponding implementation.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
operating-systems
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.