Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Code

392 commit(s) on main

Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys keenan · 50m ago 4b8c9e1d
history blame raw 3.3 KiB
1package web
2
3// Runner presence and queue depth for the Admin page's Runners tab.
4//
5// A runner is not an entity the forge stores: it is an SSH channel that leases jobs,
6// authenticated by a key. So this is built from two derived facts in the queue — the
7// presence rows a session writes while it is connected, and the work it is holding —
8// plus the queue depth per repository. Nothing here is authoritative; it answers "is
9// my build waiting because nobody is listening", which is otherwise a question about
10// a process on somebody else's machine.
11
12import (
13 "sort"
14 "strings"
15 "time"
16)
17
18// presenceWindow is how long a silent runner is still shown. A resident runner
19// refreshes presence at least once per lease wait (25s) or heartbeat (a third of its
20// lease), so two minutes is a runner that died rather than one that is thinking.
21const presenceWindow = 2 * time.Minute
22
23type runnerView struct {
24 Account string
25 Repo string
26 RunID string
27 Job string
28 Seen time.Time
29 Idle bool
30 // Labels are what the runner says it is, joined for display. They matter here
31 // because a job waiting for a machine that is not connected is invisible from the
32 // queue side: the depth says seven waiting, and only this says that nobody
33 // carrying the label the job asks for has ever connected.
34 Labels string
35}
36
37type queueView struct {
38 Name string
39 Queued int
40 Running int
41 // Cap is the per-repository bound on running jobs, 0 when there is none. It is
42 // shown beside the depth because a queue that is full and not draining is the
43 // question this page exists to answer, and "running 4 of 4" answers it in one
44 // line where "4 running, 7 waiting" leaves the reader guessing.
45 Cap int
46}
47
48// runnerStatus reads the queue for the status page. The status page is an
49// instance-admin surface, so every repository is visible, including private ones.
50// Problems are returned alongside whatever could still be read.
51func (w *Web) runnerStatus() (sessions []runnerView, queue []queueView, problems []string) {
52 if w.Runs == nil {
53 return nil, nil, nil
54 }
55 now := time.Now().UTC()
56
57 rows, err := w.Runs.Sessions(now.Add(-presenceWindow))
58 if err != nil {
59 problems = append(problems, "sessions: "+err.Error())
60 } else {
61 for _, s := range rows {
62 v := runnerView{
63 Account: s.Name, Repo: s.Repo, RunID: s.RunID, Seen: s.LastSeen,
64 Idle: s.RunID == "", Labels: strings.Join(s.Labels, ", "),
65 }
66 if s.Repo != "" {
67 if w.Repos == nil {
68 continue
69 }
70 if _, err := w.Repos.Open(s.Repo); err != nil {
71 continue // a stale row for a deleted repository
72 }
73 if run, err := w.Runs.Get(s.RunID); err == nil && run != nil {
74 v.Job = run.Job
75 }
76 }
77 sessions = append(sessions, v)
78 }
79 }
80
81 depths, err := w.Runs.QueueByRepo()
82 if err != nil {
83 problems = append(problems, "queue: "+err.Error())
84 return sessions, nil, problems
85 }
86 perRepoCap := 0
87 if w.Cfg != nil {
88 perRepoCap = w.Cfg.MaxJobsPerRepo
89 }
90 for name, d := range depths {
91 if w.Repos == nil {
92 continue
93 }
94 if _, err := w.Repos.Open(name); err != nil {
95 continue // a queue row whose repository is gone
96 }
97 queue = append(queue, queueView{Name: name, Queued: d.Queued, Running: d.Running, Cap: perRepoCap})
98 }
99 sort.Slice(queue, func(i, j int) bool { return queue[i].Name < queue[j].Name })
100 return sessions, queue, problems
101}
102