@@ -26,6 +26,7 @@ import java.util.*
2626import java.util.concurrent.CompletableFuture
2727import java.util.concurrent.ConcurrentHashMap
2828import java.util.concurrent.atomic.AtomicReference
29+ import java.util.concurrent.Semaphore
2930
3031@Suppress(" UnstableApiUsage" )
3132abstract class AbstractGitpodPortForwardingService : GitpodPortForwardingService {
@@ -50,6 +51,13 @@ abstract class AbstractGitpodPortForwardingService : GitpodPortForwardingService
5051 // Debounce job for port updates
5152 private var debounceJob: Job ? = null
5253
54+ // Batch size for port operations
55+ private val BATCH_SIZE = 10
56+ private val BATCH_DELAY = 100
57+
58+ // Semaphore to limit concurrent operations
59+ private val operationSemaphore = Semaphore (10 )
60+
5361 init { start() }
5462
5563 private fun start () {
@@ -106,7 +114,7 @@ abstract class AbstractGitpodPortForwardingService : GitpodPortForwardingService
106114
107115 // Create new debounce job
108116 debounceJob = runJob(lifetime) {
109- delay(300 ) // 300ms debounce delay
117+ delay(500 )
110118 try {
111119 if (syncInProgress.compareAndSet(false , true )) {
112120 try {
@@ -175,30 +183,56 @@ abstract class AbstractGitpodPortForwardingService : GitpodPortForwardingService
175183 .map { it.hostPortNumber }
176184 .filter { portsNumbersFromNonServedPorts.contains(it) || ! portsNumbersFromPortsList.contains(it) }
177185
178- // Process port changes in background
186+ // Process port changes in background with batching
179187 runJob(lifetime) {
180188 try {
181- // Stop unnecessary ports first
182- forwardedPortsToStopForwarding.forEach { stopForwarding(it) }
183- exposedPortsToStopExposingOnClient.forEach { stopExposingOnClient(it) }
184-
185- // Start necessary ports
186- servedPortsToStartForwarding.forEach {
187- startForwarding(it)
188- allPortsToKeep.add(it.localPort)
189+ forwardedPortsToStopForwarding.chunked(BATCH_SIZE ).forEach { batch ->
190+ batch.forEach { port ->
191+ operationSemaphore.withPermit {
192+ stopForwarding(port)
193+ }
194+ }
195+ delay(100 ) // Add delay between batches
189196 }
190197
191- exposedPortsToStartExposingOnClient.forEach {
192- startExposingOnClient(it)
193- allPortsToKeep.add(it.localPort)
198+ exposedPortsToStopExposingOnClient.chunked(BATCH_SIZE ).forEach { batch ->
199+ batch.forEach { port ->
200+ operationSemaphore.withPermit {
201+ stopExposingOnClient(port)
202+ }
203+ }
204+ delay(BATCH_DELAY )
194205 }
195206
196- // Update presentation for all ports
197- application.invokeLater {
198- portsList.forEach {
199- updatePortsPresentation(it)
200- allPortsToKeep.add(it.localPort)
207+ servedPortsToStartForwarding.chunked(BATCH_SIZE ).forEach { batch ->
208+ batch.forEach { port ->
209+ operationSemaphore.withPermit {
210+ startForwarding(port)
211+ allPortsToKeep.add(port.localPort)
212+ }
213+ }
214+ delay(BATCH_DELAY )
215+ }
216+
217+ exposedPortsToStartExposingOnClient.chunked(BATCH_SIZE ).forEach { batch ->
218+ batch.forEach { port ->
219+ operationSemaphore.withPermit {
220+ startExposingOnClient(port)
221+ allPortsToKeep.add(port.localPort)
222+ }
223+ }
224+ delay(BATCH_DELAY )
225+ }
226+
227+ // Update presentation for all ports in batches
228+ portsList.chunked(BATCH_SIZE ).forEach { batch ->
229+ application.invokeLater {
230+ batch.forEach {
231+ updatePortsPresentation(it)
232+ allPortsToKeep.add(it.localPort)
233+ }
201234 }
235+ delay(BATCH_DELAY )
202236 }
203237
204238 cleanupUnusedLifetimes(allPortsToKeep)
0 commit comments