sschmid/Entitas · error · EntityIsAlreadyRetainedByOwnerException

EntityIsAlreadyRetainedByOwnerException(_entity, owner)

Error message

EntityIsAlreadyRetainedByOwnerException(_entity, owner)

What it means

Thrown by SafeAERC.Retain when the given owner already retains the entity. SafeAERC (safe automatic entity retention control) tracks a set of owners; Owners.Add returns false if the owner is already present, which means Retain was called twice for the same owner — an unbalanced retain that would leak the entity.

Solutions

  1. Track retention yourself: only call Retain if the entity was not already retained by that owner
  2. Call Release(owner) symmetrically in the corresponding teardown/callback path
  3. Retain once at acquisition (e.g. when adding to your collection) and release at removal
  4. Switch to UnsafeAERC only if you fully control retention and accept no safety checks

Example fix

// before
void OnEntityAdded(Entity e) { e.Retain(this); _list.Add(e); } // duplicates possible
// after
void OnEntityAdded(Entity e) {
    if (!_list.Contains(e)) {
        e.Retain(this);
        _list.Add(e);
    }
}
Defensive patterns

Strategy: validation

Validate before calling

if (entity.Aerc is SafeAERC sa && !sa.Owners.Contains(owner)) entity.Retain(owner);

Type guard

bool CanRetain(Entity e, object owner) => e.Aerc is SafeAERC sa ? !sa.Owners.Contains(owner) : true;

Try / catch

try { entity.Retain(owner); }
catch (EntityIsAlreadyRetainedByOwnerException) { /* already retained by this owner */ }

Prevention

When it happens

Trigger: Calling entity.Retain(owner) twice with the same owner object without an intervening Release(owner); retaining in a handler that can fire repeatedly for the same entity.

Common situations: System retaining entities on every OnEntityAdded event including duplicates; re-entrant initialization running Retain twice; retaining in both a constructor and an OnEntityAdded handler.

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 sschmid/Entitas@37547d1bd2 (2026-09-14). Data as JSON: /api/errors/514ccb6e1b1bed6b. Report an issue: GitHub.

Appendix: source

Thrown at src/Entitas/Entity/SafeAERC.cs:31

    public sealed class SafeAERC : IAERC
    {
        public static readonly Func<Entity, IAERC> Delegate = entity => new SafeAERC(entity);

        public int RetainCount => _owners.Count;
        public HashSet<object> Owners => _owners;

        readonly Entity _entity;
        readonly HashSet<object> _owners = new HashSet<object>();

        public SafeAERC(Entity entity)
        {
            _entity = entity;
        }

        public void Retain(object owner)
        {
            if (!Owners.Add(owner))
                throw new EntityIsAlreadyRetainedByOwnerException(_entity, owner);
        }

        public void Release(object owner)
        {
            if (!Owners.Remove(owner))
                throw new EntityIsNotRetainedByOwnerException(_entity, owner);
        }
    }
}

View on GitHub (pinned to 37547d1bd2)