Release failed member snapshot requests and validate deletion frames

This commit is contained in:
evgeny committed 2026-10-29 13:34:25 +01:00
1 parent 36af8d077f
commit 9383a68f17
2 files changed
+31 -2

No files matched your search

@@ -8,7 +8,9 @@ import java.nio.ByteOrder
data class MemberKey(val group: String, val owner: Long)
/** Полные снимки CHAT-групп. Все методы вызываются последовательно на main, события не replay-ятся.
* loaded хранится отдельно: отсутствие участника в полученном списке не требует нового запроса. */
* loaded хранится отдельно: отсутствие участника в полученном списке не требует нового запроса.
* Pending подавляет повтор 10 секунд; отказ/исключение enqueue разрешает повтор сразу, не очищая последний снимок.
* Полный кадр применяется атомарно, отдельные update/remove меняют одного участника; stop очищает всё и увеличивает generation. */
class MemberRepository(
private val enqueue: (String) -> Boolean = NativeLib::requestMemberList,
private val ready: () -> Boolean = NativeLib::isReady,
@@ -37,7 +39,10 @@ class MemberRepository(
if (started != null && now - started < 10_000) return
if (started != null) log("WARN", "Snapshot response timeout channel=$group elapsedMs=${now - started}")
pending[group] = now
if (!enqueue(group)) {
val accepted = try { enqueue(group) } catch (e: Exception) {
pending.remove(group); log("ERROR", "Snapshot enqueue threw channel=$group: $e"); return
}
if (!accepted) {
pending.remove(group); log("ERROR", "Snapshot enqueue failed channel=$group"); return
}
log("DEBUG", "Snapshot requested channel=$group loaded=${group in loaded}")
@@ -61,10 +66,12 @@ class MemberRepository(
val previous = _members.value
val updated = previous.toMutableMap()
if (type == 31) {
require(data.size == 1 + length) { "invalid channel deletion size" }
updated.keys.removeAll { it.group == group }; loaded.remove(group); pending.remove(group)
} else if (type == 25) {
require(data.size == 1 + length + 8) { "invalid removal size" }
val node = ByteBuffer.wrap(data, 1 + length, 8).order(ByteOrder.LITTLE_ENDIAN).long
require(node != 0L) { "zero removal node" }
updated.remove(MemberKey(group, node))
} else {
val countSize = if (type == 23) 2 else 1
@@ -88,4 +88,26 @@ class MemberRepositoryTest {
repo.onEvent(23, event("123", row(1)))
repo.refresh("123"); assertTrue(repo.member("123", 1)!!.phoneStatusKnown)
}
@Test fun enqueueExceptionReleasesPendingAndKeepsLastSnapshot() {
var requests = 0
val diagnostics = mutableListOf<String>()
val repo = MemberRepository({ if (++requests == 1) error("JNI request failed") else true }, { true }, { 0 },
{ _, message -> diagnostics.add(message) })
repo.onEvent(12, null); repo.onEvent(23, event("123", row(1)))
val before = repo.members.value
repo.refresh("123"); repo.refresh("123")
assertEquals(2, requests); assertEquals(before, repo.members.value)
assertTrue(diagnostics.any { "enqueue threw" in it })
}
@Test fun malformedDeletionCannotClearAnOtherwiseValidSnapshot() {
val repo = repository(); repo.onEvent(23, event("123", row(1)))
val before = repo.members.value
val header = byteArrayOf(3) + "123".toByteArray()
repo.onEvent(31, header + byteArrayOf(0))
assertEquals(before, repo.members.value)
repo.onEvent(31, header)
assertTrue(repo.members.value.isEmpty())
}
}