GitoxideLabs/gitoxide · error

BUG: a slot can never be deleted if we have it recorded in…

Error message

BUG: a slot can never be deleted if we have it recorded in the index WHILE changing said index. There shouldn't be a race

What it means

`assure_slot_matches_index` in `gix-odb`'s dynamic object store looks up a pack slot recorded in an in-memory index; the `None` arm panics because a slot being consulted cannot be removed concurrently by design (single-writer access to the index). Hitting it means the slot bookkeeping was mutated unexpectedly.

Solutions

  1. Report upstream with repository layout (pack files, multi-pack-index) and the operation that panicked.
  2. Update `gix-odb`/`gix` to the latest version.
  3. If maintaining, replace `unreachable!` with an error indicating a lost slot for diagnosability.

Example fix

// before
None => unreachable!("BUG: a slot can never be deleted ..."),
// after
None => return Err(message("BUG: multi-index slot missing while consulting index; store state corrupted"))
Defensive patterns

Strategy: try-catch

Try / catch

// Not user-triggerable; wrap object loads in catch_unwind for resilience.
let obj = std::panic::catch_unwind(|| repo.find_object(id));

Prevention

When it happens

Trigger: Only if slot replacement/removal logic in the multi-pack index races or double-frees an entry — e.g. a refactor bug around `MultiIndexSlotMap` removal while a `load_index` operation holds a lookup.

Common situations: Not expected in normal use of a repository with many pack files and multi-pack indices; would appear as a panic while loading objects.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08). Data as JSON: /api/errors/0cbe10c8c1f7aaa6. Report an issue: GitHub.

Appendix: source

Thrown at gix-odb/src/store_impls/dynamic/load_index.rs:655

                        .expect("BUG: cannot change from something to nothing, would be race");
                    files_mut.put_back();
                    debug_assert_eq!(
                        files_mut.mtime(),
                        mtime,
                        "BUG: we can only put back files that didn't obviously change"
                    );
                    // Safety: can't race as we hold the lock, must be set before replacing the data.
                    // NOTE that we don't change the generation as it's still the very same index we talk about, it doesn't change
                    // identity.
                    slot.generation.store(current_generation, Ordering::SeqCst);
                    slot.files.store(files);
                } else {
                    // it's already in the correct state, either loaded or unloaded.
                }
                bundle.index_is_loaded()
            }
            None => {
                unreachable!(
                    "BUG: a slot can never be deleted if we have it recorded in the index WHILE changing said index. There shouldn't be a race"
                )
            }
        }
    }

    /// Stability means that indices returned by this API will remain valid.
    /// Without that constraint, we may unload unused packs and indices, and may rebuild the slotmap index.
    ///
    /// Note that this must be called with a lock to the relevant state held to assure these values don't change while
    /// we are working on said index.
    fn maintain_stable_indices(&self, _guard: &parking_lot::MutexGuard<'_, ()>) -> bool {
        self.num_handles_stable.load(Ordering::SeqCst) > 0
    }

    pub(crate) fn collect_snapshot(&self) -> Snapshot {
        // We don't observe changes-on-disk in our 'wait-for-load' loop.
        // That loop is meant to help assure the marker (which includes the amount of loaded indices) matches

View on GitHub (pinned to e73179060b)