Skip to content

Commit 2d30ac9

Browse files
committed
startup case
1 parent 3e44ed2 commit 2d30ac9

File tree

2 files changed

+28
-13
lines changed
  • plugins/amazonq
    • chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview
    • shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp

2 files changed

+28
-13
lines changed

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import kotlinx.coroutines.flow.Flow
2222
import kotlinx.coroutines.flow.callbackFlow
2323
import kotlinx.coroutines.flow.catch
2424
import kotlinx.coroutines.flow.distinctUntilChanged
25+
import kotlinx.coroutines.flow.first
2526
import kotlinx.coroutines.flow.launchIn
2627
import kotlinx.coroutines.flow.merge
2728
import kotlinx.coroutines.flow.onEach
@@ -568,12 +569,12 @@ class BrowserConnector(
568569
// the flow buffer will never complete so insert some arbitrary timeout until we figure out how to end the flow
569570
// after the error stream is closed and drained
570571
val errorStream = runBlocking { this@executeAsyncIfRunning.errorStream.timeout(500.milliseconds).catch { }.toList() }
571-
throw RuntimeException("LSP execution error. See logs for more details: ${errorStream.joinToString(separator = "")}", ex.cause)
572+
throw IllegalStateException("LSP execution error. See logs for more details: ${errorStream.joinToString(separator = "")}", ex.cause)
572573
}
573574

574575
throw ex
575576
}
576-
} ?: (CompletableFuture.failedFuture(IllegalStateException("LSP Server not running")))
577+
} ?: (CompletableFuture.failedFuture(IllegalStateException("LSP failed to start. See logs for more details: ${AmazonQLspService.getInstance(project).instanceFlow.first().errorStream.timeout(500.milliseconds).catch { }.toList().joinToString(separator = "")}")))
577578

578579
// We assume there is only one outgoing request per tab because the input is
579580
// blocked when there is an outgoing request

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,23 @@ import com.intellij.util.net.ssl.CertificateManager
3131
import kotlinx.coroutines.CoroutineScope
3232
import kotlinx.coroutines.Deferred
3333
import kotlinx.coroutines.Job
34+
import kotlinx.coroutines.TimeoutCancellationException
3435
import kotlinx.coroutines.async
3536
import kotlinx.coroutines.channels.BufferOverflow
3637
import kotlinx.coroutines.delay
3738
import kotlinx.coroutines.flow.Flow
3839
import kotlinx.coroutines.flow.MutableSharedFlow
3940
import kotlinx.coroutines.flow.asSharedFlow
41+
import kotlinx.coroutines.flow.catch
4042
import kotlinx.coroutines.flow.map
43+
import kotlinx.coroutines.flow.timeout
44+
import kotlinx.coroutines.flow.toList
4145
import kotlinx.coroutines.future.asCompletableFuture
4246
import kotlinx.coroutines.future.await
4347
import kotlinx.coroutines.isActive
4448
import kotlinx.coroutines.launch
4549
import kotlinx.coroutines.runBlocking
50+
import kotlinx.coroutines.selects.select
4651
import kotlinx.coroutines.sync.Mutex
4752
import kotlinx.coroutines.sync.withLock
4853
import kotlinx.coroutines.withContext
@@ -103,6 +108,7 @@ import java.nio.file.Files
103108
import java.nio.file.Path
104109
import java.util.concurrent.Future
105110
import java.util.concurrent.TimeUnit
111+
import kotlin.time.Duration.Companion.milliseconds
106112
import kotlin.time.Duration.Companion.seconds
107113

108114
// https://github.com/redhat-developer/lsp4ij/blob/main/src/main/java/com/redhat/devtools/lsp4ij/server/LSPProcessListener.java
@@ -160,7 +166,7 @@ class AmazonQLspService @VisibleForTesting constructor(
160166
constructor(project: Project, cs: CoroutineScope) : this(DefaultAmazonQServerInstanceStarter, project, cs)
161167

162168
private val _flowInstance = MutableSharedFlow<AmazonQServerInstanceFacade>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
163-
val instanceFlow = _flowInstance.asSharedFlow().map { it.languageServer }
169+
val instanceFlow = _flowInstance.asSharedFlow()
164170

165171
private var instance: Deferred<AmazonQServerInstanceFacade>
166172
private val heartbeatJob: Job
@@ -174,20 +180,17 @@ class AmazonQLspService @VisibleForTesting constructor(
174180
// manage lifecycle RAII-like so we can restart at arbitrary time
175181
// and suppress IDE error if server fails to start
176182
var attempts = 0
183+
var lastInstance: AmazonQServerInstanceFacade? = null
177184
while (isActive && attempts < 3 && checkForRemainingRestartAttempts()) {
178185
try {
179186
// no timeout; start() can download which may take long time
180-
val instance = starter.start(project, cs).also {
187+
lastInstance = starter.start(project, cs).also {
181188
Disposer.register(this@AmazonQLspService, it)
182189
}
183190

184-
// no reason this should take long to process after init key sent
185-
withTimeout(5.seconds) {
186-
// wait for handshake to complete
187-
instance.initializeResult.await()
188-
}
191+
lastInstance.waitForInitializeOrThrow(this)
189192

190-
return@async instance.also {
193+
return@async lastInstance.also {
191194
_flowInstance.emit(it)
192195
}
193196
} catch (e: Exception) {
@@ -196,7 +199,12 @@ class AmazonQLspService @VisibleForTesting constructor(
196199
attempts++
197200
}
198201

199-
error("Failed to start LSP server in 3 attempts")
202+
// does not capture all failure
203+
lastInstance?.let {
204+
_flowInstance.tryEmit(it)
205+
}
206+
207+
lastInstance ?: error("LSP failed all attempts to start")
200208
}
201209

202210
init {
@@ -296,7 +304,7 @@ class AmazonQLspService @VisibleForTesting constructor(
296304
suspend fun<T> execute(runnable: suspend AmazonQLspService.(AmazonQLanguageServer) -> T): T {
297305
val lsp = withTimeout(5.seconds) {
298306
val holder = mutex.withLock { instance }.await()
299-
holder.initializeResult.await()
307+
holder.waitForInitializeOrThrow(this)
300308

301309
holder.languageServer
302310
}
@@ -307,7 +315,7 @@ class AmazonQLspService @VisibleForTesting constructor(
307315
val lsp = try {
308316
withTimeout(5.seconds) {
309317
val holder = mutex.withLock { instance }.await()
310-
holder.initializeResult.await()
318+
holder.waitForInitializeOrThrow(this)
311319

312320
holder
313321
}
@@ -352,6 +360,12 @@ interface AmazonQServerInstanceFacade : Disposable {
352360

353361
val rawEndpoint: RemoteEndpoint
354362
get() = launcher.remoteEndpoint
363+
364+
suspend fun waitForInitializeOrThrow(scope: CoroutineScope) =
365+
select {
366+
initializeResult.onAwait { it }
367+
scope.async { launcherFuture.get() }.onAwait { error(errorStream.timeout(500.milliseconds).catch { }.toList().joinToString(separator = "")) }
368+
}
355369
}
356370

357371
private class AmazonQServerInstance(private val project: Project, private val cs: CoroutineScope) : Disposable, AmazonQServerInstanceFacade {

0 commit comments

Comments
 (0)