Skip to content

Commit d376ed3

Browse files
Merge pull request #118 from leanix/feature/CID-3856/Frequent-Websocket-connection-loss
CID-3856: Switching from default heartbeats to manual heartbeats
2 parents d8093e4 + 3cf8466 commit d376ed3

File tree

4 files changed

+50
-11
lines changed

4 files changed

+50
-11
lines changed

src/main/kotlin/net/leanix/githubagent/config/WebSocketClientConfig.kt

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import io.github.resilience4j.retry.annotation.Retry
55
import net.leanix.githubagent.handler.BrokerStompSessionHandler
66
import net.leanix.githubagent.services.LeanIXAuthService
77
import net.leanix.githubagent.shared.GitHubAgentProperties.GITHUB_AGENT_VERSION
8-
import org.springframework.beans.factory.annotation.Value
8+
import org.slf4j.LoggerFactory
99
import org.springframework.context.annotation.Bean
1010
import org.springframework.context.annotation.Configuration
1111
import org.springframework.messaging.converter.MappingJackson2MessageConverter
@@ -17,6 +17,8 @@ import org.springframework.web.socket.client.standard.StandardWebSocketClient
1717
import org.springframework.web.socket.messaging.WebSocketStompClient
1818
import org.springframework.web.socket.sockjs.client.SockJsClient
1919
import org.springframework.web.socket.sockjs.client.WebSocketTransport
20+
import java.time.Duration
21+
import java.util.concurrent.ScheduledFuture
2022

2123
@Configuration
2224
class WebSocketClientConfig(
@@ -25,21 +27,49 @@ class WebSocketClientConfig(
2527
private val leanIXAuthService: LeanIXAuthService,
2628
private val leanIXProperties: LeanIXProperties,
2729
private val gitHubEnterpriseProperties: GitHubEnterpriseProperties,
28-
@Value("\${websocket.heartbeat-interval}") private val heartbeatInterval: Long
2930
) {
31+
32+
private var heartbeatTask: ScheduledFuture<*>? = null
33+
private val logger = LoggerFactory.getLogger(WebSocketClientConfig::class.java)
34+
3035
@Retry(name = "ws_init_session")
3136
fun initSession(): StompSession {
3237
val headers = WebSocketHttpHeaders()
3338
val stompHeaders = StompHeaders()
3439
stompHeaders["Authorization"] = "Bearer ${leanIXAuthService.getBearerToken()}"
3540
stompHeaders["GitHub-Enterprise-URL"] = gitHubEnterpriseProperties.baseUrl
3641
stompHeaders["GitHub-Agent-Version"] = GITHUB_AGENT_VERSION
37-
return stompClient().connectAsync(
42+
val session = stompClient().connectAsync(
3843
leanIXProperties.wsBaseUrl,
3944
headers,
4045
stompHeaders,
4146
brokerStompSessionHandler,
4247
).get()
48+
49+
sendHeartbeat(session)
50+
return session
51+
}
52+
53+
fun sendHeartbeat(session: StompSession) {
54+
val scheduler = ThreadPoolTaskScheduler()
55+
scheduler.initialize()
56+
heartbeatTask = scheduler.scheduleAtFixedRate({
57+
kotlin.runCatching {
58+
if (session.isConnected) {
59+
session.send("/app/ghe/heartbeat", "")
60+
logger.debug("Heartbeat sent to /app/heartbeat")
61+
} else {
62+
logger.warn("Session is not connected, stopping heartbeat")
63+
stopHeartbeat()
64+
}
65+
}.onFailure {
66+
logger.error("Failed to send heartbeat: ${it.message}")
67+
}
68+
}, Duration.ofSeconds(60))
69+
}
70+
71+
fun stopHeartbeat() {
72+
heartbeatTask?.cancel(true)
4373
}
4474

4575
@Bean
@@ -52,7 +82,6 @@ class WebSocketClientConfig(
5282
val sockJsClient = SockJsClient(transports)
5383
val stompClient = WebSocketStompClient(sockJsClient)
5484
stompClient.messageConverter = jsonConverter
55-
stompClient.defaultHeartbeat = longArrayOf(heartbeatInterval, 0)
5685
val scheduler = ThreadPoolTaskScheduler()
5786
scheduler.initialize()
5887
stompClient.taskScheduler = scheduler

src/main/resources/application.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ leanix:
1212
technical-user-token: ${LEANIX_TECHNICAL_USER_TOKEN}
1313
webhookEventService:
1414
waitingTime: 10000
15-
websocket:
16-
heartbeat-interval: 30000
1715

1816
resilience4j.retry:
1917
configs:

src/test/kotlin/net/leanix/githubagent/config/WebSocketClientConfigTests.kt

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package net.leanix.githubagent.config
22

33
import com.fasterxml.jackson.databind.ObjectMapper
44
import io.mockk.coEvery
5+
import io.mockk.every
56
import io.mockk.mockk
7+
import io.mockk.verify
68
import kotlinx.coroutines.runBlocking
79
import net.leanix.githubagent.handler.BrokerStompSessionHandler
810
import net.leanix.githubagent.services.LeanIXAuthService
@@ -14,6 +16,7 @@ import org.springframework.messaging.simp.stomp.StompHeaders
1416
import org.springframework.messaging.simp.stomp.StompSession
1517
import org.springframework.web.socket.WebSocketHttpHeaders
1618
import org.springframework.web.socket.messaging.WebSocketStompClient
19+
import java.util.concurrent.ScheduledFuture
1720

1821
class WebSocketClientConfigTests {
1922
private lateinit var webSocketClientConfig: WebSocketClientConfig
@@ -23,6 +26,7 @@ class WebSocketClientConfigTests {
2326
private lateinit var leanIXProperties: LeanIXProperties
2427
private lateinit var gitHubEnterpriseProperties: GitHubEnterpriseProperties
2528
private lateinit var leanIXAuthService: LeanIXAuthService
29+
private lateinit var scheduledFuture: ScheduledFuture<*>
2630

2731
@BeforeEach
2832
fun setUp() {
@@ -34,15 +38,14 @@ class WebSocketClientConfigTests {
3438
stompSession = mockk()
3539
authService = mockk()
3640
leanIXAuthService = mockk()
37-
val heartbeatInterval = 10000L
41+
scheduledFuture = mockk()
3842

3943
webSocketClientConfig = WebSocketClientConfig(
4044
brokerStompSessionHandler,
4145
objectMapper,
4246
leanIXAuthService,
4347
leanIXProperties,
44-
gitHubEnterpriseProperties,
45-
heartbeatInterval
48+
gitHubEnterpriseProperties
4649
)
4750

4851
GitHubAgentProperties.GITHUB_AGENT_VERSION = "test-version"
@@ -66,4 +69,15 @@ class WebSocketClientConfigTests {
6669

6770
assertEquals(null, session)
6871
}
72+
73+
@Test
74+
fun `should send heartbeat when session is connected`() {
75+
val receiptable = mockk<StompSession.Receiptable>()
76+
every { stompSession.isConnected } returns true
77+
every { stompSession.send(any<String>(), any()) } returns receiptable
78+
79+
webSocketClientConfig.sendHeartbeat(stompSession)
80+
81+
verify { stompSession.send("/app/ghe/heartbeat", "") }
82+
}
6983
}

src/test/resources/application.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,3 @@ leanix:
1212
technical-user-token: ${LEANIX_TECHNICAL_USER_TOKEN:dummy}
1313
webhookEventService:
1414
waitingTime: 100
15-
websocket:
16-
heartbeat-interval: 30000

0 commit comments

Comments
 (0)