Skip to content

ShardedLock: Fix Debug implementation for ShardedLock{Read,Write}Guard - #1350

Merged
taiki-e merged 1 commit into
crossbeam-rs:mainfrom
MaxFreedomPollard:fix/sharded-lock-guard-debug
Oct 31, 2026
Merged

taiki-e merged 1 commit into
crossbeam-rs:mainfrom
MaxFreedomPollard:fix/sharded-lock-guard-debug

Conversation

@MaxFreedomPollard

Copy link
Copy Markdown
Contributor

ShardedLockReadGuard and ShardedLockWriteGuard implement Debug as f.debug_struct(..).field("lock", &self.lock), so printing a guard goes through ShardedLock's own Debug impl, which calls try_read(). A write guard holds the write lock of every shard, so that try_read() always returns WouldBlock and the guard never shows the value its holder has exclusive access to:

let lock = ShardedLock::new(vec![1, 2, 3]);
let guard = lock.write().unwrap();
println!("{:?}", guard);
// before: ShardedLockWriteGuard { lock: ShardedLock { data: <locked> } }
// after:  [1, 2, 3]

A read guard takes a second read lock on its own shard just to print itself, and prints <locked> too when that attempt fails, for example on Linux while a writer is waiting on that shard. Neither guard is Debug for unsized T, although ShardedLock<[i32]> is supported (see the unsized_type test) and the Display impls of both guards already have T: ?Sized.

std's RwLock guards had this same implementation until rust-lang/rust#83561 (Rust 1.53) replaced it with (**self).fmt(f) for the same reason, as rust-lang/rust#57703 had done for MutexGuard; rust-lang/rust#117138 later added the ?Sized bound. This PR does the same for both guards in crossbeam-utils/src/sync/sharded_lock.rs: (**self).fmt(f) with T: ?Sized + fmt::Debug, identical to the Display impls right below them. ShardedLock's own Debug impl is unchanged.

The new guard_debug test in crossbeam-utils/tests/sharded_lock.rs asserts the output of both guards for a Vec<i32> lock and, with the same setup as unsized_type, for a [i32] lock. It is single-threaded, and each guard is a temporary of its own statement, so the write lock is never requested while the read guard is alive.

This was not compiled or run locally: there was no Rust toolchain in the environment it was prepared in, so CI is the first build. Checked by reading instead:

  • The new bodies match the Display impls in the same file and std's current RwLockReadGuard/RwLockWriteGuard Debug impls.
  • The [i32] lines of the test make the same calls unsized_type already makes.
  • Without the source change the test does not compile, because the guards are not Debug for [i32]. The Vec<i32> assertions alone would fail with ShardedLockReadGuard { lock: ShardedLock { data: [1, 2, 3] } } and ShardedLockWriteGuard { lock: ShardedLock { data: <locked> } }.

Nothing public is removed. The relaxed bound only adds impls.

🤖 Generated with Claude Code

The Debug impls of ShardedLockReadGuard and ShardedLockWriteGuard
(crossbeam-utils/src/sync/sharded_lock.rs:506 and :542 before this
change) print `.field("lock", &self.lock)`, which goes through
ShardedLock's own Debug impl, and that one calls try_read(). A
ShardedLockWriteGuard holds the write lock of every shard, so that
try_read() always returns WouldBlock and a write guard always prints

    ShardedLockWriteGuard { lock: ShardedLock { data: <locked> } }

instead of the value its holder has exclusive access to. A read guard
takes a second read lock on its own shard just to print itself, and
also prints `<locked>` when that attempt fails, for example on Linux
while a writer is waiting on that shard.

This is the implementation std's RwLock guards had until
rust-lang/rust#83561 (Rust 1.53) changed it for the same reason, as
rust-lang/rust#57703 had done for MutexGuard. std's guards now use
`(**self).fmt(f)` with a `T: ?Sized + fmt::Debug` bound (the `?Sized`
came in rust-lang/rust#117138). Do the same here. This matches the
Display impls of both guards just below them, which already use
`(**self).fmt(f)` with `T: ?Sized`, and makes the guards Debug for
unsized T such as `[i32]`, which ShardedLock supports (see the existing
unsized_type test).

The new guard_debug test checks the output of both guards for a
Vec<i32> lock and for a [i32] lock.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@MaxFreedomPollard

Copy link
Copy Markdown
Contributor Author

The miri channel failure looks unrelated: this only touches ShardedLock's Debug impl, and the same job has been failing intermittently in the scheduled runs on main (e.g. 2026-10-22).

@taiki-e taiki-e left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@taiki-e
taiki-e merged commit cc440fb into crossbeam-rs:main Oct 31, 2026
58 of 60 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants