Fix multiple race conditions in TMModel #109#114
Merged
danipen merged 6 commits intodanipen:masterfrom Feb 17, 2026
Merged
Conversation
ITMModel now inherits from IDisposable, removing the explicit Dispose method declaration. Enhanced XML documentation for clarity and maintainability.
Expanded TMModelTests with new tests for Dispose() (non-blocking, idempotency), event emission after listener removal, and grammar flip deadlock scenarios. Added CountingListener and CountingClearListener helpers. Updated usings and refactored ModelLinesMock for clarity. Improves test coverage for resource management and concurrency.
Refactor TMModel threading and disposal for safety Comprehensively refactored TMModel and TokenizerThread to eliminate race conditions, prevent deadlocks, and ensure safe disposal. Introduced atomic disposal flags, improved thread lifecycle management, and enforced strict lock ordering. Listener management is now deadlock-free, and all public methods are robust against concurrent access and disposal. Added extensive documentation and improved error handling. These changes significantly increase the reliability and maintainability of background tokenization.
Removed explanatory FIX comments related to thread-safety and resource management in TMModel.cs. The code logic remains unchanged; this cleanup improves code readability by eliminating detailed inline justifications.
Added detailed XML documentation to AbstractLineList, clarifying threading, lock-ordering, and safe usage patterns to prevent deadlocks with TMModel. Replaced foreach with for loops for line iteration. Marked mLock as readonly and documented its role in thread safety. Enhanced remarks for invalidation methods to guide correct lock usage.
Improve event emission safety in TMModel Refactored event emission logic to track TokenizerThread and grammar epoch, ensuring events are only published if model state remains consistent during callbacks. Added stricter checks to prevent stale events after concurrent Stop, Dispose, or SetGrammar calls. Updated listener notification to handle nulls and re-validate model state per listener.
danipen
approved these changes
Feb 17, 2026
Owner
danipen
left a comment
There was a problem hiding this comment.
Bunch of changes! The PR is well-engineered and addresses real race conditions without changing tokenization semantics. LGTM
Owner
|
Tested with AvaloniaEdit's current master (AvaloniaUI/AvaloniaEdit@7fa5d88). No issues found. |
Owner
|
Merged! Thanks for your contribution. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #109
This PR hardens
TMModel's lifecycle and threading behavior under its intended usage (UI-thread document edits + background tokenization), addressing races, deadlock vectors, and resource cleanup issues without changing the public API surfaceFixes / changes included
Lifecycle + disposal correctness
Interlocked.CompareExchangeto prevent double-dispose / double-stop behavior under concurrent callsCancellationTokenSource,AutoResetEvent, and workerTask) are cancelled/disposed safely and exactly onceTokenizer thread state machine hardening
TokenizerThreadto guard start/stop/signal/dispose transitions and eliminate check-then-act racesSetGrammar/Stop(thread identity check under the model lock)Model state synchronization (race fixes)
ModelLine.Stateused for tokenization by capturing the starting state under_lockbefore calling into the tokenizerModelLine.Tokens,ModelLine.IsInvalid, and end-state propagation) under_lockwith epoch/thread/tokenizer identity checks so stale work is discarded and re-queued safelyLock ordering + deadlock prevention
_lock → listenersand documented it in code commentslock(listeners)by snapshotting the listener list under the lock and invoking callbacks outside the lockStop()and to prevent adding listeners to a disposed modelForceTokenization / invalidation robustness
ForceTokenization(start,end)clamps indices under_lockand ensures grammar/thread checks are coherent with the captured thread instance (avoids TOCTOU races)InvalidateLineRangeclamps indices under_lockand signals the worker thread safely after enqueueing invalid workNotes / non-goals