carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package rollout
2
3import (
4 "context"
5 "time"
6
7 "github.com/carlosframework/platform/internal/appconfig"
8)
9
10// configTrack is one instance's two config revisions (spec §8.1). Delivered
11// is what config sync last wrote; active is the revision the current
12// generation started with. They are equal at rest.
13type configTrack struct {
14 delivered string
15 active string
16 // sleeping: the instance had no process when delivered was written
17 // (spec §8.1). Its next wake starts with that file, so no attempt is
18 // pending and the ack goes out at once, marked Hibernating.
19 sleeping bool
20 fails int
21 lastErr string
22
23 ack *appconfig.ConfigAck
24 ackRev string
25 ackWrite func(context.Context, appconfig.ConfigAck)
26}
27
28// configBackoff is the wait after the nth consecutive failed config attempt:
29// one minute, doubling, at most thirty (spec §8.1).
30func configBackoff(n int) time.Duration {
31 d := time.Minute
32 for i := 1; i < n && d < 30*time.Minute; i++ {
33 d *= 2
34 }
35 return min(d, 30*time.Minute)
36}
37
38// deferAck holds a config ack until the revision it was written for is
39// active, and returns a func to run now when it already is. A route config
40// sync never reported a delivery for (a shape the scheduler does not
41// restart) acknowledges at once, as before. So does a sleeping instance,
42// with today's hibernating acknowledgement: the config landed and nothing
43// woke to run it (ConfigAck.Hibernating, "delivered on wake").
44func (t *configTrack) deferAck(a appconfig.ConfigAck, write func(context.Context, appconfig.ConfigAck)) func(context.Context) {
45 // A newer delivery supersedes any ack still held for an older one, whether
46 // or not this one waits: releasing the old ack later would write it after
47 // the newer ack, with a revision that is no longer delivered (spec §8.1).
48 t.ack, t.ackWrite, t.ackRev = nil, nil, ""
49 if t.sleeping {
50 a.Hibernating, a.Restarted = true, false
51 return func(ctx context.Context) { write(ctx, a) }
52 }
53 if t.delivered == "" || t.active == t.delivered {
54 return func(ctx context.Context) { write(ctx, a) }
55 }
56 t.ack, t.ackRev, t.ackWrite = &a, t.delivered, write
57 return nil
58}
59
60// activate records rev as active and returns the held ack when it was
61// waiting for exactly rev and rev is still the delivered revision.
62func (t *configTrack) activate(rev string) (func(context.Context), bool) {
63 if rev == "" {
64 return nil, false
65 }
66 t.active = rev
67 if rev == t.delivered {
68 t.fails, t.lastErr = 0, ""
69 }
70 if t.ack == nil || t.ackRev != rev || rev != t.delivered {
71 return nil, false
72 }
73 a, write := *t.ack, t.ackWrite
74 t.ack, t.ackWrite, t.ackRev = nil, nil, ""
75 return func(ctx context.Context) { write(ctx, a) }, true
76}
77