mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 20:11:54 +00:00
syncx: import code from sync.Once* proposal (#44653)
This is the code from https://go.dev/cl/451356 renamed into an internal syncx package. This allows us to use the feature before it lands in the go stdlib. As an example I've updated some call sites under cmd/gitserver to replace usages of sync.Once. Note: not all sync.Once usages can be replaced, but a majority can be in our codebase. The exception to this is a lot of our graphql resolvers use the first context.Context passed in, so wouldn't work with these utility functions. Test Plan: go test
This commit is contained in:
parent
a0881361cb
commit
c5357634e8
@ -2,28 +2,20 @@
|
||||
// to expose the raw system certificates on linux.
|
||||
package cacert
|
||||
|
||||
import "sync"
|
||||
|
||||
var (
|
||||
systemOnce sync.Once
|
||||
systemCerts [][]byte
|
||||
systemErr error
|
||||
import (
|
||||
"github.com/sourcegraph/sourcegraph/internal/syncx"
|
||||
)
|
||||
|
||||
// System returns PEM encoded system certificates. Note: This function only
|
||||
// works on Linux. Other operating systems do not rely on PEM files at known
|
||||
// locations, instead they rely on system calls.
|
||||
func System() ([][]byte, error) {
|
||||
systemOnce.Do(func() {
|
||||
c, err := loadSystemRoots()
|
||||
if err != nil {
|
||||
systemErr = err
|
||||
return
|
||||
}
|
||||
systemCerts = c.certs
|
||||
})
|
||||
return systemCerts, systemErr
|
||||
}
|
||||
var System = syncx.OnceValues(func() ([][]byte, error) {
|
||||
c, err := loadSystemRoots()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.certs, nil
|
||||
})
|
||||
|
||||
// CertPool exists for interaction with x509. Do not use.
|
||||
type CertPool struct {
|
||||
|
||||
@ -54,6 +54,7 @@ import (
|
||||
"github.com/sourcegraph/sourcegraph/internal/observation"
|
||||
"github.com/sourcegraph/sourcegraph/internal/ratelimit"
|
||||
streamhttp "github.com/sourcegraph/sourcegraph/internal/search/streaming/http"
|
||||
"github.com/sourcegraph/sourcegraph/internal/syncx"
|
||||
"github.com/sourcegraph/sourcegraph/internal/trace"
|
||||
"github.com/sourcegraph/sourcegraph/internal/trace/ot"
|
||||
"github.com/sourcegraph/sourcegraph/internal/types"
|
||||
@ -1186,6 +1187,10 @@ func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request) {
|
||||
searchRunning.Inc()
|
||||
defer searchRunning.Dec()
|
||||
|
||||
observeLatency := syncx.OnceFunc(func() {
|
||||
searchLatency.Observe(time.Since(searchStart).Seconds())
|
||||
})
|
||||
|
||||
eventWriter, err := streamhttp.NewWriter(w)
|
||||
if err != nil {
|
||||
tr.SetError(err)
|
||||
@ -1193,12 +1198,9 @@ func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var latencyOnce sync.Once
|
||||
matchesBuf := streamhttp.NewJSONArrayBuf(8*1024, func(data []byte) error {
|
||||
tr.AddEvent("flushing data", attribute.Int("data.len", len(data)))
|
||||
latencyOnce.Do(func() {
|
||||
searchLatency.Observe(time.Since(searchStart).Seconds())
|
||||
})
|
||||
observeLatency()
|
||||
return eventWriter.EventBytes("matches", data)
|
||||
})
|
||||
|
||||
@ -2607,10 +2609,22 @@ func (s *Server) doBackgroundRepoUpdate(repo api.RepoName, revspec string) error
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
badRefsOnce sync.Once
|
||||
badRefs []string
|
||||
)
|
||||
// older versions of git do not remove tags case insensitively, so we generate
|
||||
// every possible case of HEAD (2^4 = 16)
|
||||
var badRefs = syncx.OnceValue(func() []string {
|
||||
refs := make([]string, 0, 1<<4)
|
||||
for bits := uint8(0); bits < (1 << 4); bits++ {
|
||||
s := []byte("HEAD")
|
||||
for i, c := range s {
|
||||
// lowercase if the i'th bit of bits is 1
|
||||
if bits&(1<<i) != 0 {
|
||||
s[i] = c - 'A' + 'a'
|
||||
}
|
||||
}
|
||||
refs = append(refs, string(s))
|
||||
}
|
||||
return refs
|
||||
})
|
||||
|
||||
// removeBadRefs removes bad refs and tags from the git repo at dir. This
|
||||
// should be run after a clone or fetch. If your repository contains a ref or
|
||||
@ -2621,27 +2635,12 @@ var (
|
||||
//
|
||||
// Instead we just remove this ref.
|
||||
func removeBadRefs(ctx context.Context, dir GitDir) {
|
||||
// older versions of git do not remove tags case insensitively, so we
|
||||
// generate every possible case of HEAD (2^4 = 16)
|
||||
badRefsOnce.Do(func() {
|
||||
for bits := uint8(0); bits < (1 << 4); bits++ {
|
||||
s := []byte("HEAD")
|
||||
for i, c := range s {
|
||||
// lowercase if the i'th bit of bits is 1
|
||||
if bits&(1<<i) != 0 {
|
||||
s[i] = c - 'A' + 'a'
|
||||
}
|
||||
}
|
||||
badRefs = append(badRefs, string(s))
|
||||
}
|
||||
})
|
||||
|
||||
args := append([]string{"branch", "-D"}, badRefs...)
|
||||
args := append([]string{"branch", "-D"}, badRefs()...)
|
||||
cmd := exec.CommandContext(ctx, "git", args...)
|
||||
dir.Set(cmd)
|
||||
_ = cmd.Run()
|
||||
|
||||
args = append([]string{"tag", "-d"}, badRefs...)
|
||||
args = append([]string{"tag", "-d"}, badRefs()...)
|
||||
cmd = exec.CommandContext(ctx, "git", args...)
|
||||
dir.Set(cmd)
|
||||
_ = cmd.Run()
|
||||
|
||||
91
internal/syncx/oncefunc.go
Normal file
91
internal/syncx/oncefunc.go
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// package syncx contains an accepted proposal for the sync package in go1.20.
|
||||
// See https://github.com/golang/go/issues/56102 and https://go.dev/cl/451356
|
||||
package syncx
|
||||
|
||||
import "sync"
|
||||
|
||||
// OnceFunc returns a function that invokes f only once. The returned function
|
||||
// may be called concurrently.
|
||||
//
|
||||
// If f panics, the returned function will panic with the same value on every call.
|
||||
func OnceFunc(f func()) func() {
|
||||
var once sync.Once
|
||||
var valid bool
|
||||
var p any
|
||||
return func() {
|
||||
once.Do(func() {
|
||||
defer func() {
|
||||
p = recover()
|
||||
if !valid {
|
||||
// Re-panic immediately so on the first call the user gets a
|
||||
// complete stack trace into f.
|
||||
panic(p)
|
||||
}
|
||||
}()
|
||||
f()
|
||||
valid = true // Set only if f does not panic
|
||||
})
|
||||
if !valid {
|
||||
panic(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OnceValue returns a function that invokes f only once and returns the value
|
||||
// returned by f. The returned function may be called concurrently.
|
||||
//
|
||||
// If f panics, the returned function will panic with the same value on every call.
|
||||
func OnceValue[T any](f func() T) func() T {
|
||||
var once sync.Once
|
||||
var valid bool
|
||||
var p any
|
||||
var result T
|
||||
return func() T {
|
||||
once.Do(func() {
|
||||
defer func() {
|
||||
p = recover()
|
||||
if !valid {
|
||||
panic(p)
|
||||
}
|
||||
}()
|
||||
result = f()
|
||||
valid = true
|
||||
})
|
||||
if !valid {
|
||||
panic(p)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// OnceValues returns a function that invokes f only once and returns the values
|
||||
// returned by f. The returned function may be called concurrently.
|
||||
//
|
||||
// If f panics, the returned function will panic with the same value on every call.
|
||||
func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
|
||||
var once sync.Once
|
||||
var valid bool
|
||||
var p any
|
||||
var r1 T1
|
||||
var r2 T2
|
||||
return func() (T1, T2) {
|
||||
once.Do(func() {
|
||||
defer func() {
|
||||
p = recover()
|
||||
if !valid {
|
||||
panic(p)
|
||||
}
|
||||
}()
|
||||
r1, r2 = f()
|
||||
valid = true
|
||||
})
|
||||
if !valid {
|
||||
panic(p)
|
||||
}
|
||||
return r1, r2
|
||||
}
|
||||
}
|
||||
159
internal/syncx/oncefunc_test.go
Normal file
159
internal/syncx/oncefunc_test.go
Normal file
@ -0,0 +1,159 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package syncx_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/syncx"
|
||||
)
|
||||
|
||||
// We assume that the Once.Do tests have already covered parallelism.
|
||||
|
||||
func TestOnceFunc(t *testing.T) {
|
||||
calls := 0
|
||||
f := syncx.OnceFunc(func() { calls++ })
|
||||
allocs := testing.AllocsPerRun(10, f)
|
||||
if calls != 1 {
|
||||
t.Errorf("want calls==1, got %d", calls)
|
||||
}
|
||||
if allocs != 0 {
|
||||
t.Errorf("want 0 allocations per call, got %v", allocs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnceValue(t *testing.T) {
|
||||
calls := 0
|
||||
f := syncx.OnceValue(func() int {
|
||||
calls++
|
||||
return calls
|
||||
})
|
||||
allocs := testing.AllocsPerRun(10, func() { f() })
|
||||
value := f()
|
||||
if calls != 1 {
|
||||
t.Errorf("want calls==1, got %d", calls)
|
||||
}
|
||||
if value != 1 {
|
||||
t.Errorf("want value==1, got %d", value)
|
||||
}
|
||||
if allocs != 0 {
|
||||
t.Errorf("want 0 allocations per call, got %v", allocs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnceValues(t *testing.T) {
|
||||
calls := 0
|
||||
f := syncx.OnceValues(func() (int, int) {
|
||||
calls++
|
||||
return calls, calls + 1
|
||||
})
|
||||
allocs := testing.AllocsPerRun(10, func() { f() })
|
||||
v1, v2 := f()
|
||||
if calls != 1 {
|
||||
t.Errorf("want calls==1, got %d", calls)
|
||||
}
|
||||
if v1 != 1 || v2 != 2 {
|
||||
t.Errorf("want v1==1 and v2==2, got %d and %d", v1, v2)
|
||||
}
|
||||
if allocs != 0 {
|
||||
t.Errorf("want 0 allocations per call, got %v", allocs)
|
||||
}
|
||||
}
|
||||
|
||||
func testOncePanic(t *testing.T, calls *int, f func()) {
|
||||
// Check that the each call to f panics with the same value, but the
|
||||
// underlying function is only called once.
|
||||
for _, label := range []string{"first time", "second time"} {
|
||||
var p any
|
||||
panicked := true
|
||||
func() {
|
||||
defer func() {
|
||||
p = recover()
|
||||
}()
|
||||
f()
|
||||
panicked = false
|
||||
}()
|
||||
if !panicked {
|
||||
t.Fatalf("%s: f did not panic", label)
|
||||
}
|
||||
if p != "x" {
|
||||
t.Fatalf("%s: want panic %v, got %v", label, "x", p)
|
||||
}
|
||||
}
|
||||
if *calls != 1 {
|
||||
t.Errorf("want calls==1, got %d", *calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnceFuncPanic(t *testing.T) {
|
||||
calls := 0
|
||||
f := syncx.OnceFunc(func() {
|
||||
calls++
|
||||
panic("x")
|
||||
})
|
||||
testOncePanic(t, &calls, f)
|
||||
}
|
||||
|
||||
func TestOnceValuePanic(t *testing.T) {
|
||||
calls := 0
|
||||
f := syncx.OnceValue(func() int {
|
||||
calls++
|
||||
panic("x")
|
||||
})
|
||||
testOncePanic(t, &calls, func() { f() })
|
||||
}
|
||||
|
||||
func TestOnceValuesPanic(t *testing.T) {
|
||||
calls := 0
|
||||
f := syncx.OnceValues(func() (int, int) {
|
||||
calls++
|
||||
panic("x")
|
||||
})
|
||||
testOncePanic(t, &calls, func() { f() })
|
||||
}
|
||||
|
||||
func TestOnceFuncPanicTraceback(t *testing.T) {
|
||||
// Test that on the first invocation of a OnceFunc, the stack trace goes all
|
||||
// the way to the origin of the panic.
|
||||
f := syncx.OnceFunc(onceFuncPanic)
|
||||
|
||||
defer func() {
|
||||
if p := recover(); p != "x" {
|
||||
t.Fatalf("want panic %v, got %v", "x", p)
|
||||
}
|
||||
stack := debug.Stack()
|
||||
want := "syncx_test.onceFuncPanic"
|
||||
if !bytes.Contains(stack, []byte(want)) {
|
||||
t.Fatalf("want stack containing %v, got:\n%s", want, string(stack))
|
||||
}
|
||||
}()
|
||||
f()
|
||||
}
|
||||
|
||||
func onceFuncPanic() {
|
||||
panic("x")
|
||||
}
|
||||
|
||||
func BenchmarkOnceFunc(b *testing.B) {
|
||||
b.Run("OnceFunc", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
f := syncx.OnceFunc(func() {})
|
||||
for i := 0; i < b.N; i++ {
|
||||
f()
|
||||
}
|
||||
})
|
||||
// Versus open-coding with Once.Do
|
||||
b.Run("Once", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
var once sync.Once
|
||||
f := func() {}
|
||||
for i := 0; i < b.N; i++ {
|
||||
once.Do(f)
|
||||
}
|
||||
})
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user