@@ -85,6 +85,7 @@ import java.net.URI
8585import java.nio.charset.StandardCharsets
8686import java.nio.file.Files
8787import java.util.concurrent.Future
88+ import kotlin.time.Duration.Companion.milliseconds
8889import kotlin.time.Duration.Companion.seconds
8990
9091// https://github.com/redhat-developer/lsp4ij/blob/main/src/main/java/com/redhat/devtools/lsp4ij/server/LSPProcessListener.java
@@ -131,6 +132,8 @@ class AmazonQLspService(private val project: Project, private val cs: CoroutineS
131132 val encryptionManager
132133 get() = instance.getCompleted().encryptionManager
133134 private val heartbeatJob: Job
135+ private val restartTimestamps = ArrayDeque <Long >()
136+ private val restartMutex = Mutex () // Separate mutex for restart tracking
134137
135138 // dont allow lsp commands if server is restarting
136139 private val mutex = Mutex (false )
@@ -179,14 +182,18 @@ class AmazonQLspService(private val project: Project, private val cs: CoroutineS
179182
180183 // Check if the launcher's Future (startListening) is done
181184 // If it's done, that means the connection has been terminated
182- if (currentInstance.launcherFuture.isDone) {
183- LOG .debug { " LSP server connection terminated, restarting server" }
185+ if (currentInstance.launcherFuture.isDone || true ) {
186+ LOG .debug { " LSP server connection terminated, checking restart limits" }
187+ waitForRestartSlot()
188+ LOG .debug { " Restarting LSP server" }
184189 restart()
185190 } else {
186- LOG .debug { " LSP server is currently running " }
191+ LOG .debug { " LSP server is currently running" }
187192 }
188193 } catch (e: Exception ) {
189- LOG .debug(e) { " Connection status check failed, restarting LSP server" }
194+ LOG .debug(e) { " Connection status check failed, checking restart limits" }
195+ waitForRestartSlot()
196+ LOG .debug { " Restarting LSP server" }
190197 restart()
191198 }
192199 }
@@ -219,6 +226,37 @@ class AmazonQLspService(private val project: Project, private val cs: CoroutineS
219226 instance = start()
220227 }
221228
229+ private suspend fun waitForRestartSlot (): Unit = restartMutex.withLock {
230+ val currentTime = System .currentTimeMillis()
231+
232+ while (restartTimestamps.isNotEmpty() &&
233+ currentTime - restartTimestamps.first() > RESTART_WINDOW_MS
234+ ) {
235+ restartTimestamps.removeFirst()
236+ }
237+
238+ if (restartTimestamps.size < MAX_RESTARTS ) {
239+ restartTimestamps.addLast(currentTime)
240+ return
241+ }
242+
243+ val oldestTimestamp = restartTimestamps.first()
244+ val waitTimeMs = (oldestTimestamp + RESTART_WINDOW_MS ) - currentTime
245+
246+ LOG .info { " Rate limit reached for LSP server restarts. Waiting ${waitTimeMs} ms for next available slot." }
247+
248+ restartMutex.unlock()
249+ try {
250+ delay(waitTimeMs.milliseconds)
251+ } finally {
252+ restartMutex.lock()
253+ }
254+
255+ // After waiting, recursively call this function to check again
256+ // (in case conditions changed while we were waiting)
257+ waitForRestartSlot()
258+ }
259+
222260 suspend fun <T > execute (runnable : suspend AmazonQLspService .(AmazonQLanguageServer ) -> T ): T {
223261 val lsp = withTimeout(10 .seconds) {
224262 val holder = mutex.withLock { instance }.await()
@@ -236,6 +274,8 @@ class AmazonQLspService(private val project: Project, private val cs: CoroutineS
236274
237275 companion object {
238276 private val LOG = getLogger<AmazonQLspService >()
277+ private const val MAX_RESTARTS = 5
278+ private const val RESTART_WINDOW_MS = 3 * 60 * 1000
239279 fun getInstance (project : Project ) = project.service<AmazonQLspService >()
240280
241281 fun <T > executeIfRunning (project : Project , runnable : AmazonQLspService .(AmazonQLanguageServer ) -> T ): T ? =
0 commit comments