stringtheory/interning/
map.rs

1#[cfg(not(feature = "loom"))]
2use std::sync::{
3    atomic::{
4        AtomicUsize,
5        Ordering::{AcqRel, Acquire},
6    },
7    Arc, Mutex,
8};
9use std::{collections::HashMap, num::NonZeroUsize, ptr::NonNull};
10
11#[cfg(feature = "loom")]
12use loom::sync::{
13    atomic::{
14        AtomicUsize,
15        Ordering::{AcqRel, Acquire},
16    },
17    Arc, Mutex,
18};
19
20use super::{
21    helpers::{layout_for_data, PackedLengthCapacity},
22    InternedString, Interner,
23};
24use crate::interning::helpers::{aligned_string, ReclaimedEntries, ReclaimedEntry};
25
26const HEADER_LEN: usize = std::mem::size_of::<EntryHeader>();
27const HEADER_ALIGN: usize = std::mem::align_of::<EntryHeader>();
28
29/// The minimum possible length of an entry.
30///
31/// For any entry in the interner, there is already an `EntryHeader` followed by the string data itself. In order to
32/// ensure that entries can be written contiguously, we additionally ensure that the number of bytes we utilize for the
33/// string data is aligned at least as much as `EntryHeader` itself.
34///
35/// This means that the minimum possible length of an entry, or the minimum number of bytes a valid entry could consume,
36/// is the length of the header plus the alignment of the header.
37const MINIMUM_ENTRY_LEN: usize = HEADER_LEN + HEADER_ALIGN;
38
39#[derive(Debug)]
40pub(crate) struct StringState {
41    interner: Arc<Mutex<InternerState>>,
42    header: NonNull<EntryHeader>,
43}
44
45impl StringState {
46    #[inline]
47    pub const fn as_str(&self) -> &str {
48        // SAFETY: We ensure `self.header` is well-aligned and points to an initialized `EntryHeader` value when creating `StringState`.
49        unsafe { get_entry_string(self.header) }
50    }
51}
52
53impl PartialEq for StringState {
54    fn eq(&self, other: &Self) -> bool {
55        self.header == other.header
56    }
57}
58
59impl Clone for StringState {
60    fn clone(&self) -> Self {
61        // SAFETY: The caller that creates `StringState` is responsible for ensuring that `self.header` is well-aligned
62        // and points to an initialized `EntryHeader` value.
63        let header = unsafe { self.header.as_ref() };
64        header.increment_active_refs();
65
66        Self {
67            interner: self.interner.clone(),
68            header: self.header,
69        }
70    }
71}
72
73impl Drop for StringState {
74    fn drop(&mut self) {
75        // SAFETY: The caller that creates `StringState` is responsible for ensuring that `self.header` is well-aligned
76        // and points to an initialized `EntryHeader` value.
77        let header = unsafe { self.header.as_ref() };
78        if header.decrement_active_refs() {
79            // We decremented the reference count to zero, so try to mark this entry for reclamation.
80            let mut interner = self.interner.lock().unwrap();
81            interner.mark_for_reclamation(self.header);
82        }
83    }
84}
85
86/// Metadata about an interner entry.
87///
88/// `EntryHeader` represents the smallest amount of information about an interned entry that's needed to support both
89/// lookup of existing interned strings, as well as the ability to reclaim space in the interner when an entry is no
90/// longer in use.
91struct EntryHeader {
92    /// The number of active references to this entry.
93    ///
94    /// Only incremented by the interner itself, and decremented by `InternedString` when it's dropped.
95    refs: AtomicUsize,
96
97    /// Combined length/capacity of the entry, in terms of the string itself.
98    ///
99    /// Notably, this does _not_ include the length of the header itself. For example, an entry holding the string
100    /// "hello, world!" has a string length of 13 bytes, but since we've to pad out to meet our alignment requirements
101    /// for `EntryHeader`, we would end up with a capacity of 16 bytes. As such, `EntryHeader::len` would report `13`,
102    /// while `EntryHeader::capacity` would report `16`. Likewise, `EntryHeader::entry_len` would report `40`,
103    /// accounting for the string capacity (16) as well as the header length itself (24).
104    ///
105    /// As explained in the description of `PackedLengthCapacity`, this does mean strings can't be larger than ~4 GB on
106    /// 64-bit platforms, which isn't a problem we've.
107    len_cap: PackedLengthCapacity,
108}
109
110impl EntryHeader {
111    /// Creates a tombstone entry with the given capacity.
112    ///
113    /// This is to allow for updating a region in the data buffer, which has been reclaimed, such that it's
114    /// identifiable as being unused.
115    fn tombstone(entry: ReclaimedEntry) -> Self {
116        // The usable capacity for a reclaimed entry is the full capacity minus the size of `EntryHeader` itself, as
117        // reclaimed entries represent the _entire_ region in the data buffer, but `EntryHeader` only cares about the
118        // string portion itself.
119        let str_cap = EntryHeader::usable_from_reclaimed(entry);
120
121        Self {
122            refs: AtomicUsize::new(0),
123            len_cap: PackedLengthCapacity::new(str_cap, 0),
124        }
125    }
126
127    /// Creates a new entry for the given string.
128    fn from_string(s: &str) -> Self {
129        // We're dictating the necessary capacity here, which is the length of the string rounded to the nearest
130        // multiple of the alignment of `EntryHeader`, which ensures that any subsequent entry will be properly aligned.
131        let str_cap = aligned_string::<Self>(s);
132
133        Self {
134            refs: AtomicUsize::new(1),
135            len_cap: PackedLengthCapacity::new(str_cap, s.len()),
136        }
137    }
138
139    /// Creates a new entry for the given string, based on the given reclaimed entry.
140    ///
141    /// This maps the entry header to the underlying capacity of the given reclaimed entry, which is done in cases where
142    /// a reclaimed entry is being used when interning a new string, and the reclaimed entry is larger than the string
143    /// being interned, but not large enough that we could split the excess capacity into a new reclaimed entry.
144    fn from_reclaimed_entry(mut entry: ReclaimedEntry, s: &str) -> (Self, Option<ReclaimedEntry>) {
145        // The usable capacity for a reclaimed entry is the full capacity minus the size of `EntryHeader` itself, as
146        // reclaimed entries represent the _entire_ region in the data buffer, but `EntryHeader` only cares about the
147        // string portion itself.
148        let entry_str_cap = EntryHeader::usable_from_reclaimed(entry);
149        let required_str_cap = aligned_string::<Self>(s);
150
151        // If the reclaimed entry has enough additional space beyond what we need for the string, we'll split it off and
152        // return it for the caller to keep around in the reclaimed entries list.
153        let remainder = entry_str_cap - required_str_cap;
154        let (adjusted_str_cap, maybe_split_entry) = if remainder >= MINIMUM_ENTRY_LEN {
155            let entry_len = EntryHeader::len_for(s);
156            let split_entry = entry.split_off(entry_len);
157
158            (entry_len - HEADER_LEN, Some(split_entry))
159        } else {
160            (entry_str_cap, None)
161        };
162
163        let header = Self {
164            refs: AtomicUsize::new(1),
165            len_cap: PackedLengthCapacity::new(adjusted_str_cap, s.len()),
166        };
167
168        (header, maybe_split_entry)
169    }
170
171    /// Returns the computed length of a complete entry, in bytes, for the given string.
172    ///
173    /// This includes the size of the entry header itself and the string data, when padded for alignment, and represents
174    /// the number of bytes that would be consumed in the data buffer.
175    const fn len_for(s: &str) -> usize {
176        HEADER_LEN + aligned_string::<Self>(s)
177    }
178
179    /// Returns the usable capacity of a reclaimed entry, in bytes.
180    ///
181    /// Usable refers to the number of bytes in a reclaimed entry that could be used for string data, after accounting
182    /// for the size of `EntryHeader` itself.
183    const fn usable_from_reclaimed(entry: ReclaimedEntry) -> usize {
184        entry.capacity() - HEADER_LEN
185    }
186
187    /// Returns the size of the string, in bytes, that this entry can hold.
188    const fn capacity(&self) -> usize {
189        self.len_cap.capacity()
190    }
191
192    /// Returns the size of the string, in bytes, that this entry _actually_ holds.
193    const fn len(&self) -> usize {
194        self.len_cap.len()
195    }
196
197    /// Returns the total length of the entry, in bytes.
198    ///
199    /// This includes the length of the header in addition to the string data.
200    const fn entry_len(&self) -> usize {
201        HEADER_LEN + self.capacity()
202    }
203
204    /// Returns `true` if this entry is currently referenced.
205    fn is_active(&self) -> bool {
206        self.refs.load(Acquire) != 0
207    }
208
209    /// Increments the active reference count by one.
210    fn increment_active_refs(&self) {
211        self.refs.fetch_add(1, AcqRel);
212    }
213
214    /// Decrements the active reference count by one.
215    ///
216    /// Returns `true` if the active reference count is zero _after_ calling this method.
217    fn decrement_active_refs(&self) -> bool {
218        self.refs.fetch_sub(1, AcqRel) == 1
219    }
220}
221
222// SAFETY: We don't take references to the entry header pointer that outlast `StringState`, and the only modification we
223// do to the entry header is through atomic operations, so it's safe to both send and share `StringState` between
224// threads.
225unsafe impl Send for StringState {}
226unsafe impl Sync for StringState {}
227
228#[derive(Debug)]
229struct InternerStorage {
230    // Direct pieces of our buffer allocation.
231    ptr: NonNull<u8>,
232    offset: usize,
233    capacity: NonZeroUsize,
234
235    // Length of all active entries, in bytes.
236    //
237    // This is equivalent to `self.offset` minus the total size of all reclaimed entries.
238    len: usize,
239
240    // Markers for entries that can be reused.
241    reclaimed: ReclaimedEntries,
242}
243
244impl InternerStorage {
245    fn with_capacity(capacity: NonZeroUsize) -> Self {
246        assert!(
247            capacity.get() <= isize::MAX as usize,
248            "capacity would overflow isize::MAX, which violates layout constraints"
249        );
250
251        // Allocate our data buffer. This is the main backing allocation for all interned strings, and is well-aligned
252        // for `EntryHeader`.
253        //
254        // SAFETY: `layout_for_data` ensures the layout is non-zero.
255        let data_layout = layout_for_data::<EntryHeader>(capacity);
256        let data_ptr = unsafe { std::alloc::alloc(data_layout) };
257        let ptr = match NonNull::new(data_ptr) {
258            Some(ptr) => ptr,
259            None => std::alloc::handle_alloc_error(data_layout),
260        };
261
262        Self {
263            ptr,
264            offset: 0,
265            capacity,
266            len: 0,
267            reclaimed: ReclaimedEntries::new(),
268        }
269    }
270
271    #[cfg(test)]
272    /// Returns the total number of unused bytes that are available for interning.
273    fn available(&self) -> usize {
274        self.capacity.get() - self.len
275    }
276
277    /// Returns the total number of bytes of contiguous, unoccupied space at the end of the data buffer.
278    fn available_unoccupied(&self) -> usize {
279        self.capacity.get() - self.offset
280    }
281
282    fn get_entry_ptr(&self, offset: usize) -> NonNull<EntryHeader> {
283        debug_assert!(
284            offset + MINIMUM_ENTRY_LEN <= self.capacity.get(),
285            "offset would point to entry that cannot possibly avoid extending past end of data buffer"
286        );
287
288        // SAFETY: The caller is responsible for ensuring that `offset` is within the bounds of the data buffer, and
289        // that `offset` is well-aligned for `EntryHeader`.
290        let entry_ptr = unsafe { self.ptr.as_ptr().add(offset).cast::<EntryHeader>() };
291        debug_assert!(entry_ptr.is_aligned(), "entry header pointer must be well-aligned");
292
293        // SAFETY: `entry_ptr` is derived from `self.ptr`, which itself is `NonNull<u8>`, and the caller is responsible
294        // for ensuring that `offset` is within the bounds of the data buffer, so we know `entry_ptr` is non-null.
295        unsafe { NonNull::new_unchecked(entry_ptr) }
296    }
297
298    fn write_entry(&mut self, offset: usize, entry_header: EntryHeader, s: &str) -> NonNull<EntryHeader> {
299        debug_assert_eq!(
300            entry_header.len(),
301            s.len(),
302            "entry header length must match string length"
303        );
304
305        let entry_ptr = self.get_entry_ptr(offset);
306        let entry_len = entry_header.entry_len();
307
308        // Write the entry header.
309        unsafe { entry_ptr.as_ptr().write(entry_header) };
310
311        let s_buf = s.as_bytes();
312
313        // Write the string.
314        let entry_s_buf = unsafe {
315            // Take the entry pointer and add 1, which sets our pointer to right _after_ the header.
316            let entry_s_ptr = entry_ptr.as_ptr().add(1).cast::<u8>();
317            std::slice::from_raw_parts_mut(entry_s_ptr, s_buf.len())
318        };
319        entry_s_buf.copy_from_slice(s_buf);
320
321        // Update our internal statistics.
322        self.len += entry_len;
323
324        entry_ptr
325    }
326
327    fn write_to_unoccupied(&mut self, s: &str) -> (usize, NonNull<EntryHeader>) {
328        let entry_header = EntryHeader::from_string(s);
329
330        // Write the entry to the end of the data buffer.
331        let entry_offset = self.offset;
332        self.offset += entry_header.entry_len();
333
334        (entry_offset, self.write_entry(entry_offset, entry_header, s))
335    }
336
337    fn write_to_reclaimed_entry(&mut self, entry: ReclaimedEntry, s: &str) -> (usize, NonNull<EntryHeader>) {
338        let entry_offset = entry.offset();
339        let (entry_header, maybe_split_entry) = EntryHeader::from_reclaimed_entry(entry, s);
340
341        // If we had enough capacity in the reclaimed entry to hold this string _and_ potentially hold another entry, we
342        // split it off and store that remainder entry.
343        if let Some(split_entry) = maybe_split_entry {
344            self.add_reclaimed(split_entry);
345        }
346
347        // Write the entry in place of the reclaimed entry.
348        (entry_offset, self.write_entry(entry_offset, entry_header, s))
349    }
350
351    fn add_reclaimed(&mut self, entry: ReclaimedEntry) {
352        // Reclamation is a two-step process: first, we have to actually keep track of the reclaimed entry, which
353        // potentially involves merging adjacent reclaimed entries, and then once all of that has happened, we tombstone
354        // the entry (whether merged or not).
355        //
356        // However, if the merged reclaimed entry immediately precedes any available capacity, we can skip tombstoning
357        // it, since we can just wind back `offset` to reclaim the space.
358        let merged_entry = self.reclaimed.insert(entry);
359        if merged_entry.offset() + merged_entry.capacity() == self.offset {
360            self.offset -= merged_entry.capacity();
361            self.reclaimed.remove(&merged_entry);
362            return;
363        }
364
365        self.clear_reclaimed_entry(merged_entry);
366    }
367
368    fn clear_reclaimed_entry(&mut self, entry: ReclaimedEntry) {
369        let entry_ptr = self.get_entry_ptr(entry.offset);
370
371        // Write the entry tombstone itself, which clears out the hash and sets the reference count to zero.
372        //
373        // SAFETY: We know that `entry_ptr` is valid for writes (reclaimed entries are, by definition, inactive regions
374        // in the data buffer) and is well-aligned for `EntryHeader`.
375        let tombstone = EntryHeader::tombstone(entry);
376        let str_cap = tombstone.capacity();
377
378        unsafe {
379            entry_ptr.as_ptr().write(EntryHeader::tombstone(entry));
380        }
381
382        // Write a magic value to the entire string capacity for the entry. This ensures that there's a known repeating
383        // value which, in the case of debugging issues, can be a signal that offsets/reclaimed entries are incorrect
384        // and overlapping with active entries.
385        //
386        // SAFETY: Like above, the caller is responsible for ensuring that `offset` is within the bounds of the data
387        // buffer, and that `offset + capacity` does not extend past the bounds of the data buffer.
388        unsafe {
389            // Take the entry pointer and add 1, which sets our pointer to right _after_ the header.
390            let str_ptr = entry_ptr.as_ptr().add(1).cast::<u8>();
391            let str_buf = std::slice::from_raw_parts_mut(str_ptr, str_cap);
392            str_buf.fill(0x21);
393        }
394    }
395
396    /// Returns the byte offset of the given entry header within the data buffer.
397    fn offset_of(&self, header_ptr: NonNull<EntryHeader>) -> usize {
398        // SAFETY: The caller is responsible for ensuring the entry header reference belongs to this interner. If that
399        // is upheld, then we know that entry header belongs to our data buffer, and that the pointer to the entry
400        // header is not less than the base pointer of the data buffer, ensuring the offset is non-negative.
401        let entry_offset = unsafe {
402            header_ptr
403                .cast::<u8>()
404                .as_ptr()
405                .offset_from(self.ptr.as_ptr().cast_const())
406        };
407        debug_assert!(entry_offset >= 0, "entry offset must be non-negative");
408
409        entry_offset as usize
410    }
411
412    fn mark_for_reclamation(&mut self, header_ptr: NonNull<EntryHeader>) {
413        let entry_offset = self.offset_of(header_ptr);
414
415        let header = unsafe { header_ptr.as_ref() };
416        let entry_len = header.entry_len();
417
418        let entry = ReclaimedEntry::new(entry_offset, entry_len);
419        self.len -= entry.capacity();
420        self.add_reclaimed(entry);
421    }
422}
423
424impl Drop for InternerStorage {
425    fn drop(&mut self) {
426        // SAFETY: We allocated this buffer with the global allocator, and we're generating the same layout that was
427        // used to allocate it in the first place.
428        unsafe {
429            std::alloc::dealloc(self.ptr.as_ptr(), layout_for_data::<EntryHeader>(self.capacity));
430        }
431    }
432}
433
434#[derive(Debug)]
435struct InternerState {
436    // Backing storage for the interned strings.
437    storage: InternerStorage,
438
439    // Active entries in the interner.
440    entries: HashMap<&'static str, usize>,
441}
442
443impl InternerState {
444    /// Creates a new `InternerState` with a pre-allocated buffer that has the given capacity.
445    pub fn with_capacity(capacity: NonZeroUsize) -> Self {
446        Self {
447            storage: InternerStorage::with_capacity(capacity),
448            entries: HashMap::new(),
449        }
450    }
451
452    fn mark_for_reclamation(&mut self, header_ptr: NonNull<EntryHeader>) {
453        // Reclamation must happen exactly once per entry, even though more than one thread can legitimately observe the
454        // reference count drop to zero for the same slot.
455        //
456        // `StringState::drop` decrements the reference count _before_ taking this lock, so within that window the entry
457        // can still be found -- and resurrected (`refs` 0 -> 1) -- by a concurrent `try_intern`. If that resurrecting
458        // handle is then also dropped (`refs` 1 -> 0), two different droppers each saw a 1 -> 0 transition and will
459        // each reach this method observing `refs == 0`. A bare `is_active()` check is therefore not enough on its own
460        // to decide who reclaims: reclaiming on both would double-subtract `storage.len` and insert an overlapping
461        // reclaimed entry, corrupting the data buffer.
462        //
463        // We guard against that with two checks, both performed under this lock:
464        //
465        //   1. `is_active()`: if a resurrecting handle is still live (`refs > 0`), we must NOT reclaim. The reference
466        //      count cannot rise while we hold the lock -- cloning needs a live handle, and `try_intern` needs this
467        //      lock -- so observing zero here means it stays zero through the reclamation below (no use-after-free).
468        //   2. Map identity: reclaim only if the entries map still maps this entry's string to _this_ entry's offset.
469        //      The first dropper to reclaim removes that mapping, so any later dropper of the same slot sees the string
470        //      gone (or remapped to a newer entry) and skips -- making reclamation exactly-once.
471        //
472        // SAFETY: The caller is responsible for ensuring that `header_ptr` is well-aligned and points to an initialized
473        // `EntryHeader` value that was acquired from this interner.
474        let header = unsafe { header_ptr.as_ref() };
475        if header.is_active() {
476            return;
477        }
478
479        let entry_str = unsafe { get_entry_string(header_ptr) };
480        let entry_offset = self.storage.offset_of(header_ptr);
481        if self.entries.get(entry_str) == Some(&entry_offset) {
482            // Remove the entry from the entries map first before we reclaim the entry, since doing so overwrites the
483            // entry data in the data buffer.
484            self.entries.remove(entry_str);
485            self.storage.mark_for_reclamation(header_ptr);
486        }
487    }
488
489    fn try_intern(&mut self, s: &str) -> Option<NonNull<EntryHeader>> {
490        // We can only intern strings with a size that fits within a packed length/capacity value, so if `s` is larger
491        // than that, we can't intern it, and there's existing entry we could have for it either.
492        if s.len() > PackedLengthCapacity::maximum_value() {
493            return None;
494        }
495
496        // Try and find an existing entry for this string.
497        if let Some(entry_offset) = self.entries.get(s) {
498            let entry_ptr = self.storage.get_entry_ptr(*entry_offset);
499
500            let header = unsafe { entry_ptr.as_ref() };
501            header.increment_active_refs();
502
503            return Some(entry_ptr);
504        }
505
506        let required_cap = EntryHeader::len_for(s);
507
508        // We didn't find an existing entry, so we're going to intern it.
509        //
510        // First, try and see if we have a reclaimed entry that can fit this string. If nothing suitable is found, or we
511        // have no reclaimed entries, then we'll just try to fit it in the remaining capacity of our data buffer.
512        let maybe_reclaimed_entry = self.storage.reclaimed.take_if(|entry| entry.capacity() >= required_cap);
513        let (entry_offset, entry_ptr) = if let Some(reclaimed_entry) = maybe_reclaimed_entry {
514            saluki_antithesis::sometimes!(true, "reclaimed interner slot reused for a new string");
515            self.storage.write_to_reclaimed_entry(reclaimed_entry, s)
516        } else if required_cap <= self.storage.available_unoccupied() {
517            self.storage.write_to_unoccupied(s)
518        } else {
519            // We don't have enough space to intern this string at all, so we'll just return `None`.
520            saluki_antithesis::sometimes!(true, "interner full — no capacity to intern");
521            return None;
522        };
523
524        // SAFETY: Callers of `get_entry_string` are responsible for ensuring that the chosen lifetime of the string is
525        // valid with respect to ensuring that the underlying entry lives long enough, and that the string data is valid
526        // UTF-8 from as long as the reference is live.
527        //
528        // We're creating a `'static` reference here, which is generally frowned upon when the data is in fact not
529        // `'static`, but this is safe because we never leak this lifetime outside of the interner, and we ensure that
530        // we don't actually keep this reference around longer than the entry itself, as we only need a `'static`
531        // reference to use it as the key in the entries map.
532        let entry_str = unsafe { get_entry_string(entry_ptr) };
533        self.entries.insert(entry_str, entry_offset);
534
535        Some(entry_ptr)
536    }
537}
538
539// SAFETY: We don't take references to the data buffer pointer that outlast `InternerState`, and all access to
540// `InternerState` itself is mediated through a mutex, so we're safe to send it around and share it between threads.
541unsafe impl Send for InternerState {}
542unsafe impl Sync for InternerState {}
543
544/// A string interner based on a single, fixed-size backing buffer with support for reclamation.
545///
546/// ## Overview
547///
548/// This interner uses a single, fixed-size backing buffer where interned strings are stored contiguously. This provides
549/// bounded memory usage, and the interner won't allocate additional memory for new strings once the buffer is full.
550/// Since interned strings aren't likely to need to live for the life of the program, the interner supports
551/// reclamation. Once all references to an interned string have been dropped, the storage for that string is reclaimed
552/// and can be used to hold new strings.
553///
554/// ## Storage layout
555///
556/// The backing buffer stores strings contiguously, with an entry "header" prepended to each string. The header contains
557/// relevant data -- hash of the string, reference count, and length of the string -- needed to work with the entry
558/// either when searching for existing entries or when using the entry itself.
559///
560/// The layout of an entry is as follows:
561///
562/// ```text
563/// ┌───────────────────────── entry #1 ──────────────────────────┐ ┌─ entry #2 ─┐ ┌─ entry .. ─┐
564/// ▼                                                             ▼ ▼            ▼ ▼            ▼
565/// ┏━━━━━━━━━━━┯━━━━━━━━━━━┯━━━━━━━━━━━┯━━━━━━━━━━━┯━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━┓
566/// ┃ str hash  │  ref cnt  │  str len  │ str data  │   padding   ┃ ┃   header   ┃ ┃   header   ┃
567/// ┃ (8 bytes) │ (8 bytes) │ (8 bytes) │ (N bytes) │ (1-7 bytes) ┃ ┃  & string  ┃ ┃  & string  ┃
568/// ┗━━━━━━━━━━━┷━━━━━━━━━━━┷━━━━━━━━━━━┷━━━━━━━━━━━┷━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━┛
569/// ▲                                   ▲                           ▲
570/// └────────── `EntryHeader` ──────────┘                           └── aligned for `EntryHeader`
571///          (8 byte alignment)                                         via trailing padding
572/// ```
573///
574/// The backing buffer is always aligned properly for `EntryHeader`, so that the first entry can be referenced
575/// correctly. However, when appending additional entries to the buffer, we need to ensure that those entries also have
576/// an aligned start for accessing the header. This is complicated due to the variable number of bytes for the string
577/// data.
578///
579/// Alignment padding is added to the end of the entry to ensure that when appending the next entry, the start of the
580/// entry is properly aligned for `EntryHeader`. In the worst case, up to 7 bytes could be added (and thus wasted) on
581/// this alignment padding.
582///
583/// ## `InternedString`
584///
585/// The `InternedString` type is a handle to the entry header, and thus the string data, for an interned string. It's
586/// designed to be small -- 8 bytes. -- and cheap to clone, as it contains an atomic reference to the entry header and a
587/// reference to the interner that owns the string. It dereferences to the underlying string with relatively low
588/// overhead: two pointer indirections.
589///
590/// When an `InternedString` is dropped, it decrements the reference count for the entry it points to. If the reference
591/// count drops to zero, it will attempt to mark the entry for reclamation.
592///
593/// ## Reclamation
594///
595/// As we want to bound the memory used by the interner, but also not allow it to be filled up with strings that
596/// eventually end up going entirely unused, we need a way to remove those unused strings so their underlying storage
597/// can be used for new strings. This is where reclamation comes in.
598///
599/// When a string is interned, the entry header tracks how many active references there are to it. When that reference
600/// count drops to zero, the last reference to the string attempts to mark the entry for reclamation. Assuming no other
601/// reference has been taken out on the entry in the meantime, the entry gets added to a list of "reclaimed" entries.
602///
603/// Reclaimed entries are simple markers -- start and end position in the data buffer -- which are stored in a freelist.
604/// When attempting to intern a new string, this freelist is searched to see if there's an entry large enough to fit the
605/// new string, and if so, it's used.
606///
607/// Additionally, when entries are reclaimed, adjacent entries are merged together where possible. This helps to avoid
608/// unnecessary fragmentation over time, although not as effectively as reconstructing the data buffer to re-pack
609/// entries.
610#[derive(Clone, Debug)]
611pub struct GenericMapInterner {
612    state: Arc<Mutex<InternerState>>,
613}
614
615impl GenericMapInterner {
616    /// Creates a new `GenericMapInterner` with the given capacity.
617    ///
618    /// The given capacity will potentially be rounded up by a small number of bytes (up to 7) in order to ensure the
619    /// backing buffer is properly aligned.
620    pub fn new(capacity: NonZeroUsize) -> Self {
621        Self {
622            state: Arc::new(Mutex::new(InternerState::with_capacity(capacity))),
623        }
624    }
625}
626
627impl Interner for GenericMapInterner {
628    fn is_empty(&self) -> bool {
629        self.state.lock().unwrap().entries.is_empty()
630    }
631
632    fn len(&self) -> usize {
633        self.state.lock().unwrap().entries.len()
634    }
635
636    fn len_bytes(&self) -> usize {
637        self.state.lock().unwrap().storage.len
638    }
639
640    fn capacity_bytes(&self) -> usize {
641        self.state.lock().unwrap().storage.capacity.get()
642    }
643
644    fn try_intern(&self, s: &str) -> Option<InternedString> {
645        let header = {
646            let mut state = self.state.lock().unwrap();
647            state.try_intern(s)?
648        };
649
650        Some(InternedString::from(StringState {
651            interner: Arc::clone(&self.state),
652            header,
653        }))
654    }
655}
656
657#[inline]
658const unsafe fn get_entry_string_parts(header_ptr: NonNull<EntryHeader>) -> (NonNull<u8>, usize) {
659    // SAFETY: The caller is responsible for ensuring that `header_ptr` is well-aligned and points to an initialized
660    // `EntryHeader` value.
661    let header = header_ptr.as_ref();
662
663    // Advance past the header and get the pointer to the string.
664    //
665    // SAFETY: We know that we're simply skipping over the header by advancing the pointer by one when it's still typed
666    // as `*mut EntryHeader`.
667    let s_ptr = header_ptr.add(1).cast::<u8>();
668    (s_ptr, header.len())
669}
670
671#[inline]
672const unsafe fn get_entry_string<'a>(header_ptr: NonNull<EntryHeader>) -> &'a str {
673    let (s_ptr, s_len) = get_entry_string_parts(header_ptr);
674
675    // SAFETY: We depend on `get_entry_string_parts` to give us a valid pointer and length for the string.
676    std::str::from_utf8_unchecked(std::slice::from_raw_parts(s_ptr.as_ptr() as *const _, s_len))
677}
678
679#[cfg(test)]
680mod tests {
681    use std::ops::Deref as _;
682
683    use prop::sample::Index;
684    use proptest::{collection::vec as arb_vec, prelude::*};
685
686    use super::*;
687    use crate::interning::{test_support, InternedStringState};
688
689    pub(super) fn create_interner(capacity: usize) -> GenericMapInterner {
690        assert!(capacity > 0, "capacity must be greater than zero");
691        GenericMapInterner::new(NonZeroUsize::new(capacity).unwrap())
692    }
693
694    fn available_len(interner: &GenericMapInterner) -> usize {
695        interner.state.lock().unwrap().storage.available()
696    }
697
698    fn reclaimed_len(interner: &GenericMapInterner) -> usize {
699        interner.state.lock().unwrap().storage.reclaimed.len()
700    }
701
702    fn first_reclaimed_entry(interner: &GenericMapInterner) -> ReclaimedEntry {
703        interner.state.lock().unwrap().storage.reclaimed.first().unwrap()
704    }
705
706    const fn entry_len(s: &str) -> usize {
707        EntryHeader::len_for(s)
708    }
709
710    pub(super) fn get_reclaimed_entry_for_string(s: &InternedString) -> ReclaimedEntry {
711        let state = match &s.state {
712            InternedStringState::GenericMap(state) => state,
713            _ => panic!("unexpected string state"),
714        };
715
716        let ptr = state.interner.lock().unwrap().storage.ptr.as_ptr();
717        let header = unsafe { state.header.as_ref() };
718        let offset = unsafe { state.header.as_ptr().cast::<u8>().offset_from(ptr) as usize };
719        ReclaimedEntry::new(offset, header.entry_len())
720    }
721
722    #[test]
723    fn basic() {
724        let interner = create_interner(1024);
725
726        let s1 = interner.try_intern("hello").unwrap();
727        let s2 = interner.try_intern("world").unwrap();
728        let s3 = interner.try_intern("hello").unwrap();
729
730        assert_eq!(s1.deref(), "hello");
731        assert_eq!(s2.deref(), "world");
732        assert_eq!(s3.deref(), "hello");
733
734        // The pointers from the interned strings should be the same, but not between the interned string and a pointer
735        // to an equivalent (but not interned) string:
736        assert!(std::ptr::eq(s1.deref() as *const _, s3.deref() as *const _));
737
738        let local_hello = "hello";
739        assert!(!std::ptr::eq(s1.deref() as *const _, local_hello as *const _));
740    }
741
742    #[test]
743    fn try_intern_without_capacity() {
744        // Big enough to fit a single "hello world!" string, but not big enough to fit two.
745        let interner = create_interner(48);
746
747        let s1 = interner.try_intern("hello world!");
748        assert!(s1.is_some());
749
750        let s2 = interner.try_intern("hello, world");
751        assert!(s2.is_none());
752    }
753
754    #[test]
755    fn reclaim_after_dropped() {
756        let interner = create_interner(1024);
757
758        let s1 = interner.try_intern("hello world!").expect("should not fail to intern");
759        let s1_entry_len = entry_len(&s1);
760
761        assert_eq!(interner.len(), 1);
762        assert_eq!(available_len(&interner), 1024 - s1_entry_len);
763        assert_eq!(reclaimed_len(&interner), 0);
764
765        // Drop the interned string, which should decrement the reference count to zero and then reclaim the entry.
766        drop(s1);
767
768        assert_eq!(interner.len(), 0);
769        assert_eq!(available_len(&interner), 1024);
770        assert_eq!(reclaimed_len(&interner), 0);
771    }
772
773    #[test]
774    fn interns_to_reclaimed_entry_with_leftover() {
775        // We want to intern a string initially, which takes up almost all of the capacity, and then drop it so it gets
776        // reclaimed. After that, we'll intern a much smaller string which should lead to utilizing that reclaimed
777        // entry, but only a part of it. Finally, we'll intern another new string.
778        //
779        // The point is to demonstrate that our reclamation logic is sound in terms of allowing reclaimed entries to be
780        // split while the search/insertion logic is operating.
781        let capacity = 256;
782        let interner = create_interner(capacity);
783
784        // We craft four strings such that the first two (`s_large` and `s_medium1`) will take up enough capacity that
785        // `s_small` can't possibly be interned in the available capacity. We'll also craft `s_medium2` so it can fit
786        // within the reclaimed entry for `s_large` but takes enough capacity that `s_small` cannot fit in the leftover
787        // reclaimed entry that we split off.
788        let s_large = "99 bottles of beer on the wall, 99 bottles of beer! take one down, pass it around, 98 bottles of beer on the wall!";
789        let s_medium1 = "no act of kindness, no matter how small, is ever wasted";
790        let s_medium2 = "if you want to go fast, go alone; if you want to go far, go together";
791        let s_small = "are you there god? it's me, margaret";
792
793        let phase1_available_capacity = capacity - entry_len(s_large) - entry_len(s_medium1);
794        assert!(phase1_available_capacity < entry_len(s_small));
795        assert!((entry_len(s_large) - entry_len(s_medium2)) < entry_len(s_small));
796        assert!(entry_len(s_medium2) < entry_len(s_large));
797
798        // Phase 1: intern our two larger strings.
799        let s1 = interner.try_intern(s_large).expect("should not fail to intern");
800        let s2 = interner.try_intern(s_medium1).expect("should not fail to intern");
801
802        assert_eq!(interner.len(), 2);
803        assert_eq!(available_len(&interner), phase1_available_capacity);
804        assert_eq!(reclaimed_len(&interner), 0);
805
806        // Phase 2: drop `s_large` so it gets reclaimed.
807        drop(s1);
808        assert_eq!(interner.len(), 1);
809        assert_eq!(reclaimed_len(&interner), 1);
810
811        // Phase 3: intern `s_medium2`, which should fit in the reclaimed entry for `s_large`. This should leave a
812        // small, split off reclaimed entry.
813        let s3 = interner.try_intern(s_medium2).expect("should not fail to intern");
814
815        assert_eq!(interner.len(), 2);
816        assert_eq!(reclaimed_len(&interner), 1);
817
818        // Phase 4: intern `s_small`, which should not fit in the leftover reclaimed entry from `s_large` _or_ the
819        // available capacity.
820        let s4 = interner.try_intern(s_small);
821        assert_eq!(s4, None);
822
823        assert_eq!(interner.len(), 2);
824        assert_eq!(reclaimed_len(&interner), 1);
825
826        // And make sure we can still dereference the interned strings we _do_ have left:
827        assert_eq!(s2.deref(), s_medium1);
828        assert_eq!(s3.deref(), s_medium2);
829    }
830
831    #[test]
832    fn has_reclaimed_entries_string_fits_exactly() {
833        // The interner is large enough to fit two of the identically-sized strings, but not all three. We show that
834        // when we drop an entry and it is reclaimed, a string of identical size should always be able to reuse that
835        // reclaimed entry.
836        const S1_VALUE: &str = "hello world!";
837        const S2_VALUE: &str = "hello, world";
838        const S3_VALUE: &str = "hello--world";
839
840        let interner = create_interner(80);
841
842        // Intern the first two strings, which should fit without issue.
843        let s1 = interner.try_intern(S1_VALUE).expect("should not fail to intern");
844        let s1_reclaimed_expected = get_reclaimed_entry_for_string(&s1);
845        let _s2 = interner.try_intern(S2_VALUE).expect("should not fail to intern");
846
847        assert_eq!(interner.len(), 2);
848        assert_eq!(reclaimed_len(&interner), 0);
849
850        // Try to intern a third string, which should fail as we don't have the space.
851        let s3 = interner.try_intern(S3_VALUE);
852        assert_eq!(s3, None);
853
854        // Drop the first string, which should decrement the reference count to zero and then reclaim the entry.
855        drop(s1);
856
857        assert_eq!(interner.len(), 1);
858        assert_eq!(reclaimed_len(&interner), 1);
859
860        let s1_reclaimed = first_reclaimed_entry(&interner);
861        assert_eq!(s1_reclaimed_expected, s1_reclaimed);
862
863        // Try again to intern the third string, which should now succeed and take over the reclaimed entry entirely
864        // as the strings are identical in length.
865        let _s3 = interner.try_intern(S3_VALUE).expect("should not fail to intern");
866
867        assert_eq!(interner.len(), 2);
868        assert_eq!(reclaimed_len(&interner), 0);
869    }
870
871    #[test]
872    fn reclaimed_entry_reuse_split_too_small() {
873        // This situation is slightly contrived, but: we want to test that when there's a reclaimed entry of a certain
874        // size, reusing that reclaimed entry won't lead to it being split if the resulting split entry would be too
875        // "small": unable to hold another minimum-sized entry.
876        //
877        // We have to intern three strings to do this because we only track reclaimed entries when they're followed by
878        // in-use entries, and the string we drop to create a reclaimed entry has to be big enough, but not _too_ big,
879        // to hold the string we want to intern after it.
880        let interner = create_interner(128);
881
882        // Declare our strings to intern and just check some preconditions by hand.
883        let s_one = "a horse, a horse, my kingdom for a horse!";
884        let s_one_entry_len = entry_len(s_one);
885        let s_two = "why hello there, beautiful";
886        let s_two_entry_len = entry_len(s_two);
887        let s_three = "real gs move in silence like lasagna";
888        let s_three_entry_len = entry_len(s_three);
889
890        assert!(s_one_entry_len <= interner.capacity_bytes());
891        assert!(s_two_entry_len <= interner.capacity_bytes());
892        assert!(s_one_entry_len + s_two_entry_len + s_three_entry_len > interner.capacity_bytes());
893        assert!(s_one_entry_len > s_two_entry_len);
894        assert!(s_three_entry_len > s_two_entry_len);
895        assert!((s_one_entry_len - s_three_entry_len) < MINIMUM_ENTRY_LEN);
896
897        // Intern the first two strings, which should fit without issue.
898        let s1 = interner.try_intern(s_one).expect("should not fail to intern");
899        let s1_reclaimed_expected = get_reclaimed_entry_for_string(&s1);
900        let _s2 = interner.try_intern(s_two).expect("should not fail to intern");
901
902        assert_eq!(interner.len(), 2);
903        assert_eq!(reclaimed_len(&interner), 0);
904
905        // Try to intern the third string, which should fail as we don't have the space.
906        let s3 = interner.try_intern(s_three);
907        assert_eq!(s3, None);
908
909        // Drop the first string, which should decrement the reference count to zero and then reclaim the entry.
910        drop(s1);
911
912        assert_eq!(interner.len(), 1);
913        assert_eq!(reclaimed_len(&interner), 1);
914
915        let s1_reclaimed = first_reclaimed_entry(&interner);
916        assert_eq!(s1_reclaimed_expected, s1_reclaimed);
917
918        // Try again to intern the third string, which should now succeed and take over the reclaimed entry, but since
919        // the remainder of the reclaimed entry after taking the necessary capacity for `s_three` is not large enough
920        // (`MINIMUM_ENTRY_LEN`), we shouldn't end up splitting the reclaimed entry, and instead, `s3` should consume
921        // the entire reclaimed entry.
922        let s3 = interner.try_intern(s_three).expect("should not fail to intern");
923        let s3_reclaimed_expected = get_reclaimed_entry_for_string(&s3);
924
925        assert_eq!(interner.len(), 2);
926        assert_eq!(reclaimed_len(&interner), 0);
927        assert_eq!(s1_reclaimed_expected, s3_reclaimed_expected);
928    }
929
930    #[test]
931    fn reclaimed_entry_adjacent_to_spare_capacity() {
932        let interner = create_interner(128);
933
934        // Intern two smallish strings that fit without issue.
935        let s1 = interner.try_intern("hello, world!").expect("should not fail to intern");
936        let s2 = interner
937            .try_intern("cheeeeeehooooo!")
938            .expect("should not fail to intern");
939        let s1_entry_len = entry_len(&s1);
940        let s2_entry_len = entry_len(&s2);
941
942        assert_eq!(reclaimed_len(&interner), 0);
943        assert_eq!(available_len(&interner), 128 - s1_entry_len - s2_entry_len);
944
945        drop(s2);
946        assert_eq!(reclaimed_len(&interner), 0);
947        assert_eq!(available_len(&interner), 128 - s1_entry_len);
948
949        drop(s1);
950        assert_eq!(reclaimed_len(&interner), 0);
951        assert_eq!(available_len(&interner), 128);
952    }
953
954    #[test]
955    fn len_bytes_reps_active_interned_entries() {
956        let interner = create_interner(256);
957        let mut active_interned_entries_len = 0;
958        let string_1 = "hello world!";
959        let string_2 = "hello again, world";
960        let string_3 = "this is another string";
961
962        // Intern a string.
963        let s1 = interner.try_intern(string_1).expect("should not fail to intern");
964        active_interned_entries_len += entry_len(string_1);
965        assert_eq!(interner.len(), 1);
966        assert_eq!(interner.len_bytes(), active_interned_entries_len);
967
968        // Intern a second string.
969        let _s2 = interner.try_intern(string_2).expect("should not fail to intern");
970        active_interned_entries_len += entry_len(string_2);
971        assert_eq!(interner.len(), 2);
972        assert_eq!(interner.len_bytes(), active_interned_entries_len);
973
974        // Drop the first string.
975        drop(s1);
976        active_interned_entries_len -= entry_len(string_1);
977        assert_eq!(interner.len(), 1);
978        assert_eq!(interner.len_bytes(), active_interned_entries_len);
979
980        // Intern a new string.
981        let _s3 = interner.try_intern(string_3).expect("should not fail to intern");
982        active_interned_entries_len += entry_len(string_3);
983        assert_eq!(interner.len(), 2);
984        assert_eq!(interner.len_bytes(), active_interned_entries_len);
985    }
986
987    proptest! {
988        #[test]
989        #[cfg_attr(miri, ignore)]
990        fn property_test_entry_count_accurate(
991            strs in test_support::arb_alphanum_strings(1..=128, 16..=512),
992            indices in arb_vec(any::<Index>(), 1..=1000),
993        ) {
994            // Create an interner with enough capacity to hold all of the strings we've generated. This is the maximum
995            // string size multiplied by the number of strings we've generated... plus a little constant factor per
996            // string to account for the entry header. The header size differs between interner implementations, which
997            // is exactly why the interner construction (and thus this `property_test_`-named entry point) stays
998            // per-implementation while the invariant check is shared.
999            const ENTRY_SIZE: usize = 128 + HEADER_LEN;
1000            let interner = create_interner(ENTRY_SIZE * indices.len());
1001
1002            test_support::assert_entry_count_matches_unique_strings(&interner, &strs, &indices)?;
1003        }
1004    }
1005}
1006
1007#[cfg(all(test, feature = "loom"))]
1008mod loom_tests {
1009    use std::ops::Deref;
1010
1011    use super::{
1012        tests::{create_interner, get_reclaimed_entry_for_string},
1013        *,
1014    };
1015
1016    #[test]
1017    fn concurrent_drop_and_intern() {
1018        fn reclaimed_entries(interner: &GenericMapInterner) -> Vec<ReclaimedEntry> {
1019            interner
1020                .state
1021                .lock()
1022                .unwrap()
1023                .storage
1024                .reclaimed
1025                .iter()
1026                .copied()
1027                .collect()
1028        }
1029
1030        fn do_reclaimed_entries_overlap(a: ReclaimedEntry, b: ReclaimedEntry) -> bool {
1031            let a_start = a.offset;
1032            let a_end = a.offset + a.capacity - 1;
1033
1034            let b_start = b.offset;
1035            let b_end = b.offset + b.capacity - 1;
1036
1037            (a_start <= b_start && b_start <= a_end) || (a_start <= b_end && b_end <= a_end)
1038        }
1039
1040        const STRING_TO_INTERN: &str = "hello, world!";
1041
1042        // This test is meant to explore the thread orderings when one thread is trying to drop (and thus reclaim) the
1043        // last active reference to an interned string, and another thread is trying to intern that very same string.
1044        //
1045        // We accept, as a caveat, that a possible outcome is that we intern the "new" string again, even though an
1046        // existing entry to that string may have existed in an alternative ordering.
1047        loom::model(|| {
1048            let interner = create_interner(1024);
1049            let t2_interner = interner.clone();
1050
1051            // Intern the string from thread T1.
1052            let t1_interned_s = interner
1053                .try_intern(STRING_TO_INTERN)
1054                .expect("should not fail to intern");
1055            assert_eq!(t1_interned_s.deref(), STRING_TO_INTERN);
1056            let t1_reclaimed_entry = get_reclaimed_entry_for_string(&t1_interned_s);
1057
1058            // Spawn thread T2, which tries to intern the same string and returns the handle to it.
1059            let t2_result = loom::thread::spawn(move || {
1060                let interned_s = t2_interner
1061                    .try_intern(STRING_TO_INTERN)
1062                    .expect("should not fail to intern");
1063                let reclaimed_entry = get_reclaimed_entry_for_string(&interned_s);
1064
1065                (interned_s, reclaimed_entry)
1066            });
1067
1068            drop(t1_interned_s);
1069
1070            let (t2_interned_s, t2_reclaimed_entry) = t2_result.join().expect("should not fail to join T2");
1071            assert_eq!(t2_interned_s.deref(), STRING_TO_INTERN);
1072
1073            // What we're checking for here is that either:
1074            // - there's no reclaimed entries (T2 found the existing entry for the string before T1 dropped it)
1075            // - there's a reclaimed entry (T2 didn't find the existing entry for the string before T1 marked it as
1076            //   inactive) but the reclaimed entry does _not_ overlap with the interned string from T2, meaning we
1077            //   didn't get confused and allow T2 to use an existing entry that T1 then later marked as reclaimed
1078            let reclaimed_entries = reclaimed_entries(&interner);
1079            assert!(reclaimed_entries.len() <= 1, "should have at most one reclaimed entry");
1080
1081            if !reclaimed_entries.is_empty() {
1082                // If we do have a reclaimed entry, it needs to match exactly with only one of the interned strings.
1083                let is_t1_entry = reclaimed_entries.first().unwrap() == &t1_reclaimed_entry;
1084                let is_t2_entry = reclaimed_entries.first().unwrap() == &t2_reclaimed_entry;
1085
1086                assert!(
1087                    (is_t1_entry || is_t2_entry) && !(is_t1_entry && is_t2_entry),
1088                    "should only match one interned string"
1089                );
1090
1091                // Additionally, we ensure that the reclaimed entry does not overlap with the other interned string.
1092                assert!(
1093                    !do_reclaimed_entries_overlap(t1_reclaimed_entry, t2_reclaimed_entry),
1094                    "reclaimed entry should not overlap with remaining interned string"
1095                );
1096            }
1097        });
1098    }
1099
1100    #[test]
1101    fn concurrent_resurrect_and_double_drop() {
1102        // Regression test for a double-reclamation race.
1103        //
1104        // Unlike `concurrent_drop_and_intern` -- where the second thread keeps its handle alive -- here the second
1105        // thread interns the string _and_ drops it within the race window. This models the dangerous interleaving:
1106        //
1107        //   1. T1 drops the last reference: `refs` 1 -> 0 (but T1 hasn't taken the interner lock yet).
1108        //   2. T2 calls `try_intern`, finds the still-mapped entry, and resurrects it: `refs` 0 -> 1.
1109        //   3. T2 drops its handle: `refs` 1 -> 0.
1110        //   4. T1 and T2 each take the lock and observe `refs == 0`, so BOTH mark the same slot for reclamation.
1111        //
1112        // A double reclamation either underflows `storage.len` (a panic under overflow-checks, which the loom test
1113        // profile enables) or inserts a spurious/overlapping reclaimed entry. After every handle to the single interned
1114        // string has been dropped, the only correct end state is a fully empty interner.
1115        const STRING_TO_INTERN: &str = "hello, world!";
1116
1117        loom::model(|| {
1118            let interner = create_interner(1024);
1119            let t2_interner = interner.clone();
1120
1121            // T1 interns the string and holds the only reference.
1122            let t1_interned_s = interner
1123                .try_intern(STRING_TO_INTERN)
1124                .expect("should not fail to intern");
1125
1126            // T2 interns the same string and immediately drops it. The interesting orderings are those where this runs
1127            // while T1's drop has decremented the refcount to zero but not yet taken the interner lock.
1128            let t2 = loom::thread::spawn(move || {
1129                let t2_interned_s = t2_interner
1130                    .try_intern(STRING_TO_INTERN)
1131                    .expect("should not fail to intern");
1132                assert_eq!(t2_interned_s.deref(), STRING_TO_INTERN);
1133                drop(t2_interned_s);
1134            });
1135
1136            // T1 drops its (originally last) reference.
1137            drop(t1_interned_s);
1138
1139            t2.join().expect("should not fail to join T2");
1140
1141            // Every handle has been dropped, so the slot must have been reclaimed exactly once: the interner is fully
1142            // empty, with consistent accounting and no leftover reclaimed entries. A double reclaim breaks at least one
1143            // of these (and typically panics first via the `storage.len` underflow).
1144            let state = interner.state.lock().unwrap();
1145            assert!(state.entries.is_empty(), "entries map should be empty");
1146            assert_eq!(
1147                state.storage.len, 0,
1148                "storage.len should be zero (it is double-subtracted on a double reclaim)"
1149            );
1150            assert_eq!(state.storage.offset, 0, "offset should wind back to zero");
1151            assert!(
1152                state.storage.reclaimed.is_empty(),
1153                "no reclaimed entries should remain (a double reclaim inserts a spurious/overlapping entry)"
1154            );
1155        });
1156    }
1157}