1111 */
1212package com.redhat.devtools.gateway
1313
14- import com.intellij.openapi.diagnostic.thisLogger
1514import com.jetbrains.gateway.thinClientLink.LinkedClientManager
1615import com.jetbrains.gateway.thinClientLink.ThinClientHandle
1716import com.jetbrains.rd.util.lifetime.Lifetime
1817import com.redhat.devtools.gateway.openshift.DevWorkspaces
1918import com.redhat.devtools.gateway.openshift.Pods
2019import com.redhat.devtools.gateway.server.RemoteIDEServer
2120import io.kubernetes.client.openapi.ApiException
21+ import kotlinx.coroutines.CoroutineScope
22+ import kotlinx.coroutines.Dispatchers
23+ import kotlinx.coroutines.launch
24+ import java.io.Closeable
2225import java.io.IOException
2326import java.net.ServerSocket
2427import java.net.URI
@@ -46,21 +49,14 @@ class DevSpacesConnection(private val devSpacesContext: DevSpacesContext) {
4649 onDisconnected : () -> Unit
4750 ): ThinClientHandle {
4851 val workspace = devSpacesContext.devWorkspace
52+ devSpacesContext.addWorkspace(workspace)
4953
50- synchronized(devSpacesContext.activeWorkspaces) {
51- if (devSpacesContext.activeWorkspaces.contains(workspace)) {
52- throw IllegalStateException (" Workspace '${workspace.name} ' is already connected." )
53- }
54- devSpacesContext.activeWorkspaces.add(workspace)
55- }
56-
57- var client: ThinClientHandle ? = null
58- var forwarder: AutoCloseable ? = null
54+ var remoteIdeServer: RemoteIDEServer ? = null
55+ var forwarder: Closeable ? = null
5956
60- try {
57+ return try {
6158 startAndWaitDevWorkspace()
62-
63- val remoteIdeServer = RemoteIDEServer (devSpacesContext)
59+ remoteIdeServer = RemoteIDEServer (devSpacesContext)
6460 val remoteIdeServerStatus = remoteIdeServer.getStatus()
6561 val joinLink = remoteIdeServerStatus.joinLink
6662 ? : throw IOException (" Could not connect, remote IDE is not ready. No join link present." )
@@ -72,7 +68,7 @@ class DevSpacesConnection(private val devSpacesContext: DevSpacesContext) {
7268
7369 val effectiveJoinLink = joinLink.replace(" :5990" , " :$localPort " )
7470
75- client = LinkedClientManager
71+ val client = LinkedClientManager
7672 .getInstance()
7773 .startNewClient(
7874 Lifetime .Eternal ,
@@ -82,101 +78,40 @@ class DevSpacesConnection(private val devSpacesContext: DevSpacesContext) {
8278 false
8379 )
8480
85- client.run {
86- lifetime.onTermination {
87- try {
88- forwarder.close()
89- } catch (_: Exception ) {
90- // ignore cleanup errors
91- }
92- }
93-
94- lifetime.onTermination {
95- try {
96- if (remoteIdeServer.waitServerTerminated()) {
97- DevWorkspaces (devSpacesContext.client)
98- .stop(
99- devSpacesContext.devWorkspace.namespace,
100- devSpacesContext.devWorkspace.name
101- )
102- onDevWorkspaceStopped() // UI refresh through callback
103- }
104- } finally {
105- synchronized(devSpacesContext.activeWorkspaces) {
106- devSpacesContext.activeWorkspaces.remove(workspace)
107- }
108- onDisconnected()
109- }
110- }
111-
112- lifetime.onTermination {
113- onDisconnected() // UI refresh through callback
114- }
81+ client.clientClosed.advise(client.lifetime) {
82+ onClientClosed(onDisconnected , onDevWorkspaceStopped, remoteIdeServer, forwarder)
11583 }
11684
117- return client
85+ client
11886 } catch (e: Exception ) {
119- try {
120- disconnectAndCleanup(client, forwarder, onDevWorkspaceStopped, onDisconnected) // Cancel if started
121- } catch (_: Exception ) {}
122-
123- try {
124- forwarder?.close()
125- } catch (_: Exception ) {}
126-
127- synchronized(devSpacesContext.activeWorkspaces) {
128- devSpacesContext.activeWorkspaces.remove(workspace)
129- }
130-
131- // Make sure UI refresh still happens on failure
132- onDisconnected()
133-
87+ onClientClosed(onDisconnected, onDevWorkspaceStopped, remoteIdeServer, forwarder)
13488 throw e
13589 }
13690 }
13791
138- private fun disconnectAndCleanup (
139- client : ThinClientHandle ? ,
140- forwarder : AutoCloseable ? ,
92+ private fun onClientClosed (
93+ onDisconnected : () -> Unit ,
14194 onDevWorkspaceStopped : () -> Unit ,
142- onDisconnected : () -> Unit
95+ remoteIdeServer : RemoteIDEServer ? ,
96+ forwarder : Closeable ?
14397 ) {
144- if (client == null ) {
145- onDisconnected()
146- return
147- }
148-
149- try {
150- // Close the port forwarder first
98+ CoroutineScope (Dispatchers .IO ).launch {
99+ val currentWorkspace = devSpacesContext.devWorkspace
151100 try {
101+ onDisconnected.invoke()
102+ if (true == remoteIdeServer?.waitServerTerminated()) {
103+ DevWorkspaces (devSpacesContext.client)
104+ .stop(
105+ devSpacesContext.devWorkspace.namespace,
106+ devSpacesContext.devWorkspace.name
107+ )
108+ .also { onDevWorkspaceStopped() }
109+ }
152110 forwarder?.close()
153- } catch (e: Exception ) {
154- thisLogger().debug(" Failed to close port forwarder: ${e.message} " )
155- }
156-
157- // Stop workspace cleanly
158- val devWorkspaces = DevWorkspaces (devSpacesContext.client)
159- val workspace = devSpacesContext.devWorkspace
160-
161- try {
162- devWorkspaces.stop(workspace.namespace, workspace.name)
163- onDevWorkspaceStopped()
164- } catch (e: Exception ) {
165- thisLogger().debug(" Workspace stop failed: ${e.message} " )
166- }
167-
168- // Remove from active list and update state
169- synchronized(devSpacesContext.activeWorkspaces) {
170- devSpacesContext.activeWorkspaces.remove(workspace)
171- }
172- onDisconnected()
173-
174- } catch (e: Exception ) {
175- thisLogger().debug(" Error while terminating client: ${e.message} " )
176- synchronized(devSpacesContext.activeWorkspaces) {
177- devSpacesContext.activeWorkspaces.remove(devSpacesContext.devWorkspace)
111+ } finally {
112+ devSpacesContext.removeWorkspace(currentWorkspace)
113+ onDisconnected()
178114 }
179- onDisconnected()
180115 }
181116 }
182117
0 commit comments