stamparm/maltrail · error

cannot modify a finalized TrailsDict

Error message

cannot modify a finalized TrailsDict

What it means

This is a generic lifecycle-state guard in TrailsDict.__setitem__: once the dict has been finalized — either frozen or switched to a memory-mapped read-only backend (self._frozen / self._mmap set) — the internal index structures are considered immutable, so any key assignment is refused. It fires when application code keeps a reference to a TrailsDict after finalization (e.g. after freeze or loading from a bin file) and then attempts to store or overwrite a trail. Fix by performing all mutations before finalizing, or by creating a new mutable TrailsDict instance (e.g. via clear()/constructor) instead of writing to the finalized one.

Solutions

  1. Create a new TrailsDict for updates instead of writing to the finalized one
  2. Delay freeze()/bin-loading until all writes are complete
  3. Call clear() (which re-inits) before writing if a fresh empty dict is intended
  4. Guard writes with a check for the frozen/mmap state and skip or log instead

Example fix

// before
if trail_changed:
    trails[key] = value  # trails already frozen
// after
if trail_changed and trails._frozen is None and trails._mmap is None:
    trails[key] = value
Defensive patterns

Strategy: type-guard

Validate before calling

def trails_writable(td):
    return td._frozen is None and td._mmap is None

Type guard

def is_writable_trailsdict(td):
    return isinstance(td, TrailsDict) and td._frozen is None and td._mmap is None

Try / catch

try:
    trails[key] = value
except Exception as e:
    if "finalized" in str(e):
        trails = TrailsDict(); trails[key] = value  # start a fresh cycle

Prevention

When it happens

Trigger: trails.freeze() followed by trails['x'] = (1,2); assigning into a TrailsDict loaded from a memory-mapped bin; an update()/build path that finishes and freezes before a late writer writes.

Common situations: Code holding a reference to a frozen dict shared across threads/sensors; re-running an updater against an already-finalized dict; forgetting to re-create (or clear) the dict before a new update cycle.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of stamparm/maltrail@77cfb06d76 (2026-09-13). Data as JSON: /api/errors/63be8cd63ea067a3. Report an issue: GitHub.

Appendix: source

Thrown at core/trailsdict.py:270

        while i < n and hi[i] == target_hi:
            if lo[i] == target_lo:
                return pair_list[values[i]]
            i += 1
        return default

    def __len__(self):
        mm = self._mmap
        if mm is not None:
            return mm["length"]
        frozen = self._frozen
        return frozen[5] if frozen is not None else len(self._trails)

    def clear(self):
        self.__init__()

    def __setitem__(self, key, value):
        if self._frozen is not None or self._mmap is not None:
            raise Exception("cannot modify a finalized TrailsDict")
        if not isinstance(value, (tuple, list)):
            raise Exception("unsupported type '%s'" % type(value))

        pair = (value[0], value[1])
        shared = self._pairs.get(pair)
        if shared is None:
            shared = pair
            self._pairs[pair] = pair
        self._trails[key] = shared

    def __delitem__(self, key):
        if self._frozen is not None or self._mmap is not None:
            raise Exception("cannot modify a finalized TrailsDict")
        del self._trails[key]

    def update(self, value):
        if self._frozen is not None or self._mmap is not None:
            raise Exception("cannot modify a finalized TrailsDict")

View on GitHub (pinned to 77cfb06d76)