8
8
"math"
9
9
mrand "math/rand"
10
10
"strings"
11
+ "sync/atomic"
11
12
"time"
12
13
13
14
"github.com/hashicorp/go-multierror"
@@ -34,66 +35,68 @@ import (
34
35
35
36
type Controller struct {
36
37
store database.Store
38
+ cfg codersdk.PrebuildsConfig
37
39
pubsub pubsub.Pubsub
38
- logger slog.Logger
39
40
40
- nudgeCh chan * uuid.UUID
41
- closeCh chan struct {}
41
+ logger slog.Logger
42
+ nudgeCh chan * uuid.UUID
43
+ cancelFn context.CancelCauseFunc
44
+ closed atomic.Bool
42
45
}
43
46
44
- func NewController (store database.Store , pubsub pubsub.Pubsub , logger slog.Logger ) * Controller {
47
+ func NewController (store database.Store , pubsub pubsub.Pubsub , cfg codersdk. PrebuildsConfig , logger slog.Logger ) * Controller {
45
48
return & Controller {
46
49
store : store ,
47
50
pubsub : pubsub ,
48
51
logger : logger ,
52
+ cfg : cfg ,
49
53
nudgeCh : make (chan * uuid.UUID , 1 ),
50
- closeCh : make (chan struct {}, 1 ),
51
54
}
52
55
}
53
56
54
- func (c Controller ) Loop (ctx context.Context ) {
55
- ticker := time .NewTicker (time . Second * 5 ) // TODO: configurable? 1m probably lowest valid value
57
+ func (c * Controller ) Loop (ctx context.Context ) error {
58
+ ticker := time .NewTicker (c . cfg . ReconciliationInterval . Value ())
56
59
defer ticker .Stop ()
57
60
58
61
// TODO: create new authz role
59
- ctx = dbauthz .AsSystemRestricted (ctx )
62
+ ctx , cancel := context .WithCancelCause (dbauthz .AsSystemRestricted (ctx ))
63
+ c .cancelFn = cancel
60
64
61
- // TODO: bounded concurrency?
62
- var eg errgroup.Group
63
65
for {
64
66
select {
65
67
// Accept nudges from outside the control loop to trigger a new iteration.
66
68
case template := <- c .nudgeCh :
67
- eg .Go (func () error {
68
- c .reconcile (ctx , template )
69
- return nil
70
- })
69
+ c .reconcile (ctx , template )
71
70
// Trigger a new iteration on each tick.
72
71
case <- ticker .C :
73
- eg .Go (func () error {
74
- c .reconcile (ctx , nil )
75
- return nil
76
- })
77
- case <- c .closeCh :
78
- c .logger .Info (ctx , "control loop stopped" )
79
- goto wait
72
+ c .reconcile (ctx , nil )
80
73
case <- ctx .Done ():
81
- c .logger .Error (context .Background (), "control loop exited: %w " , ctx .Err ())
82
- goto wait
74
+ c .logger .Error (context .Background (), "prebuilds reconciliation loop exited" , slog . Error ( ctx .Err ()), slog . F ( "cause" , context . Cause ( ctx ) ))
75
+ return ctx . Err ()
83
76
}
84
77
}
78
+ }
79
+
80
+ func (c * Controller ) Close (cause error ) {
81
+ if c .isClosed () {
82
+ return
83
+ }
84
+ c .closed .Store (true )
85
+ if c .cancelFn != nil {
86
+ c .cancelFn (cause )
87
+ }
88
+ }
85
89
86
- // TODO: no gotos
87
- wait:
88
- _ = eg .Wait ()
90
+ func (c * Controller ) isClosed () bool {
91
+ return c .closed .Load ()
89
92
}
90
93
91
- func (c Controller ) ReconcileTemplate (templateID uuid.UUID ) {
94
+ func (c * Controller ) ReconcileTemplate (templateID uuid.UUID ) {
92
95
// TODO: replace this with pubsub listening
93
96
c .nudgeCh <- & templateID
94
97
}
95
98
96
- func (c Controller ) reconcile (ctx context.Context , templateID * uuid.UUID ) {
99
+ func (c * Controller ) reconcile (ctx context.Context , templateID * uuid.UUID ) {
97
100
var logger slog.Logger
98
101
if templateID == nil {
99
102
logger = c .logger .With (slog .F ("reconcile_context" , "all" ))
@@ -167,7 +170,7 @@ type reconciliationActions struct {
167
170
168
171
// calculateActions MUST be called within the context of a transaction (TODO: isolation)
169
172
// with an advisory lock to prevent TOCTOU races.
170
- func (c Controller ) calculateActions (ctx context.Context , template database.Template , state database.GetTemplatePrebuildStateRow ) (* reconciliationActions , error ) {
173
+ func (c * Controller ) calculateActions (ctx context.Context , template database.Template , state database.GetTemplatePrebuildStateRow ) (* reconciliationActions , error ) {
171
174
// TODO: align workspace states with how we represent them on the FE and the CLI
172
175
// right now there's some slight differences which can lead to additional prebuilds being created
173
176
@@ -279,7 +282,7 @@ func (c Controller) calculateActions(ctx context.Context, template database.Temp
279
282
return actions , nil
280
283
}
281
284
282
- func (c Controller ) reconcileTemplate (ctx context.Context , template database.Template ) error {
285
+ func (c * Controller ) reconcileTemplate (ctx context.Context , template database.Template ) error {
283
286
logger := c .logger .With (slog .F ("template_id" , template .ID .String ()))
284
287
285
288
// get number of desired vs actual prebuild instances
@@ -360,7 +363,7 @@ func (c Controller) reconcileTemplate(ctx context.Context, template database.Tem
360
363
return nil
361
364
}
362
365
363
- func (c Controller ) createPrebuild (ctx context.Context , db database.Store , prebuildID uuid.UUID , template database.Template , presetID uuid.UUID ) error {
366
+ func (c * Controller ) createPrebuild (ctx context.Context , db database.Store , prebuildID uuid.UUID , template database.Template , presetID uuid.UUID ) error {
364
367
name , err := generateName ()
365
368
if err != nil {
366
369
return xerrors .Errorf ("failed to generate unique prebuild ID: %w" , err )
@@ -394,7 +397,7 @@ func (c Controller) createPrebuild(ctx context.Context, db database.Store, prebu
394
397
395
398
return c .provision (ctx , db , prebuildID , template , presetID , database .WorkspaceTransitionStart , workspace )
396
399
}
397
- func (c Controller ) deletePrebuild (ctx context.Context , db database.Store , prebuildID uuid.UUID , template database.Template , presetID uuid.UUID ) error {
400
+ func (c * Controller ) deletePrebuild (ctx context.Context , db database.Store , prebuildID uuid.UUID , template database.Template , presetID uuid.UUID ) error {
398
401
workspace , err := db .GetWorkspaceByID (ctx , prebuildID )
399
402
if err != nil {
400
403
return xerrors .Errorf ("get workspace by ID: %w" , err )
@@ -406,7 +409,7 @@ func (c Controller) deletePrebuild(ctx context.Context, db database.Store, prebu
406
409
return c .provision (ctx , db , prebuildID , template , presetID , database .WorkspaceTransitionDelete , workspace )
407
410
}
408
411
409
- func (c Controller ) provision (ctx context.Context , db database.Store , prebuildID uuid.UUID , template database.Template , presetID uuid.UUID , transition database.WorkspaceTransition , workspace database.Workspace ) error {
412
+ func (c * Controller ) provision (ctx context.Context , db database.Store , prebuildID uuid.UUID , template database.Template , presetID uuid.UUID , transition database.WorkspaceTransition , workspace database.Workspace ) error {
410
413
tvp , err := db .GetPresetParametersByTemplateVersionID (ctx , template .ActiveVersionID )
411
414
if err != nil {
412
415
return xerrors .Errorf ("fetch preset details: %w" , err )
@@ -464,10 +467,6 @@ func (c Controller) provision(ctx context.Context, db database.Store, prebuildID
464
467
return nil
465
468
}
466
469
467
- func (c Controller ) Stop () {
468
- c .closeCh <- struct {}{}
469
- }
470
-
471
470
// generateName generates a 20-byte prebuild name which should safe to use without truncation in most situations.
472
471
// UUIDs may be too long for a resource name in cloud providers (since this ID will be used in the prebuild's name).
473
472
//
0 commit comments