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.

Stop repeating the index check and the history summaries

73ec800beafe53eea186fbc1cebec3306cc49da0 · Keenan Nemetz · 10d ago · parent 5d440e37

Stop repeating the index check and the history summaries

The request memo covered the repository, the open counts and the change
list, but two more reads were repeated:

- The index refresh (issue rows, filters, `#42` numbers) ran on every
  surface that shows one, and each run started by resolving the metadata
  ref. A page calls it from the header, the number lookup and its own
  view, so it now happens once per request, and the resolved commit is
  shared with the change-request read instead of being resolved twice.
- The overview walked the repository's whole history on every load:
  `shortlog` for contributors, `rev-list --count` for the code page's
  commit count, and the tree grouping for languages. Those are properties
  of a commit, and a commit id is content-addressed, so they cannot go
  stale: they are remembered by commit id in a bounded per-process cache
  that evicts oldest-first.

On the fixture (30 change requests, 30 issues) the overview drops from 19
git calls to 9 on a repeated load, the code page from 12 to 11, and every
page loses one `rev-parse` for the metadata ref.

server/web/memo.go +69−8

@@ -37,18 +37,30 @@ type memoRequests struct {
37 37 err error
38 38 }
39 39
40 // memoHead is the metadata branch's commit as resolved for this request. The
41 // index and the request list both need it, and it cannot move under a read
42 // request without a push arriving mid-render.
43 type memoHead struct {
44 sha string
45 err error
46 }
47
40 48 type requestMemo struct {
41 mu sync.Mutex
42 repos map[string]*repo.Meta
43 counts map[string]memoCounts
44 lists map[string]memoRequests
49 mu sync.Mutex
50 repos map[string]*repo.Meta
51 counts map[string]memoCounts
52 lists map[string]memoRequests
53 ensured map[string]error
54 heads map[string]memoHead
45 55 }
46 56
47 57 func newRequestMemo() *requestMemo {
48 58 return &requestMemo{
49 repos: map[string]*repo.Meta{},
50 counts: map[string]memoCounts{},
51 lists: map[string]memoRequests{},
59 repos: map[string]*repo.Meta{},
60 counts: map[string]memoCounts{},
61 lists: map[string]memoRequests{},
62 ensured: map[string]error{},
63 heads: map[string]memoHead{},
52 64 }
53 65 }
54 66
@@ -93,6 +105,51 @@ func (w *Web) repo(memo *requestMemo, name string) (*repo.Meta, error) {
93 105 return m, nil
94 106 }
95 107
108 // metadataHead resolves the metadata branch once per request. Both the index
109 // refresh and the change-request read start by resolving it, and a page needs
110 // both.
111 func (w *Web) metadataHead(memo *requestMemo, m *repo.Meta) (string, error) {
112 if memo == nil {
113 return m.Git().RevParse(metadata.Ref)
114 }
115 memo.mu.Lock()
116 h, ok := memo.heads[m.Name]
117 memo.mu.Unlock()
118 if ok {
119 return h.sha, h.err
120 }
121 sha, err := m.Git().RevParse(metadata.Ref)
122 memo.mu.Lock()
123 memo.heads[m.Name] = memoHead{sha: sha, err: err}
124 memo.mu.Unlock()
125 return sha, err
126 }
127
128 // ensureIndex refreshes the rows the repository derives from its metadata branch
129 // at most once per request. Every surface that shows a number or a count asks
130 // for it, and the refresh starts with a git call to see whether the branch
131 // moved: the header, the issue list and the number in a URL should not each pay
132 // for the same check on one page.
133 func (w *Web) ensureIndex(memo *requestMemo, m *repo.Meta) error {
134 if memo == nil {
135 return w.Index.Ensure(m)
136 }
137 memo.mu.Lock()
138 err, ok := memo.ensured[m.Name]
139 memo.mu.Unlock()
140 if ok {
141 return err
142 }
143 head, err := w.metadataHead(memo, m)
144 if err == nil {
145 err = w.Index.EnsureAt(m, head)
146 }
147 memo.mu.Lock()
148 memo.ensured[m.Name] = err
149 memo.mu.Unlock()
150 return err
151 }
152
96 153 // requestsAt returns the change requests on the metadata branch, reading them
97 154 // once per request. The list is the whole branch, so the header's open count and
98 155 // the page's rows share one read rather than paying for the tree twice.
@@ -106,7 +163,11 @@ func (w *Web) requestsAt(memo *requestMemo, m *repo.Meta) ([]*metadata.Request,
106 163 if ok {
107 164 return entry.list, entry.err
108 165 }
109 list, err := metadata.LoadRequests(m.Git(), metadata.Ref)
166 head, err := w.metadataHead(memo, m)
167 var list []*metadata.Request
168 if err == nil {
169 list, err = metadata.LoadRequestsAt(m.Git(), head)
170 }
110 171 memo.mu.Lock()
111 172 memo.lists[m.Name] = memoRequests{list: list, err: err}
112 173 memo.mu.Unlock()