Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

impl: improved debugging with named coroutines and additional logging#185

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
fioan89 merged 8 commits intomainfromimpl-improve-diagnose
Aug 28, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletionsCHANGELOG.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,10 @@

## Unreleased

### Added

- improved diagnose support

## 0.6.3 - 2025-08-25

### Added
Expand Down
2 changes: 1 addition & 1 deletiongradle.properties
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
version=0.6.3
version=0.6.4
group=com.coder.toolbox
name=coder-toolbox
25 changes: 13 additions & 12 deletionssrc/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,6 +24,7 @@ import com.jetbrains.toolbox.api.remoteDev.states.EnvironmentDescription
import com.jetbrains.toolbox.api.remoteDev.states.RemoteEnvironmentState
import com.jetbrains.toolbox.api.ui.actions.ActionDescription
import com.squareup.moshi.Moshi
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
Expand DownExpand Up@@ -81,7 +82,7 @@ class CoderRemoteEnvironment(
val actions = mutableListOf<Action>()
if (wsRawStatus.canStop()) {
actions.add(Action(context.i18n.ptrl("Open web terminal")) {
context.cs.launch {
context.cs.launch(CoroutineName("Open Web Terminal Action")) {
context.desktop.browse(client.url.withPath("/${workspace.ownerName}/$name/terminal").toString()) {
context.ui.showErrorInfoPopup(it)
}
Expand All@@ -90,7 +91,7 @@ class CoderRemoteEnvironment(
}
actions.add(
Action(context.i18n.ptrl("Open in dashboard")) {
context.cs.launch {
context.cs.launch(CoroutineName("Open in Dashboard Action")) {
context.desktop.browse(
client.url.withPath("/@${workspace.ownerName}/${workspace.name}").toString()
) {
Expand All@@ -100,7 +101,7 @@ class CoderRemoteEnvironment(
})

actions.add(Action(context.i18n.ptrl("View template")) {
context.cs.launch {
context.cs.launch(CoroutineName("View Template Action")) {
context.desktop.browse(client.url.withPath("/templates/${workspace.templateName}").toString()) {
context.ui.showErrorInfoPopup(it)
}
Expand All@@ -110,14 +111,14 @@ class CoderRemoteEnvironment(
if (wsRawStatus.canStart()) {
if (workspace.outdated) {
actions.add(Action(context.i18n.ptrl("Update and start")) {
context.cs.launch {
context.cs.launch(CoroutineName("Update and Start Action")) {
val build = client.updateWorkspace(workspace)
update(workspace.copy(latestBuild = build), agent)
}
})
} else {
actions.add(Action(context.i18n.ptrl("Start")) {
context.cs.launch {
context.cs.launch(CoroutineName("Start Action")) {
val build = client.startWorkspace(workspace)
update(workspace.copy(latestBuild = build), agent)

Expand All@@ -128,14 +129,14 @@ class CoderRemoteEnvironment(
if (wsRawStatus.canStop()) {
if (workspace.outdated) {
actions.add(Action(context.i18n.ptrl("Update and restart")) {
context.cs.launch {
context.cs.launch(CoroutineName("Update and Restart Action")) {
val build = client.updateWorkspace(workspace)
update(workspace.copy(latestBuild = build), agent)
}
})
}
actions.add(Action(context.i18n.ptrl("Stop")) {
context.cs.launch {
context.cs.launch(CoroutineName("Stop Action")) {
tryStopSshConnection()

val build = client.stopWorkspace(workspace)
Expand DownExpand Up@@ -169,7 +170,7 @@ class CoderRemoteEnvironment(
pollJob = pollNetworkMetrics()
}

private fun pollNetworkMetrics(): Job = context.cs.launch {
private fun pollNetworkMetrics(): Job = context.cs.launch(CoroutineName("Network Metrics Poller")) {
context.logger.info("Starting the network metrics poll job for $id")
while (isActive) {
context.logger.debug("Searching SSH command's PID for workspace $id...")
Expand DownExpand Up@@ -227,7 +228,7 @@ class CoderRemoteEnvironment(
actionsList.update {
getAvailableActions()
}
context.cs.launch {
context.cs.launch(CoroutineName("Workspace Status Updater")) {
state.update {
wsRawStatus.toRemoteEnvironmentState(context)
}
Expand DownExpand Up@@ -262,7 +263,7 @@ class CoderRemoteEnvironment(
*/
fun startSshConnection(): Boolean {
if (wsRawStatus.ready() && !isConnected.value) {
context.cs.launch {
context.cs.launch(CoroutineName("SSH Connection Trigger")) {
connectionRequest.update {
true
}
Expand All@@ -284,7 +285,7 @@ class CoderRemoteEnvironment(
}

override val deleteActionFlow: StateFlow<(() -> Unit)?> = MutableStateFlow {
context.cs.launch {
context.cs.launch(CoroutineName("Delete Workspace Action")) {
try {
client.removeWorkspace(workspace)
// mark the env as deleting otherwise we will have to
Expand All@@ -293,7 +294,7 @@ class CoderRemoteEnvironment(
WorkspaceAndAgentStatus.DELETING.toRemoteEnvironmentState(context)
}

context.cs.launch {
context.cs.launch(CoroutineName("Workspace Deletion Poller")) {
withTimeout(5.minutes) {
var workspaceStillExists = true
while (context.cs.isActive && workspaceStillExists) {
Expand Down
194 changes: 101 additions & 93 deletionssrc/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ import com.jetbrains.toolbox.api.remoteDev.RemoteProvider
import com.jetbrains.toolbox.api.ui.actions.ActionDelimiter
import com.jetbrains.toolbox.api.ui.actions.ActionDescription
import com.jetbrains.toolbox.api.ui.components.UiPage
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
Expand DownExpand Up@@ -87,113 +88,114 @@ class CoderRemoteProvider(
* workspace is added, reconfigure SSH using the provided cli (including the
* first time).
*/
private fun poll(client: CoderRestClient, cli: CoderCLIManager): Job = context.cs.launch {
var lastPollTime = TimeSource.Monotonic.markNow()
while (isActive) {
try {
context.logger.debug("Fetching workspace agents from ${client.url}")
val resolvedEnvironments = client.workspaces().flatMap { ws ->
// Agents are not included in workspaces that are off
// so fetch them separately.
when (ws.latestBuild.status) {
WorkspaceStatus.RUNNING -> ws.latestBuild.resources
else -> emptyList()
}.ifEmpty {
client.resources(ws)
}.flatMap { resource ->
resource.agents?.distinctBy {
// There can be duplicates with coder_agent_instance.
// TODO: Can we just choose one or do they hold
// different information?
it.name
}?.map { agent ->
// If we have an environment already, update that.
val env = CoderRemoteEnvironment(context, client, cli, ws, agent)
lastEnvironments.firstOrNull { it == env }?.let {
it.update(ws, agent)
it
} ?: env
} ?: emptyList()
}
}.toSet()
private fun poll(client: CoderRestClient, cli: CoderCLIManager): Job =
context.cs.launch(CoroutineName("Workspace Poller")) {
var lastPollTime = TimeSource.Monotonic.markNow()
while (isActive) {
try {
context.logger.debug("Fetching workspace agents from ${client.url}")
val resolvedEnvironments = client.workspaces().flatMap { ws ->
// Agents are not included in workspaces that are off
// so fetch them separately.
when (ws.latestBuild.status) {
WorkspaceStatus.RUNNING -> ws.latestBuild.resources
else -> emptyList()
}.ifEmpty {
client.resources(ws)
}.flatMap { resource ->
resource.agents?.distinctBy {
// There can be duplicates with coder_agent_instance.
// TODO: Can we just choose one or do they hold
// different information?
it.name
}?.map { agent ->
// If we have an environment already, update that.
val env = CoderRemoteEnvironment(context, client, cli, ws, agent)
lastEnvironments.firstOrNull { it == env }?.let {
it.update(ws, agent)
it
} ?: env
} ?: emptyList()
}
}.toSet()

// In case we logged out while running the query.
if (!isActive) {
return@launch
}
// In case we logged out while running the query.
if (!isActive) {
return@launch
}

// Reconfigure if environments changed.
if (lastEnvironments.size != resolvedEnvironments.size || lastEnvironments != resolvedEnvironments) {
context.logger.info("Workspaces have changed, reconfiguring CLI: $resolvedEnvironments")
cli.configSsh(resolvedEnvironments.map { it.asPairOfWorkspaceAndAgent() }.toSet())
}
// Reconfigure if environments changed.
if (lastEnvironments.size != resolvedEnvironments.size || lastEnvironments != resolvedEnvironments) {
context.logger.info("Workspaces have changed, reconfiguring CLI: $resolvedEnvironments")
cli.configSsh(resolvedEnvironments.map { it.asPairOfWorkspaceAndAgent() }.toSet())
}

environments.update {
LoadableState.Value(resolvedEnvironments.toList())
}
if (!isInitialized.value) {
context.logger.info("Environments for ${client.url} are now initialized")
isInitialized.update {
true
environments.update {
LoadableState.Value(resolvedEnvironments.toList())
}
if (!isInitialized.value) {
context.logger.info("Environments for ${client.url} are now initialized")
isInitialized.update {
true
}
}
lastEnvironments.apply {
clear()
addAll(resolvedEnvironments.sortedBy { it.id })
}
}
lastEnvironments.apply {
clear()
addAll(resolvedEnvironments.sortedBy { it.id })
}

if (WorkspaceConnectionManager.shouldEstablishWorkspaceConnections) {
WorkspaceConnectionManager.allConnected().forEach { wsId ->
val env = lastEnvironments.firstOrNull() { it.id == wsId }
if (env != null && !env.isConnected()) {
context.logger.info("Establishing lost SSH connection for workspace with id $wsId")
if (!env.startSshConnection()) {
context.logger.info("Can't establish lost SSH connection for workspace with id $wsId")
if (WorkspaceConnectionManager.shouldEstablishWorkspaceConnections) {
WorkspaceConnectionManager.allConnected().forEach { wsId ->
val env = lastEnvironments.firstOrNull() { it.id == wsId }
if (env != null && !env.isConnected()) {
context.logger.info("Establishing lost SSH connection for workspace with id $wsId")
if (!env.startSshConnection()) {
context.logger.info("Can't establish lost SSH connection for workspace with id $wsId")
}
}
}
WorkspaceConnectionManager.reset()
}
WorkspaceConnectionManager.reset()
}

WorkspaceConnectionManager.collectStatuses(lastEnvironments)
} catch (_: CancellationException) {
context.logger.debug("${client.url} polling loop canceled")
break
} catch (ex: Exception) {
val elapsed = lastPollTime.elapsedNow()
if (elapsed > POLL_INTERVAL * 2) {
context.logger.info("wake-up from an OS sleep was detected")
} else {
context.logger.error(ex, "workspace polling error encountered")
if (ex is APIResponseException && ex.isTokenExpired) {
WorkspaceConnectionManager.shouldEstablishWorkspaceConnections = true
close()
context.envPageManager.showPluginEnvironmentsPage()
errorBuffer.add(ex)
break
WorkspaceConnectionManager.collectStatuses(lastEnvironments)
} catch (_: CancellationException) {
context.logger.debug("${client.url} polling loop canceled")
break
} catch (ex: Exception) {
val elapsed = lastPollTime.elapsedNow()
if (elapsed > POLL_INTERVAL * 2) {
context.logger.info("wake-up from an OS sleep was detected")
} else {
context.logger.error(ex, "workspace polling error encountered")
if (ex is APIResponseException && ex.isTokenExpired) {
WorkspaceConnectionManager.shouldEstablishWorkspaceConnections = true
close()
context.envPageManager.showPluginEnvironmentsPage()
errorBuffer.add(ex)
break
}
}
}
}

select {
onTimeout(POLL_INTERVAL) {
context.logger.debug("workspace poller waked up by the $POLL_INTERVAL timeout")
}
triggerSshConfig.onReceive { shouldTrigger ->
if (shouldTrigger) {
context.logger.debug("workspace poller waked up because it should reconfigure the ssh configurations")
cli.configSsh(lastEnvironments.map { it.asPairOfWorkspaceAndAgent() }.toSet())
select {
onTimeout(POLL_INTERVAL) {
context.logger.debug("workspace poller waked up by the $POLL_INTERVAL timeout")
}
}
triggerProviderVisible.onReceive { isCoderProviderVisible ->
if (isCoderProviderVisible) {
context.logger.debug("workspace poller waked up by Coder Toolbox which is currently visible, fetching latest workspace statuses")
triggerSshConfig.onReceive { shouldTrigger ->
if (shouldTrigger) {
context.logger.debug("workspace poller waked up because it should reconfigure the ssh configurations")
cli.configSsh(lastEnvironments.map { it.asPairOfWorkspaceAndAgent() }.toSet())
}
}
triggerProviderVisible.onReceive { isCoderProviderVisible ->
if (isCoderProviderVisible) {
context.logger.debug("workspace poller waked up by Coder Toolbox which is currently visible, fetching latest workspace statuses")
}
}
}
lastPollTime = TimeSource.Monotonic.markNow()
}
lastPollTime = TimeSource.Monotonic.markNow()
}
}

/**
* Stop polling, clear the client and environments, then go back to the
Expand DownExpand Up@@ -221,7 +223,7 @@ class CoderRemoteProvider(
override val additionalPluginActions: StateFlow<List<ActionDescription>> = MutableStateFlow(
listOf(
Action(context.i18n.ptrl("Create workspace")) {
context.cs.launch {
context.cs.launch(CoroutineName("Create Workspace Action")) {
context.desktop.browse(client?.url?.withPath("/templates").toString()) {
context.ui.showErrorInfoPopup(it)
}
Expand DownExpand Up@@ -299,7 +301,7 @@ class CoderRemoteProvider(
visibility
}
if (visibility.providerVisible) {
context.cs.launch {
context.cs.launch(CoroutineName("Notify Plugin Visibility")) {
triggerProviderVisible.send(true)
}
}
Expand DownExpand Up@@ -396,11 +398,17 @@ class CoderRemoteProvider(
context.secrets.lastDeploymentURL = client.url.toString()
context.secrets.lastToken = client.token ?: ""
context.secrets.storeTokenFor(client.url, context.secrets.lastToken)
context.logger.info("Deployment URL and token were stored and will be available for automatic connection")
this.client = client
pollJob?.cancel()
pollJob?.let {
it.cancel()
context.logger.info("Workspace poll job with reference ${pollJob} was canceled")
}
environments.showLoadingMessage()
coderHeaderPage.setTitle(context.i18n.pnotr(client.url.toString()))
context.logger.info("Displaying ${client.url} in the UI")
pollJob = poll(client, cli)
context.logger.info("Workspace poll job created with reference $pollJob")
context.envPageManager.showPluginEnvironmentsPage()
}

Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -315,6 +315,7 @@ class CoderCLIManager(
) {
context.logger.info("Configuring SSH config at ${context.settingsStore.sshConfigPath}")
writeSSHConfig(modifySSHConfig(readSSHConfig(), wsWithAgents, feats))
context.logger.info("Finished configuring SSH config")
}

/**
Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp