carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostbox
2
3import (
4 "context"
5 "database/sql"
6 "encoding/json"
7 "errors"
8 "time"
9)
10
11// ExecuteServingPark records uncertainty before invoking filesystem authority.
12// The callback runs without a SQLite transaction: it reads the same journal
13// under root's namespace and host locks. A pending replay revalidates authority
14// and resumes the deterministic retirement; it never rolls filesystem work back.
15func (s *JournalService) ExecuteServingPark(ctx context.Context, peer string, q JournalRequest, run func(context.Context) (ServingParkResult, error)) (JournalResponse, error) {
16 out := JournalResponse{V: 1, ID: q.ID}
17 if err := validateServingParkRequest(q); err != nil {
18 return out, err
19 }
20 if s == nil || s.Launches == nil || s.Launches.db == nil || s.parkGate == nil || peer == "" || run == nil {
21 return out, ErrUnsupported
22 }
23 select {
24 case s.parkGate <- struct{}{}:
25 defer func() { <-s.parkGate }()
26 case <-ctx.Done():
27 return out, ctx.Err()
28 }
29 pending, replayed, err := s.prepareServingPark(ctx, peer, q)
30 if err != nil || replayed {
31 return pending, err
32 }
33 result, err := run(ctx)
34 if err != nil {
35 // Even an identity refusal may follow a prior rename or a lost save
36 // reply. Preserve the pending receipt and the exact request for replay.
37 return out, errors.Join(ErrJournalUncertain, err)
38 }
39 out.ServingPark = &result
40 body, err := json.Marshal(out)
41 if err != nil {
42 return out, errors.Join(ErrJournalUncertain, err)
43 }
44 changed, err := s.Launches.db.ExecContext(ctx, "UPDATE journal_requests SET response=? WHERE id=? AND peer=? AND digest=?", body, q.ID, peer, journalRequestDigest(q))
45 if err != nil {
46 return out, errors.Join(ErrJournalUncertain, err)
47 }
48 n, err := changed.RowsAffected()
49 if err != nil || n != 1 {
50 return out, ErrJournalUncertain
51 }
52 return out, nil
53}
54
55func (s *JournalService) prepareServingPark(ctx context.Context, peer string, q JournalRequest) (JournalResponse, bool, error) {
56 out := JournalResponse{V: 1, ID: q.ID}
57 tx, err := s.Launches.db.BeginTx(ctx, nil)
58 if err != nil {
59 return out, false, err
60 }
61 defer tx.Rollback()
62 var oldPeer, digest string
63 var body []byte
64 err = tx.QueryRowContext(ctx, "SELECT peer,digest,response FROM journal_requests WHERE id=?", q.ID).Scan(&oldPeer, &digest, &body)
65 found := err == nil
66 if found {
67 if oldPeer != peer || digest != journalRequestDigest(q) {
68 return out, false, ErrChanged
69 }
70 if json.Unmarshal(body, &out) != nil || out.V != 1 || out.ID != q.ID {
71 return out, false, ErrJournalUncertain
72 }
73 if out.Error == "" && out.ServingPark != nil {
74 return out, true, nil
75 }
76 if out.Error != "uncertain" || out.ServingPark != nil {
77 return out, false, ErrJournalUncertain
78 }
79 } else if !errors.Is(err, sql.ErrNoRows) {
80 return out, false, err
81 }
82 now := time.Now()
83 if s.Now != nil {
84 now = s.Now()
85 }
86 var rejectBefore int64
87 if err = tx.QueryRowContext(ctx, "SELECT reject_before FROM journal_receipt_retention WHERE id=1").Scan(&rejectBefore); err != nil {
88 return out, false, err
89 }
90 age := now.Sub(time.Unix(0, q.IssuedAt))
91 if q.IssuedAt <= rejectBefore || age > JournalRequestWindow || age < -JournalRequestWindow {
92 return out, false, ErrJournalUncertain
93 }
94 if _, err = journalPrune(ctx, tx, rejectBefore, now); err != nil {
95 return out, false, err
96 }
97 if !found {
98 out.Error = "uncertain"
99 body, err = json.Marshal(out)
100 if err != nil {
101 return out, false, err
102 }
103 if _, err = tx.ExecContext(ctx, "INSERT INTO journal_requests(id,peer,digest,response,created,issued) VALUES(?,?,?,?,?,?)", q.ID, peer, journalRequestDigest(q), body, now.UnixNano(), q.IssuedAt); err != nil {
104 return out, false, err
105 }
106 }
107 return out, false, tx.Commit()
108}
109