Skip to content

Commit 2126fe1

Browse files
committed
Wait for workspace to stop before updating
It turns out if you try to send the update right after stopping it will say a build is already in progress.
1 parent 9aefc46 commit 2126fe1

File tree

3 files changed

+55
-19
lines changed

3 files changed

+55
-19
lines changed

src/main/kotlin/com/coder/gateway/sdk/BaseCoderRestClient.kt

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -213,19 +213,16 @@ open class BaseCoderRestClient(
213213
return buildResponse.body()!!
214214
}
215215

216+
/**
217+
* Start the workspace with the latest template version. Best practice is
218+
* to STOP a workspace before doing an update if it is started.
219+
* 1. If the update changes parameters, the old template might be needed to
220+
* correctly STOP with the existing parameter values.
221+
* 2. The agent gets a new ID and token on each START build. Many template
222+
* authors are not diligent about making sure the agent gets restarted
223+
* with this information when we do two START builds in a row.
224+
*/
216225
fun updateWorkspace(workspace: Workspace): WorkspaceBuild {
217-
// Best practice is to STOP a workspace before doing an update if it is
218-
// started.
219-
// 1. If the update changes parameters, the old template might be needed
220-
// to correctly STOP with the existing parameter values.
221-
// 2. The agent gets a new ID and token on each START build. Many
222-
// template authors are not diligent about making sure the agent gets
223-
// restarted with this information when we do two START builds in a
224-
// row.
225-
if (workspace.latestBuild.transition == WorkspaceTransition.START) {
226-
stopWorkspace(workspace)
227-
}
228-
229226
val template = template(workspace.templateID)
230227

231228
val buildRequest =

src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import java.net.ConnectException
7070
import java.net.SocketTimeoutException
7171
import java.net.URL
7272
import java.net.UnknownHostException
73+
import java.time.Duration
7374
import javax.net.ssl.SSLHandshakeException
7475
import javax.swing.Icon
7576
import javax.swing.JCheckBox
@@ -271,8 +272,47 @@ class CoderWorkspacesStepView(val setNextButtonEnabled: (Boolean) -> Unit) : Cod
271272
cs.launch {
272273
withContext(Dispatchers.IO) {
273274
try {
274-
c.updateWorkspace(workspace)
275-
loadWorkspaces()
275+
// Stop the workspace first if it is running.
276+
if (workspace.latestBuild.status == WorkspaceStatus.RUNNING) {
277+
logger.info("Waiting for ${workspace.name} to stop before updating")
278+
c.stopWorkspace(workspace)
279+
loadWorkspaces()
280+
var elapsed = Duration.ofSeconds(0)
281+
val timeout = Duration.ofSeconds(1)
282+
val maxWait = Duration.ofMinutes(10)
283+
while (isActive) { // Wait for the workspace to fully stop.
284+
delay(timeout.toMillis())
285+
val found = tableOfWorkspaces.items.firstOrNull{ it.workspace.id == workspace.id }
286+
when (val status = found?.workspace?.latestBuild?.status) {
287+
WorkspaceStatus.PENDING, WorkspaceStatus.STOPPING, WorkspaceStatus.RUNNING -> {
288+
logger.info("Still waiting for ${workspace.name} to stop before updating")
289+
}
290+
WorkspaceStatus.STARTING, WorkspaceStatus.FAILED,
291+
WorkspaceStatus.CANCELING, WorkspaceStatus.CANCELED,
292+
WorkspaceStatus.DELETING, WorkspaceStatus.DELETED -> {
293+
logger.warn("Canceled ${workspace.name} update due to status change to $status")
294+
break
295+
}
296+
null -> {
297+
logger.warn("Canceled ${workspace.name} update because it no longer exists")
298+
break
299+
}
300+
WorkspaceStatus.STOPPED -> {
301+
logger.info("${workspace.name} has stopped; updating now")
302+
c.updateWorkspace(workspace)
303+
break
304+
}
305+
}
306+
elapsed += timeout
307+
if (elapsed > maxWait) {
308+
logger.error("Canceled ${workspace.name} update because it took took longer than ${maxWait.toMinutes()} minutes to stop")
309+
break
310+
}
311+
}
312+
} else {
313+
c.updateWorkspace(workspace)
314+
loadWorkspaces()
315+
}
276316
} catch (e: WorkspaceResponseException) {
277317
logger.warn("Could not update workspace ${workspace.name}, reason: $e")
278318
} catch (e: TemplateResponseException) {

src/test/kotlin/com/coder/gateway/sdk/BaseCoderRestClientTest.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -235,26 +235,25 @@ class BaseCoderRestClientTest {
235235
})
236236

237237
// Fails to stop a non-existent workspace.
238-
val badWorkspace = DataGen.workspace("bad")
238+
val badWorkspace = DataGen.workspace("bad", templates[0].id)
239239
val ex = assertFailsWith(
240240
exceptionClass = WorkspaceResponseException::class,
241241
block = { client.updateWorkspace(badWorkspace) })
242-
assertEquals(listOf(Pair("stop", badWorkspace.id)), actions)
242+
assertEquals(listOf(
243+
Pair("get_template", badWorkspace.templateID),
244+
Pair("update", badWorkspace.id)), actions)
243245
assertContains(ex.message.toString(), "The requested resource could not be found")
244246
actions.clear()
245247

246-
// When workspace is started it should stop first.
247248
with(workspaces[0]) {
248249
client.updateWorkspace(this)
249250
val expected = listOf(
250-
Pair("stop", id),
251251
Pair("get_template", templateID),
252252
Pair("update", id))
253253
assertEquals(expected, actions)
254254
actions.clear()
255255
}
256256

257-
// When workspace is stopped it will not stop first.
258257
with(workspaces[1]) {
259258
client.updateWorkspace(this)
260259
val expected = listOf(

0 commit comments

Comments
 (0)