carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package registry
2
3import (
4 "crypto/rand"
5 "database/sql"
6 "encoding/hex"
7 "errors"
8)
9
10var ErrTimerChanged = errors.New("registry: timer registration changed")
11
12// UpsertTimerRegistration compares the captured registration and returns the
13// resulting registration in the same transaction. Empty means absent at read.
14func (r *Registry) UpsertTimerRegistration(t Timer, expected string) (*Timer, error) {
15 if r.remote != nil {
16 out, err := r.remote.call(nil, remoteRequest{Method: "UpsertTimerRegistration", T: t, Expected: expected}, nil)
17 return out.Timer, err
18 }
19
20 tx, err := r.sql.Begin()
21 if err != nil {
22 return nil, err
23 }
24 defer tx.Rollback()
25 old, err := scanTimer(tx.QueryRow(`SELECT `+timerCols+` FROM timers WHERE host=? AND name=?`, t.Host, t.Name))
26 if errors.Is(err, sql.ErrNoRows) {
27 if expected != "" {
28 return nil, ErrTimerChanged
29 }
30 } else if err != nil {
31 return nil, err
32 } else {
33 if old.Kind != t.Kind {
34 return nil, ErrTimerKind
35 }
36 if expected == "" || old.Registration != expected {
37 return nil, ErrTimerChanged
38 }
39 }
40 rt, err := scanRoute(tx.QueryRow(`SELECT `+routeCols+` FROM routes WHERE host=?`, t.Host).Scan)
41 if err != nil && !errors.Is(err, sql.ErrNoRows) {
42 return nil, err
43 }
44 if err == nil && rt.ClaimOwnership.Valid() {
45 if t.ClaimOwner != "" && (t.ClaimIncarnation != rt.ClaimIncarnation || t.ClaimStamp != rt.ClaimStamp || t.ClaimOwner != rt.ClaimOwner) {
46 return nil, ErrClaimOwnership
47 }
48 t.ClaimOwnership = rt.ClaimOwnership
49 }
50 var id [16]byte
51 if _, err = rand.Read(id[:]); err != nil {
52 return nil, err
53 }
54 t.Registration = hex.EncodeToString(id[:])
55 if t.ClaimOwnership.Valid() {
56 t.ClaimRowID = t.Registration
57 }
58 if err = upsertTimer(tx, t); err != nil {
59 return nil, err
60 }
61 row, err := scanTimer(tx.QueryRow(`SELECT `+timerCols+` FROM timers WHERE host=? AND name=?`, t.Host, t.Name))
62 if err != nil {
63 return nil, err
64 }
65 if err := tx.Commit(); err != nil {
66 return nil, err
67 }
68 return &row, nil
69}
70
71func (r *Registry) SetTimerStatusRegistration(host, name, registration, status string) error {
72 if r.remote != nil {
73 _, err := r.remote.call(nil, remoteRequest{Method: "SetTimerStatusRegistration", Host: host, Name: name, Registration: registration, Status: status}, nil)
74 return err
75 }
76
77 if registration == "" {
78 return ErrTimerChanged
79 }
80 res, err := r.sql.Exec(`UPDATE timers SET last_status=? WHERE host=? AND name=? AND registration=?`, status, host, name, registration)
81 return timerRegistrationResult(res, err)
82}
83
84func (r *Registry) FireTimerNowRegistration(host, name, registration string, now int64) error {
85 if r.remote != nil {
86 _, err := r.remote.call(nil, remoteRequest{Method: "FireTimerNowRegistration", Host: host, Name: name, Registration: registration, Now: now}, nil)
87 return err
88 }
89
90 if registration == "" {
91 return ErrTimerChanged
92 }
93 var id [16]byte
94 if _, err := rand.Read(id[:]); err != nil {
95 return err
96 }
97 next := hex.EncodeToString(id[:])
98 res, err := r.sql.Exec(`UPDATE timers SET next_at=?, due_at=?, attempts=0, registration=?, claim_row_id=CASE WHEN claim_owner='' THEN claim_row_id ELSE ? END WHERE host=? AND name=? AND registration=?`, now, now, next, next, host, name, registration)
99 return timerRegistrationResult(res, err)
100}
101
102func timerRegistrationResult(res sql.Result, err error) error {
103 if err != nil {
104 return err
105 }
106 n, err := res.RowsAffected()
107 if err == nil && n == 0 {
108 return ErrTimerChanged
109 }
110 return err
111}
112