@@ -80,6 +80,7 @@ type WorkspaceInfo struct {
80
80
81
81
Ports []PortInfo
82
82
Auth * wsapi.WorkspaceAuthentication
83
+ Phase wsapi.WorkspacePhase
83
84
}
84
85
85
86
// PortInfo contains all information ws-proxy needs to know about a workspace port
@@ -265,7 +266,7 @@ func (p *RemoteWorkspaceInfoProvider) listen(client wsapi.WorkspaceManagerClient
265
266
}
266
267
267
268
if status .Phase == wsapi .WorkspacePhase_STOPPED {
268
- p .cache .Delete (status .Metadata .MetaId )
269
+ p .cache .Delete (status .Metadata .MetaId , status . Id )
269
270
} else {
270
271
info := mapWorkspaceStatusToInfo (status )
271
272
p .cache .Insert (info )
@@ -309,10 +310,11 @@ func mapWorkspaceStatusToInfo(status *wsapi.WorkspaceStatus) *WorkspaceInfo {
309
310
IDEPublicPort : getPortStr (status .Spec .Url ),
310
311
Ports : portInfos ,
311
312
Auth : status .Auth ,
313
+ Phase : status .Phase ,
312
314
}
313
315
}
314
316
315
- // WorkspaceInfo return the WorkspaceInfo avaiable for the given workspaceID.
317
+ // WorkspaceInfo return the WorkspaceInfo available for the given workspaceID.
316
318
// Callers should make sure their context gets canceled properly. For good measure
317
319
// this function will timeout by itself as well.
318
320
func (p * RemoteWorkspaceInfoProvider ) WorkspaceInfo (ctx context.Context , workspaceID string ) * WorkspaceInfo {
@@ -355,10 +357,14 @@ func getPortStr(urlStr string) string {
355
357
return portURL .Port ()
356
358
}
357
359
360
+ // TODO(rl): type def workspaceID and instanceID throughout for better readability and type safety?
361
+ type workspaceInfosByInstance map [string ]* WorkspaceInfo
362
+ type instanceInfosByWorkspace map [string ]workspaceInfosByInstance
363
+
358
364
// workspaceInfoCache stores WorkspaceInfo in a manner which is easy to query for WorkspaceInfoProvider
359
365
type workspaceInfoCache struct {
360
- // WorkspaceInfos indexed by workspaceID
361
- infos map [ string ] * WorkspaceInfo
366
+ // WorkspaceInfos indexed by workspaceID and then instanceID
367
+ infos instanceInfosByWorkspace
362
368
// WorkspaceCoords indexed by public (proxy) port (string)
363
369
coordsByPublicPort map [string ]* WorkspaceCoords
364
370
@@ -371,7 +377,7 @@ type workspaceInfoCache struct {
371
377
func newWorkspaceInfoCache () * workspaceInfoCache {
372
378
var mu sync.RWMutex
373
379
return & workspaceInfoCache {
374
- infos : make (map [string ]* WorkspaceInfo ),
380
+ infos : make (map [string ]workspaceInfosByInstance ),
375
381
coordsByPublicPort : make (map [string ]* WorkspaceCoords ),
376
382
mu : & mu ,
377
383
cond : sync .NewCond (& mu ),
@@ -382,7 +388,7 @@ func (c *workspaceInfoCache) Reinit(infos []*WorkspaceInfo) {
382
388
c .cond .L .Lock ()
383
389
defer c .cond .L .Unlock ()
384
390
385
- c .infos = make (map [string ]* WorkspaceInfo , len (infos ))
391
+ c .infos = make (map [string ]workspaceInfosByInstance , len (infos ))
386
392
c .coordsByPublicPort = make (map [string ]* WorkspaceCoords , len (c .coordsByPublicPort ))
387
393
388
394
for _ , info := range infos {
@@ -400,7 +406,15 @@ func (c *workspaceInfoCache) Insert(info *WorkspaceInfo) {
400
406
}
401
407
402
408
func (c * workspaceInfoCache ) doInsert (info * WorkspaceInfo ) {
403
- c .infos [info .WorkspaceID ] = info
409
+ existingInfos , ok := c .infos [info .WorkspaceID ]
410
+ if ! ok {
411
+ existingInfos = make (map [string ]* WorkspaceInfo )
412
+ }
413
+ existingInfos [info .InstanceID ] = info
414
+ c .infos [info .WorkspaceID ] = existingInfos
415
+
416
+ // NOTE: in the unlikely event that a subsequent insert changes the port
417
+ // then we add it here assuming that the delete will remove it
404
418
c .coordsByPublicPort [info .IDEPublicPort ] = & WorkspaceCoords {
405
419
ID : info .WorkspaceID ,
406
420
}
@@ -413,25 +427,63 @@ func (c *workspaceInfoCache) doInsert(info *WorkspaceInfo) {
413
427
}
414
428
}
415
429
416
- func (c * workspaceInfoCache ) Delete (workspaceID string ) {
430
+ func (c * workspaceInfoCache ) Delete (workspaceID string , instanceID string ) {
417
431
c .cond .L .Lock ()
418
432
defer c .cond .L .Unlock ()
419
433
420
- info , present := c .infos [workspaceID ]
421
- if ! present || info == nil {
434
+ infos , present := c .infos [workspaceID ]
435
+ if ! present || infos == nil {
422
436
return
423
437
}
424
- delete (c .coordsByPublicPort , info .IDEPublicPort )
425
- delete (c .infos , workspaceID )
438
+
439
+ // Keep only the active instances and public port(s) for the workspace id
440
+ var instanceIDEPublicPort string
441
+ if info , ok := infos [instanceID ]; ok {
442
+ delete (infos , instanceID )
443
+ instanceIDEPublicPort = info .IDEPublicPort
444
+ }
445
+
446
+ if len (infos ) == 0 {
447
+ delete (c .infos , workspaceID )
448
+ } else {
449
+ c .infos [workspaceID ] = infos
450
+ }
451
+
452
+ // Clean up the public port if port no longer active
453
+ activePublicPorts := make (map [string ]interface {})
454
+ for _ , info := range infos {
455
+ activePublicPorts [info .IDEPublicPort ] = true
456
+ }
457
+
458
+ if _ , ok := activePublicPorts [instanceIDEPublicPort ]; ! ok {
459
+ log .WithField ("workspaceID" , workspaceID ).WithField ("instanceID" , instanceID ).WithField ("port" , instanceIDEPublicPort ).Debug ("deleting port for workspace" )
460
+ delete (c .coordsByPublicPort , instanceIDEPublicPort )
461
+ }
426
462
}
427
463
428
464
// WaitFor waits for workspace info until that info is available or the context is canceled.
429
465
func (c * workspaceInfoCache ) WaitFor (ctx context.Context , workspaceID string ) (w * WorkspaceInfo , ok bool ) {
430
466
c .mu .RLock ()
431
- w , ok = c .infos [workspaceID ]
467
+ existing , ok : = c .infos [workspaceID ]
432
468
c .mu .RUnlock ()
469
+
470
+ getCandidate := func (infos map [string ]* WorkspaceInfo ) * WorkspaceInfo {
471
+ // Find the *best* candidate i.e. prefer any instance running over stopping/stopped
472
+ // If there are >1 instances in running state it will pick the first one
473
+ // TODO(rl): make this a smarter container? i.e. priorised by (smart) status
474
+ var candidate * WorkspaceInfo
475
+ for _ , info := range infos {
476
+ if candidate == nil || (info .Phase == wsapi .WorkspacePhase_RUNNING && candidate .Phase != info .Phase ) {
477
+ candidate = info
478
+ } else if candidate .Phase < info .Phase {
479
+ candidate = info
480
+ }
481
+ }
482
+ return candidate
483
+ }
484
+
433
485
if ok {
434
- return
486
+ return getCandidate ( existing ), true
435
487
}
436
488
437
489
inc := make (chan * WorkspaceInfo )
@@ -446,12 +498,12 @@ func (c *workspaceInfoCache) WaitFor(ctx context.Context, workspaceID string) (w
446
498
return
447
499
}
448
500
449
- info , ok := c .infos [workspaceID ]
501
+ infos , ok := c .infos [workspaceID ]
450
502
if ! ok {
451
503
continue
452
504
}
453
505
454
- inc <- info
506
+ inc <- getCandidate ( infos )
455
507
return
456
508
}
457
509
}()
0 commit comments