feat: Enhance packet handling logging to notify number of waiters and catch errors

This commit is contained in:
k1ngsterr1
2026-01-12 04:31:42 +05:00
parent e04010d720
commit a7976c7cf3
2 changed files with 137 additions and 70 deletions

View File

@@ -104,6 +104,7 @@ class Protocol(
/** /**
* Start heartbeat to keep connection alive * Start heartbeat to keep connection alive
* Как в Архиве - отправляем text "heartbeat"
*/ */
private fun startHeartbeat(intervalSeconds: Int) { private fun startHeartbeat(intervalSeconds: Int) {
heartbeatJob?.cancel() heartbeatJob?.cancel()
@@ -114,10 +115,14 @@ class Protocol(
heartbeatJob = scope.launch { heartbeatJob = scope.launch {
while (isActive) { while (isActive) {
delay(intervalMs) delay(intervalMs)
if (webSocket?.send("heartbeat") == true) { try {
log("💓 Heartbeat sent") if (webSocket?.send("heartbeat") == true) {
} else { log("💓 Heartbeat sent")
log("💔 Heartbeat failed to send") } else {
log("💔 Heartbeat failed - socket closed or null")
}
} catch (e: Exception) {
log("💔 Heartbeat error: ${e.message}")
} }
} }
} }
@@ -132,14 +137,18 @@ class Protocol(
return return
} }
// Allow reconnection even if connected (for manual reconnect) // Закрываем старый сокет если есть (как в Архиве)
if (_state.value == ProtocolState.CONNECTED || _state.value == ProtocolState.AUTHENTICATED) { webSocket?.let { oldSocket ->
log("Already connected/authenticated, skipping...") try {
return oldSocket.close(1000, "Reconnecting")
log("🔄 Closed old WebSocket before reconnect")
} catch (e: Exception) {
log("⚠️ Error closing old socket: ${e.message}")
}
} }
webSocket = null
isManuallyClosed = false isManuallyClosed = false
// Don't reset reconnectAttempts here - it's reset on successful connection in onOpen
_state.value = ProtocolState.CONNECTING _state.value = ProtocolState.CONNECTING
_lastError.value = null _lastError.value = null
@@ -164,6 +173,7 @@ class Protocol(
} }
override fun onMessage(webSocket: WebSocket, bytes: ByteString) { override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
log("📥 onMessage called - ${bytes.size} bytes")
handleMessage(bytes.toByteArray()) handleMessage(bytes.toByteArray())
} }
@@ -172,16 +182,23 @@ class Protocol(
} }
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
log("⚠️ WebSocket closing: $code - $reason") log("⚠️ WebSocket closing: code=$code reason='$reason'")
log("⚠️ Stack trace at closing:")
Thread.currentThread().stackTrace.take(10).forEach {
log(" at $it")
}
} }
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
log("❌ WebSocket closed: $code - $reason") log("❌ WebSocket closed: code=$code reason='$reason'")
log("❌ State was: ${_state.value}")
handleDisconnect() handleDisconnect()
} }
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
log("❌ WebSocket error: ${t.message}") log("❌ WebSocket failure: ${t.message}")
log("❌ Response: ${response?.code} ${response?.message}")
t.printStackTrace()
_lastError.value = t.message _lastError.value = t.message
handleDisconnect() handleDisconnect()
} }
@@ -251,7 +268,23 @@ class Protocol(
val hexDump = data.take(50).joinToString(" ") { String.format("%02X", it) } val hexDump = data.take(50).joinToString(" ") { String.format("%02X", it) }
log(" Hex: $hexDump${if (data.size > 50) "..." else ""}") log(" Hex: $hexDump${if (data.size > 50) "..." else ""}")
webSocket?.send(ByteString.of(*data)) val socket = webSocket
if (socket == null) {
log("❌ WebSocket is null, cannot send packet ${packet.getPacketId()}")
return
}
try {
val sent = socket.send(ByteString.of(*data))
if (sent) {
log("✅ Packet ${packet.getPacketId()} sent successfully")
} else {
log("❌ Failed to send packet ${packet.getPacketId()} - send() returned false")
}
} catch (e: Exception) {
log("❌ Exception sending packet ${packet.getPacketId()}: ${e.message}")
e.printStackTrace()
}
} }
private fun flushPacketQueue() { private fun flushPacketQueue() {
@@ -282,11 +315,15 @@ class Protocol(
packet.receive(stream) packet.receive(stream)
// Notify waiters // Notify waiters
val waitersCount = packetWaiters[packetId]?.size ?: 0
log("📥 Notifying $waitersCount waiter(s) for packet $packetId")
packetWaiters[packetId]?.forEach { callback -> packetWaiters[packetId]?.forEach { callback ->
try { try {
callback(packet) callback(packet)
} catch (e: Exception) { } catch (e: Exception) {
log("❌ Error in packet handler: ${e.message}") log("❌ Error in packet handler: ${e.message}")
e.printStackTrace()
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -301,37 +338,26 @@ class Protocol(
handshakeComplete = false handshakeComplete = false
handshakeJob?.cancel() handshakeJob?.cancel()
heartbeatJob?.cancel() heartbeatJob?.cancel()
webSocket = null // Обнуляем сокет чтобы connect() мог создать новый
// Автоматический reconnect (как в Архиве) // Автоматический reconnect (упрощённая логика как в Архиве)
if (!isManuallyClosed) { if (!isManuallyClosed) {
// Логируем потерю соединения log("🔄 Connection lost from $previousState, reconnecting...")
if (previousState == ProtocolState.AUTHENTICATED || previousState == ProtocolState.CONNECTED) { reconnectAttempts++
log("🔄 Connection lost from $previousState, will reconnect...")
reconnectAttempts = 0 // Сбрасываем счётчик при неожиданной потере val delay = if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
RECONNECT_INTERVAL
} else {
// После максимума попыток - ждём дольше
reconnectAttempts = 0
30000L
} }
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { log("🔄 Reconnecting in ${delay}ms (attempt $reconnectAttempts)")
reconnectAttempts++
val delay = RECONNECT_INTERVAL * minOf(reconnectAttempts, 3) // Exponential backoff до 15 секунд
log("🔄 Reconnecting in ${delay}ms (attempt $reconnectAttempts/$MAX_RECONNECT_ATTEMPTS)")
scope.launch { scope.launch {
delay(delay) delay(delay)
if (!isManuallyClosed) { if (!isManuallyClosed) {
connect() connect()
}
}
} else {
log("❌ Max reconnect attempts reached, resetting counter...")
// Сбрасываем счетчик и пробуем снова через большой интервал
reconnectAttempts = 0
scope.launch {
delay(30000) // 30 секунд перед следующей серией попыток
if (!isManuallyClosed) {
log("🔄 Restarting reconnection attempts...")
connect()
}
} }
} }
} }

View File

@@ -191,6 +191,9 @@ fun ChatsListScreen(
var titleClickCount by remember { mutableStateOf(0) } var titleClickCount by remember { mutableStateOf(0) }
var lastClickTime by remember { mutableStateOf(0L) } var lastClickTime by remember { mutableStateOf(0L) }
// Status dialog state
var showStatusDialog by remember { mutableStateOf(false) }
// Search state - используем ViewModel для поиска пользователей // Search state - используем ViewModel для поиска пользователей
val searchViewModel = remember { SearchUsersViewModel() } val searchViewModel = remember { SearchUsersViewModel() }
val searchQuery by searchViewModel.searchQuery.collectAsState() val searchQuery by searchViewModel.searchQuery.collectAsState()
@@ -226,12 +229,58 @@ fun ChatsListScreen(
} }
*/ */
// Status dialog
if (showStatusDialog) {
AlertDialog(
onDismissRequest = { showStatusDialog = false },
title = { Text("Connection Status", fontWeight = FontWeight.Bold) },
text = {
Column {
Row(verticalAlignment = Alignment.CenterVertically) {
Box(
modifier = Modifier
.size(12.dp)
.clip(CircleShape)
.background(
when (protocolState) {
ProtocolState.AUTHENTICATED -> Color(0xFF4CAF50)
ProtocolState.CONNECTING, ProtocolState.CONNECTED, ProtocolState.HANDSHAKING -> Color(0xFFFFC107)
ProtocolState.DISCONNECTED -> Color(0xFFF44336)
}
)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = when (protocolState) {
ProtocolState.DISCONNECTED -> "Disconnected"
ProtocolState.CONNECTING -> "Connecting..."
ProtocolState.CONNECTED -> "Connected"
ProtocolState.HANDSHAKING -> "Authenticating..."
ProtocolState.AUTHENTICATED -> "Authenticated"
},
fontSize = 16.sp
)
}
}
},
confirmButton = {
Button(onClick = { showStatusDialog = false }) {
Text("OK")
}
},
containerColor = if (isDarkTheme) Color(0xFF212121) else Color.White
)
}
// Simple background // Simple background
Box(modifier = Modifier.fillMaxSize().background(backgroundColor)) { Box(modifier = Modifier.fillMaxSize().background(backgroundColor)) {
ModalNavigationDrawer( ModalNavigationDrawer(
drawerState = drawerState, drawerState = drawerState,
drawerContent = { drawerContent = {
ModalDrawerSheet(drawerContainerColor = drawerBackgroundColor) { ModalDrawerSheet(
drawerContainerColor = drawerBackgroundColor,
modifier = Modifier.width(280.dp)
) {
// Drawer Header // Drawer Header
Column( Column(
modifier = modifier =
@@ -319,7 +368,7 @@ fun ChatsListScreen(
selected = false, selected = false,
colors = drawerItemColors, colors = drawerItemColors,
onClick = { onClick = {
scope.launch { drawerState.close() } // Don't close drawer when toggling theme
onToggleTheme() onToggleTheme()
} }
) )
@@ -410,7 +459,7 @@ fun ChatsListScreen(
Box(modifier = Modifier.fillMaxWidth()) { Box(modifier = Modifier.fillMaxWidth()) {
// Title - Triple click to open dev console (stays in place, just fades) // Title - Triple click to open dev console (stays in place, just fades)
if (titleAlpha > 0.01f) { if (titleAlpha > 0.01f) {
Column( Row(
modifier = Modifier modifier = Modifier
.clickable { .clickable {
val currentTime = val currentTime =
@@ -435,7 +484,8 @@ fun ChatsListScreen(
lastClickTime = lastClickTime =
currentTime currentTime
} }
.graphicsLayer { alpha = titleAlpha } .graphicsLayer { alpha = titleAlpha },
verticalAlignment = Alignment.CenterVertically
) { ) {
Text( Text(
"Rosetta", "Rosetta",
@@ -443,32 +493,23 @@ fun ChatsListScreen(
fontSize = 20.sp, fontSize = 20.sp,
color = textColor color = textColor
) )
if (protocolState != Spacer(modifier = Modifier.width(8.dp))
ProtocolState.AUTHENTICATED // Status indicator dot
) { Box(
Text( modifier = Modifier
text = .size(10.dp)
when (protocolState) { .clip(CircleShape)
ProtocolState .background(
.DISCONNECTED -> when (protocolState) {
"Connecting..." ProtocolState.AUTHENTICATED -> Color(0xFF4CAF50) // Green
ProtocolState ProtocolState.CONNECTING, ProtocolState.CONNECTED, ProtocolState.HANDSHAKING -> Color(0xFFFFC107) // Yellow
.CONNECTING -> ProtocolState.DISCONNECTED -> Color(0xFFF44336) // Red
"Connecting..." }
ProtocolState )
.CONNECTED -> .clickable {
"Authenticating..." showStatusDialog = true
ProtocolState }
.HANDSHAKING -> )
"Authenticating..."
ProtocolState
.AUTHENTICATED ->
""
},
fontSize = 12.sp,
color = secondaryTextColor
)
}
} }
} }