UnigramDev/Unigram/composer-typing-performance • 806ba3d • 2 files, +299/-192
Guard the group call managers against Dispose
VoipGroupCall reaches Dispose from Update on TDLib update thread while the UI
thread calls SetVolume, AddIncomingVideoOutput and the rest, and stopping a
manager frees the tgcalls instance the other thread may be part way into. Its
sibling VoipCall has always serialised this behind _managerLock; VoipGroupCall
declared the same field but only ever wrapped a block of commented out code
copied along with it.
Held now around every call that reaches the instance, and around the teardown in
Dispose and EndScreenSharing. Left outside: the IsMuted and
IsNoiseSuppressionEnabled getters, which read a native field rather than
touching m_impl, and the null checks that only decide whether to start
something.
Two spots needed more than a wrapper. The EmitJoinPayload continuations run
after awaits, so they take the lock again where they call in, since C# cannot
hold one across an await. And UpdateParticipant dereferenced _manager
unconditionally from the update thread, which was a null reference waiting to
happen quite apart from the race.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/composer-typing-performance • a94e531 • 2 files, +16/-37
Flatten buttons text
UnigramDev/Unigram/composer-typing-performance • 4731dca • 2 files, +34/-5
Check a file against the disk only the first time it is seen
ParseFile called NativeUtils.FileExists after every parse, for files it had seen
thousands of times as readily as for new ones. Every parsed object carries files
— a history page is hundreds of them, nearly all already cached — and this runs
on the thread draining td_receive, so each one spent a syscall re-answering a
question already answered.
ProcessFile, on the old type-crossed parser, only ever checked a file id it had
not seen. That is the right shape and ParseFile now matches it, which drops the
steady-state cost from per-file-per-update to once per file id per session.
Nothing real is lost by not repeating it. TDLib sends no update when a file
disappears behind its back, so the repeat check only caught an external delete
when some unrelated update happened to arrive for that same file — luck rather
than detection. The reliable path is GetFileAsync catching FileNotFoundException
where the file is actually used. What the first-sight check is genuinely for is
the cache having been cleared between sessions, and that still works.
This does not help the startup replay, where every file is a first sight by
definition. Whether that burst is worth deferring off the receive thread is
worth counting before building anything.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Post #20595
37