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/activator"
8 "github.com/carlosframework/platform/internal/registry"
9 "github.com/carlosframework/platform/internal/release"
10)
11
12// executeWorker runs one resolved attempt on a socketless host (spec §6.5,
13// §7.9) through the activator's same-entry replacement. The host's good
14// build, by its retained identity, goes with it as the fallback for a failed
15// adoption in overlap mode; the activator decides whether it applies.
16//
17// While a hold-ran marker of the app stands on the host, the replacement
18// carries no fallback (Astra R3 N5, spec §6.4): a hold build ran there
19// without passing and may have migrated the database, so a failed
20// replacement must not start the good build against it.
21//
22// An abandoned result (a staging refusal, a busy entry, a re-check that
23// refused) is deferred by done(), as a refused swap is: while the attempt is
24// still the host's newest it waits abandonDeferral, and one superseded by a
25// newer desire does not wait at all.
26func (s *scheduler) executeWorker(ctx context.Context, rt registry.Route, a activator.Attempt) activator.SwapResult {
27 m := s.m
28 var fallback *release.StagedIdentity
29 acct := release.RouteAccount(rt)
30 if _, marked := holdRanFor(m.d.Book, acct, rt.App, rt.Host); !marked {
31 if g := rt.Good(); g.Version != "" && g.Digest != "" && g.Version != a.Build {
32 fallback = &release.StagedIdentity{Version: g.Version, Digest: g.Digest, Demand: g.Demand,
33 StagedPath: release.VersionPath(m.d.OptDir, acct, g.Version, rt.App)}
34 }
35 }
36 m.emit(rt, EvSwapping, "info", textSwapping(a.Build, a.Cause))
37 return m.d.Act.ReplaceWorker(ctx, a, fallback, s.newest)
38}
39
40// admitOne is admit for one host, for the recycle hook: the host's pending
41// attempt, if it is a release-class attempt (an adoption) the queue would
42// start right now. It honours everything admit does for that host: its
43// waits, its cohort's canary and settle, a halt, and swap_concurrency. A
44// restart or config attempt is not taken: the recycle runs on the serving
45// build, and that attempt follows in its turn. The caller holds the
46// scheduler's lock.
47func (q *appQueue) admitOne(host string, now time.Time) (dispatch, bool) {
48 h := q.hosts[host]
49 if h == nil || h.running != nil || q.running >= q.concurrency {
50 return dispatch{}, false
51 }
52 d, ok := q.gate(h, now)
53 if !ok || d.Class != classAdopt {
54 return dispatch{}, false
55 }
56 q.start(h, d)
57 return d, true
58}
59
60// CoalesceRecycle is plan 4's Service.CoalesceRecycle (spec §7.9). The
61// activator calls it under its own lock (activator.Service.mu), so it calls
62// nothing in the activator and waits on nothing that could: it takes
63// Manager.mu (schedulerOf) and then the scheduler's lock, the documented
64// order, and the attempt it admits runs on its own goroutine. True means an
65// attempt makes the spawn and the recycle is skipped: one admitted now, or
66// one already running for the host (ReplaceWorker respawns that child
67// itself, and its observation reads a crash as the build's). Once Close has
68// begun it is also true, with nothing admitted.
69func (m *Manager) CoalesceRecycle(host string) bool {
70 s := m.schedulerOf(host)
71 if s == nil {
72 return false
73 }
74 s.mu.Lock()
75 if h := s.q.hosts[host]; h != nil && h.running != nil {
76 s.mu.Unlock()
77 return true
78 }
79 // Counted before it is admitted, as the scheduler loop counts its
80 // admissions, so Close joins its done() (spec §8.6, Task 11 fix round
81 // 1). joinMu is a leaf, so it may be taken here under activator.Service.mu
82 // and s.mu. Once Close has begun nothing is admitted and the recycle is
83 // skipped: the agent is stopping, and the child goes with it.
84 if !m.enter(true) {
85 s.mu.Unlock()
86 return true
87 }
88 d, ok := s.q.admitOne(host, m.d.Now())
89 if !ok {
90 m.leave()
91 s.mu.Unlock()
92 return false
93 }
94 s.mu.Unlock()
95 go func() {
96 defer m.leave()
97 s.execute(m.ctx, d)
98 }()
99 return true
100}
101