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 · 18m ago 4b8c9e1d
history blame raw 2.3 KiB
1package web
2
3// Bounds for the pages that shell out to git.
4//
5// Search, compare, blame, history and archives each start a git process against a
6// repository somebody else controls. A repository can be large, a diff can be
7// quadratic, and a blame walks history: none of these is bounded by anything the
8// instance configured, and all of them are reachable by an anonymous reader on a
9// public repository. So each one takes a slot from a fixed-size pool and runs under
10// a deadline; past the wait the reader is told to come back rather than queued
11// behind an unbounded number of other readers.
12
13import (
14 "context"
15 "net/http"
16 "time"
17
18 "rodent.software/vole/core/git"
19 "rodent.software/vole/forge/repo"
20)
21
22const (
23 // webGitConcurrent bounds how many of these pages run git at once.
24 webGitConcurrent = 8
25 // webGitTimeout bounds one page's git work. A read of a repository this size
26 // takes milliseconds; a minute means something is wrong, and the process is
27 // killed rather than left to finish after the reader has gone.
28 webGitTimeout = 30 * time.Second
29 // webGitWait is how long a page waits for a slot before answering 503.
30 webGitWait = 5 * time.Second
31)
32
33// gitSlot takes one of the bounded slots, waiting a short while so a burst of page
34// loads queues instead of failing. It answers the request itself when there is no
35// slot, and reports whether the handler should continue.
36func (w *Web) gitSlot(rw http.ResponseWriter, r *http.Request) (func(), bool) {
37 if w.gitSem == nil {
38 return func() {}, true
39 }
40 select {
41 case w.gitSem <- struct{}{}:
42 return func() { <-w.gitSem }, true
43 default:
44 }
45 wait := w.gitWait
46 if wait <= 0 {
47 wait = webGitWait
48 }
49 t := time.NewTimer(wait)
50 defer t.Stop()
51 select {
52 case w.gitSem <- struct{}{}:
53 return func() { <-w.gitSem }, true
54 case <-t.C:
55 rw.Header().Set("Retry-After", "5")
56 http.Error(rw, "the server is busy running other repository queries; try again shortly", http.StatusServiceUnavailable)
57 return nil, false
58 }
59}
60
61// gitRepo returns the repository handle with this page's deadline attached, so a
62// git process that outlives it is killed instead of holding a slot. The caller must
63// call cancel when the handler is done.
64func gitRepo(r *http.Request, m *repo.Meta) (*git.Repo, context.CancelFunc) {
65 ctx, cancel := context.WithTimeout(r.Context(), webGitTimeout)
66 return m.Git().WithContext(ctx), cancel
67}
68