Skip to content

Cleanup magic effects sync api - #945

Draft
eduardosmaniotto wants to merge 5 commits into
MUnique:masterfrom
eduardosmaniotto:cleanup/magic-effects-sync-api
Draft

eduardosmaniotto wants to merge 5 commits into
MUnique:masterfrom
eduardosmaniotto:cleanup/magic-effects-sync-api

Conversation

@eduardosmaniotto

Copy link
Copy Markdown
Contributor

Follow-up to the MagicEffectsList torn-read race fix #940: removes the
two ...Async compatibility shims it left behind.

Change

  • MagicEffectsList: new sync TryGetActiveEffectOfSubType(byte);
    both Clear* methods use GetActiveEffectsSnapshot() directly;
    GetActiveEffectsSnapshotAsync() and
    TryGetActiveEffectOfSubTypeAsync() deleted.
  • Callers migrated: AttackableExtensions (combat elemental effects),
    ApplyMagicEffectConsumeHandlerPlugIn (consumables),
    CastleSiegeContext (join-side scan).

Why now

The twins were ValueTask.FromResult wrappers with no I/O — awaiting
them never yielded — kept only so existing callers compiled
unchanged. With all four call sites converted, they serve no purpose.

MagicEffectsList exposed its mutable SortedList directly while
effect-expiry timers mutated it under lock, so lock-free readers
(BuffHandler.IsEffectActive, BotBuffHandler.HasEffect) could hit
ArgumentException from Values.ToArray() mid-copy and abort the
helper tick. The branch hotfix widened the catch to also swallow
that variant, but left every other reader racy.

Centralize synchronization in MagicEffectsList behind a Lock with
thread-safe queries (ContainsEffect, ContainsAnyEffect, HasEffect,
TryGetEffect, GetActiveEffectsSnapshot) and migrate all GameLogic
and GameServer readers onto them. The live ActiveEffects reference
is removed so the invariant (all access under lock) is enforced,
not documented. Tests now seed state via AddEffectAsync and assert
via snapshots.
Subscribe EffectTimeOut inside the lock and skip already-disposed
effects in AddEffectAsync, closing the race where a timer firing
between Add and subscribe left a stuck entry that poisoned _contains
and could spin ClearAllEffectsAsync forever; ClearAll now also
force-removes a head that survives its own disposal.

Add a TryGetEffect(MagicEffectDefinition) overload and move the
invisibility lookup back to definition matching, narrow
EffectNumbers to const short and drop the truncating int overload
(explicit byte casts at the packet boundary), and mark the async
twins as source-compat shims.

Tests seed via AddEffectAsync with teardown cleanup, assert via
snapshots, and a new concurrency test hammers add/expiry against
all four read APIs to pin the fix.
GetActiveEffectsSnapshotAsync and TryGetActiveEffectOfSubTypeAsync
performed no I/O and completed synchronously; they existed only as
source-compatibility wrappers left over from the torn-read race fix.
Add a sync TryGetActiveEffectOfSubType core, migrate the four
remaining callers (Clear* methods, combat elemental-effect path,
consumable path, castle siege join-side scan) to the synchronous
API, and delete both twins.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant