Merge branch 'main' into application_management
This commit is contained in:
commit
a4bc16b60d
6
go.mod
6
go.mod
|
@ -27,7 +27,7 @@ require (
|
|||
codeberg.org/gruf/go-runners v1.6.3
|
||||
codeberg.org/gruf/go-sched v1.2.4
|
||||
codeberg.org/gruf/go-storage v0.2.0
|
||||
codeberg.org/gruf/go-structr v0.8.11
|
||||
codeberg.org/gruf/go-structr v0.9.0
|
||||
codeberg.org/superseriousbusiness/activity v1.12.0-gts
|
||||
codeberg.org/superseriousbusiness/exif-terminator v0.10.0
|
||||
codeberg.org/superseriousbusiness/httpsig v1.3.0-SSB
|
||||
|
@ -83,7 +83,7 @@ require (
|
|||
go.uber.org/automaxprocs v1.6.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
golang.org/x/image v0.24.0
|
||||
golang.org/x/net v0.35.0
|
||||
golang.org/x/net v0.36.0
|
||||
golang.org/x/oauth2 v0.27.0
|
||||
golang.org/x/sys v0.31.0
|
||||
golang.org/x/text v0.23.0
|
||||
|
@ -95,7 +95,7 @@ require (
|
|||
|
||||
require (
|
||||
codeberg.org/gruf/go-fastpath/v2 v2.0.0 // indirect
|
||||
codeberg.org/gruf/go-mangler v1.4.1 // indirect
|
||||
codeberg.org/gruf/go-mangler v1.4.3 // indirect
|
||||
codeberg.org/gruf/go-maps v1.0.4 // indirect
|
||||
codeberg.org/superseriousbusiness/go-jpeg-image-structure/v2 v2.1.0-SSB // indirect
|
||||
codeberg.org/superseriousbusiness/go-png-image-structure/v2 v2.1.0-SSB // indirect
|
||||
|
|
|
@ -24,8 +24,8 @@ codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f h1:Ss6Z+vygy+jOGhj9
|
|||
codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f/go.mod h1:F9pl4h34iuVN7kucKam9fLwsItTc+9mmaKt7pNXRd/4=
|
||||
codeberg.org/gruf/go-loosy v0.0.0-20231007123304-bb910d1ab5c4 h1:IXwfoU7f2whT6+JKIKskNl/hBlmWmnF1vZd84Eb3cyA=
|
||||
codeberg.org/gruf/go-loosy v0.0.0-20231007123304-bb910d1ab5c4/go.mod h1:fiO8HE1wjZCephcYmRRsVnNI/i0+mhy44Z5dQalS0rM=
|
||||
codeberg.org/gruf/go-mangler v1.4.1 h1:Dv58jFfy9On49L11ji6tpADUknwoJA46iaiZvnNXecs=
|
||||
codeberg.org/gruf/go-mangler v1.4.1/go.mod h1:mDmW8Ia352RvNFaXoP9K60TgcmCZJtX0j6wm3vjAsJE=
|
||||
codeberg.org/gruf/go-mangler v1.4.3 h1:mdtcbGDyj0AS9LE/H1imQreICVn6BQiks554jzdAozc=
|
||||
codeberg.org/gruf/go-mangler v1.4.3/go.mod h1:mDmW8Ia352RvNFaXoP9K60TgcmCZJtX0j6wm3vjAsJE=
|
||||
codeberg.org/gruf/go-maps v1.0.4 h1:K+Ww4vvR3TZqm5jqrKVirmguZwa3v1VUvmig2SE8uxY=
|
||||
codeberg.org/gruf/go-maps v1.0.4/go.mod h1:ASX7osM7kFwt5O8GfGflcFjrwYGD8eIuRLl/oMjhEi8=
|
||||
codeberg.org/gruf/go-mempool v0.0.0-20240507125005-cef10d64a760 h1:m2/UCRXhjDwAg4vyji6iKCpomKw6P4PmBOUi5DvAMH4=
|
||||
|
@ -38,8 +38,8 @@ codeberg.org/gruf/go-sched v1.2.4 h1:ddBB9o0D/2oU8NbQ0ldN5aWxogpXPRBATWi58+p++Hw
|
|||
codeberg.org/gruf/go-sched v1.2.4/go.mod h1:wad6l+OcYGWMA2TzNLMmLObsrbBDxdJfEy5WvTgBjNk=
|
||||
codeberg.org/gruf/go-storage v0.2.0 h1:mKj3Lx6AavEkuXXtxqPhdq+akW9YwrnP16yQBF7K5ZI=
|
||||
codeberg.org/gruf/go-storage v0.2.0/go.mod h1:o3GzMDE5QNUaRnm/daUzFqvuAaC4utlgXDXYO79sWKU=
|
||||
codeberg.org/gruf/go-structr v0.8.11 h1:I3cQCHpK3fQSXWaaUfksAJRN4+efULiuF11Oi/m8c+o=
|
||||
codeberg.org/gruf/go-structr v0.8.11/go.mod h1:zkoXVrAnKosh8VFAsbP/Hhs8FmLBjbVVy5w/Ngm8ApM=
|
||||
codeberg.org/gruf/go-structr v0.9.0 h1:UYw8igp3I4UBnlsRyDR2AbF3g7NPEP7HBrQs1I15218=
|
||||
codeberg.org/gruf/go-structr v0.9.0/go.mod h1:mUvBvn4q1iM/I+d3Fj1w/gxGUU/Ve9GpiNo6dPmBJnk=
|
||||
codeberg.org/superseriousbusiness/activity v1.12.0-gts h1:frNGTENLmOIQHKfOw/jj3UVj/GjHBljDx+CFAAK+m6Q=
|
||||
codeberg.org/superseriousbusiness/activity v1.12.0-gts/go.mod h1:enxU1Lva4OcK6b/NBXscoHSEgEMsKJvdHrQFifQxp4o=
|
||||
codeberg.org/superseriousbusiness/exif-terminator v0.10.0 h1:FiLX/AK07tzceS36I+kOP2aEH+aytjPSIlFoYePMEyg=
|
||||
|
@ -558,8 +558,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
|||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
|
||||
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
|
||||
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//go:build go1.19 || go1.20 || go1.21 || go1.22 || go1.23
|
||||
|
||||
package mangler
|
||||
|
||||
import (
|
||||
|
@ -35,8 +37,17 @@ func append_uint64(b []byte, u uint64) []byte {
|
|||
}
|
||||
|
||||
type typecontext struct {
|
||||
ntype reflect.Type
|
||||
rtype reflect.Type
|
||||
isptr bool
|
||||
direct bool
|
||||
ntype reflect.Type
|
||||
rtype reflect.Type
|
||||
}
|
||||
|
||||
func (ctx *typecontext) set_nested(direct bool) {
|
||||
ctx.direct = ctx.direct && direct && !ctx.isptr
|
||||
ctx.ntype = ctx.rtype
|
||||
ctx.rtype = nil
|
||||
ctx.isptr = false
|
||||
}
|
||||
|
||||
func deref_ptr_mangler(ctx typecontext, mangle Mangler, n uint) Mangler {
|
||||
|
@ -44,16 +55,14 @@ func deref_ptr_mangler(ctx typecontext, mangle Mangler, n uint) Mangler {
|
|||
panic("bad input")
|
||||
}
|
||||
|
||||
// Non-nested value types,
|
||||
// i.e. just direct ptrs to
|
||||
// primitives require one
|
||||
// less dereference to ptr.
|
||||
if ctx.ntype == nil {
|
||||
// If this is a direct value type, i.e. non-nested primitive,
|
||||
// or part of a single-field struct / single element array
|
||||
// then it can be treated as a direct ptr with 1 less deref.
|
||||
if ctx.direct {
|
||||
n--
|
||||
}
|
||||
|
||||
return func(buf []byte, ptr unsafe.Pointer) []byte {
|
||||
|
||||
// Deref n number times.
|
||||
for i := n; i > 0; i-- {
|
||||
|
||||
|
@ -117,6 +126,15 @@ func iter_array_mangler(ctx typecontext, mangle Mangler) Mangler {
|
|||
// no. array elements.
|
||||
n := ctx.ntype.Len()
|
||||
|
||||
// Optimize
|
||||
// easy cases.
|
||||
switch n {
|
||||
case 0:
|
||||
return empty_mangler
|
||||
case 1:
|
||||
return mangle
|
||||
}
|
||||
|
||||
// memory size of elem.
|
||||
esz := ctx.rtype.Size()
|
||||
|
||||
|
@ -139,19 +157,27 @@ func iter_array_mangler(ctx typecontext, mangle Mangler) Mangler {
|
|||
}
|
||||
|
||||
func iter_struct_mangler(ctx typecontext, manglers []Mangler) Mangler {
|
||||
if ctx.rtype == nil || len(manglers) != ctx.rtype.NumField() {
|
||||
if ctx.rtype == nil || len(manglers) != ctx.ntype.NumField() {
|
||||
panic("bad input")
|
||||
}
|
||||
|
||||
// Optimized easy cases.
|
||||
switch len(manglers) {
|
||||
case 0:
|
||||
return empty_mangler
|
||||
case 1:
|
||||
return manglers[0]
|
||||
}
|
||||
|
||||
type field struct {
|
||||
mangle Mangler
|
||||
offset uintptr
|
||||
}
|
||||
|
||||
// Bundle together the fields and manglers.
|
||||
fields := make([]field, ctx.rtype.NumField())
|
||||
fields := make([]field, ctx.ntype.NumField())
|
||||
for i := range fields {
|
||||
rfield := ctx.rtype.FieldByIndex([]int{i})
|
||||
rfield := ctx.ntype.Field(i)
|
||||
fields[i].offset = rfield.Offset
|
||||
fields[i].mangle = manglers[i]
|
||||
if fields[i].mangle == nil {
|
||||
|
@ -178,6 +204,10 @@ func iter_struct_mangler(ctx typecontext, manglers []Mangler) Mangler {
|
|||
}
|
||||
}
|
||||
|
||||
func empty_mangler(buf []byte, _ unsafe.Pointer) []byte {
|
||||
return buf
|
||||
}
|
||||
|
||||
// array_at returns ptr to index in array at ptr, given element size.
|
||||
func array_at(ptr unsafe.Pointer, esz uintptr, i int) unsafe.Pointer {
|
||||
return unsafe.Pointer(uintptr(ptr) + esz*uintptr(i))
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
// function will be returned for given value interface{} and reflected type. Else panics.
|
||||
func loadMangler(t reflect.Type) Mangler {
|
||||
ctx := typecontext{rtype: t}
|
||||
ctx.direct = true
|
||||
|
||||
// Load mangler fn
|
||||
mng := load(ctx)
|
||||
|
@ -103,6 +104,9 @@ func loadReflectPtr(ctx typecontext) Mangler {
|
|||
n++
|
||||
}
|
||||
|
||||
// Set ptr type.
|
||||
ctx.isptr = true
|
||||
|
||||
// Search for elemn type mangler.
|
||||
if mng := load(ctx); mng != nil {
|
||||
return deref_ptr_mangler(ctx, mng, n)
|
||||
|
@ -157,11 +161,13 @@ func loadReflectKnownSlice(ctx typecontext) Mangler {
|
|||
|
||||
// loadReflectSlice ...
|
||||
func loadReflectSlice(ctx typecontext) Mangler {
|
||||
// Set nesting type.
|
||||
ctx.ntype = ctx.rtype
|
||||
|
||||
// Get nested element type.
|
||||
ctx.rtype = ctx.rtype.Elem()
|
||||
elem := ctx.rtype.Elem()
|
||||
|
||||
// Set this as nested type.
|
||||
ctx.set_nested(false)
|
||||
ctx.rtype = elem
|
||||
|
||||
// Preferably look for known slice mangler func
|
||||
if mng := loadReflectKnownSlice(ctx); mng != nil {
|
||||
|
@ -178,11 +184,14 @@ func loadReflectSlice(ctx typecontext) Mangler {
|
|||
|
||||
// loadReflectArray ...
|
||||
func loadReflectArray(ctx typecontext) Mangler {
|
||||
// Set nesting type.
|
||||
ctx.ntype = ctx.rtype
|
||||
|
||||
// Get nested element type.
|
||||
ctx.rtype = ctx.rtype.Elem()
|
||||
elem := ctx.rtype.Elem()
|
||||
|
||||
// Set this as a nested value type.
|
||||
direct := ctx.rtype.Len() <= 1
|
||||
ctx.set_nested(direct)
|
||||
ctx.rtype = elem
|
||||
|
||||
// Use manglers for nested iteration.
|
||||
if mng := load(ctx); mng != nil {
|
||||
|
@ -196,17 +205,15 @@ func loadReflectArray(ctx typecontext) Mangler {
|
|||
func loadReflectStruct(ctx typecontext) Mangler {
|
||||
var mngs []Mangler
|
||||
|
||||
// Set nesting type.
|
||||
ctx.ntype = ctx.rtype
|
||||
// Set this as a nested value type.
|
||||
direct := ctx.rtype.NumField() <= 1
|
||||
ctx.set_nested(direct)
|
||||
|
||||
// Gather manglers for all fields.
|
||||
for i := 0; i < ctx.ntype.NumField(); i++ {
|
||||
|
||||
// Field typectx.
|
||||
ctx := typecontext{
|
||||
ntype: ctx.ntype,
|
||||
rtype: ctx.ntype.Field(i).Type,
|
||||
}
|
||||
// Update context with field at index.
|
||||
ctx.rtype = ctx.ntype.Field(i).Type
|
||||
|
||||
// Load mangler.
|
||||
mng := load(ctx)
|
||||
|
|
|
@ -2,141 +2,9 @@
|
|||
|
||||
A library with a series of performant data types with automated struct value indexing. Indexing is supported via arbitrary combinations of fields, and in the case of the cache type, negative results (errors!) are also supported.
|
||||
|
||||
Under the hood, go-structr maintains a hashmap per index, where each hashmap is a hashmap keyed by serialized input key type. This is handled by the incredibly performant serialization library [go-mangler](https://codeberg.org/gruf/go-mangler), which at this point in time supports just about **any** arbitrary type, so feel free to index by *anything*!
|
||||
Under the hood, go-structr maintains a hashmap per index, where each hashmap is a hashmap keyed by serialized input key type. This is handled by the incredibly performant serialization library [go-mangler](https://codeberg.org/gruf/go-mangler), which at this point in time supports *most* arbitrary types (other than maps, channels, functions), so feel free to index by by almost *anything*!
|
||||
|
||||
## Cache example
|
||||
|
||||
```golang
|
||||
type Cached struct {
|
||||
Username string
|
||||
Domain string
|
||||
URL string
|
||||
CountryCode int
|
||||
}
|
||||
|
||||
var c structr.Cache[*Cached]
|
||||
|
||||
c.Init(structr.CacheConfig[*Cached]{
|
||||
|
||||
// Fields this cached struct type
|
||||
// will be indexed and stored under.
|
||||
Indices: []structr.IndexConfig{
|
||||
{Fields: "Username,Domain", AllowZero: true},
|
||||
{Fields: "URL"},
|
||||
{Fields: "CountryCode", Multiple: true},
|
||||
},
|
||||
|
||||
// Maximum LRU cache size before
|
||||
// new entries cause evictions.
|
||||
MaxSize: 1000,
|
||||
|
||||
// User provided value copy function to
|
||||
// reduce need for reflection + ensure
|
||||
// concurrency safety for returned values.
|
||||
Copy: func(c *Cached) *Cached {
|
||||
c2 := new(Cached)
|
||||
*c2 = *c
|
||||
return c2
|
||||
},
|
||||
|
||||
// User defined invalidation hook.
|
||||
Invalidate: func(c *Cached) {
|
||||
log.Println("invalidated:", c)
|
||||
},
|
||||
})
|
||||
|
||||
// Access and store indexes ahead-of-time for perf.
|
||||
usernameDomainIndex := c.Index("Username,Domain")
|
||||
urlIndex := c.Index("URL")
|
||||
countryCodeIndex := c.Index("CountryCode")
|
||||
|
||||
var url string
|
||||
|
||||
// Generate URL index key.
|
||||
urlKey := urlIndex.Key(url)
|
||||
|
||||
// Load value from cache, with callback function to hydrate
|
||||
// cache if value cannot be found under index name with key.
|
||||
// Negative (error) results are also cached, with user definable
|
||||
// errors to ignore from caching (e.g. context deadline errs).
|
||||
value, err := c.LoadOne(urlIndex, func() (*Cached, error) {
|
||||
return dbType.SelectByURL(url)
|
||||
}, urlKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Store value in cache, only if provided callback
|
||||
// function returns without error. Passes value through
|
||||
// invalidation hook regardless of error return value.
|
||||
//
|
||||
// On success value will be automatically added to and
|
||||
// accessible under all initially configured indices.
|
||||
if err := c.Store(value, func() error {
|
||||
return dbType.Insert(value)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Generate country code index key.
|
||||
countryCodeKey := countryCodeIndex.Key(42)
|
||||
|
||||
// Invalidate all cached results stored under
|
||||
// provided index name with give field value(s).
|
||||
c.Invalidate(countryCodeIndex, countryCodeKey)
|
||||
```
|
||||
|
||||
## Queue example
|
||||
|
||||
```golang
|
||||
|
||||
type Queued struct{
|
||||
Username string
|
||||
Domain string
|
||||
URL string
|
||||
CountryCode int
|
||||
}
|
||||
|
||||
var q structr.Queue[*Queued]
|
||||
|
||||
q.Init(structr.QueueConfig[*Cached]{
|
||||
|
||||
// Fields this queued struct type
|
||||
// will be indexed and stored under.
|
||||
Indices: []structr.IndexConfig{
|
||||
{Fields: "Username,Domain", AllowZero: true},
|
||||
{Fields: "URL"},
|
||||
{Fields: "CountryCode", Multiple: true},
|
||||
},
|
||||
|
||||
// User defined pop hook.
|
||||
Pop: func(c *Cached) {
|
||||
log.Println("popped:", c)
|
||||
},
|
||||
})
|
||||
|
||||
// Access and store indexes ahead-of-time for perf.
|
||||
usernameDomainIndex := q.Index("Username,Domain")
|
||||
urlIndex := q.Index("URL")
|
||||
countryCodeIndex := q.Index("CountryCode")
|
||||
|
||||
// ...
|
||||
q.PushBack(Queued{
|
||||
Username: "billybob",
|
||||
Domain: "google.com",
|
||||
URL: "https://some.website.here",
|
||||
CountryCode: 42,
|
||||
})
|
||||
|
||||
// ...
|
||||
queued, ok := q.PopFront()
|
||||
|
||||
// Generate country code index key.
|
||||
countryCodeKey := countryCodeIndex.Key(42)
|
||||
|
||||
// ...
|
||||
queuedByCountry := q.Pop(countryCodeIndex, countryCodeKey)
|
||||
```
|
||||
See the [docs](https://pkg.go.dev/codeberg.org/gruf/go-structr) for more API information.
|
||||
|
||||
## Notes
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
## Timeline Todos
|
||||
|
||||
- optimize store() to operate on sorted list
|
||||
|
||||
- finish writing code comments
|
|
@ -17,7 +17,7 @@ func DefaultIgnoreErr(err error) bool {
|
|||
}
|
||||
|
||||
// CacheConfig defines config vars
|
||||
// for initializing a struct cache.
|
||||
// for initializing a Cache{} type.
|
||||
type CacheConfig[StructType any] struct {
|
||||
|
||||
// IgnoreErr defines which errors to
|
||||
|
@ -70,14 +70,13 @@ type Cache[StructType any] struct {
|
|||
indices []Index
|
||||
|
||||
// max cache size, imposes size
|
||||
// limit on the lruList in order
|
||||
// limit on the lru list in order
|
||||
// to evict old entries.
|
||||
maxSize int
|
||||
|
||||
// protective mutex, guards:
|
||||
// - Cache{}.lruList
|
||||
// - Cache{}.*
|
||||
// - Index{}.data
|
||||
// - Cache{} hook fns
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
|
@ -105,6 +104,7 @@ func (c *Cache[T]) Init(config CacheConfig[T]) {
|
|||
// Safely copy over
|
||||
// provided config.
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
c.indices = make([]Index, len(config.Indices))
|
||||
for i, cfg := range config.Indices {
|
||||
c.indices[i].ptr = unsafe.Pointer(c)
|
||||
|
@ -114,7 +114,6 @@ func (c *Cache[T]) Init(config CacheConfig[T]) {
|
|||
c.copy = config.Copy
|
||||
c.invalid = config.Invalidate
|
||||
c.maxSize = config.MaxSize
|
||||
c.mutex.Unlock()
|
||||
}
|
||||
|
||||
// Index selects index with given name from cache, else panics.
|
||||
|
@ -161,6 +160,7 @@ func (c *Cache[T]) Get(index *Index, keys ...Key) []T {
|
|||
// Concatenate all *values* from cached items.
|
||||
index.get(keys[i].key, func(item *indexed_item) {
|
||||
if value, ok := item.data.(T); ok {
|
||||
|
||||
// Append value COPY.
|
||||
value = c.copy(value)
|
||||
values = append(values, value)
|
||||
|
@ -431,6 +431,7 @@ func (c *Cache[T]) Store(value T, store func() error) error {
|
|||
}
|
||||
|
||||
// Invalidate invalidates all results stored under index keys.
|
||||
// Note that if set, this will call the invalidate hook on each.
|
||||
func (c *Cache[T]) Invalidate(index *Index, keys ...Key) {
|
||||
if index == nil {
|
||||
panic("no index given")
|
||||
|
@ -455,7 +456,7 @@ func (c *Cache[T]) Invalidate(index *Index, keys ...Key) {
|
|||
values = append(values, value)
|
||||
}
|
||||
|
||||
// Delete cached.
|
||||
// Delete item.
|
||||
c.delete(item)
|
||||
})
|
||||
}
|
||||
|
@ -478,6 +479,7 @@ func (c *Cache[T]) Invalidate(index *Index, keys ...Key) {
|
|||
// Trim will truncate the cache to ensure it
|
||||
// stays within given percentage of MaxSize.
|
||||
func (c *Cache[T]) Trim(perc float64) {
|
||||
|
||||
// Acquire lock.
|
||||
c.mutex.Lock()
|
||||
|
||||
|
@ -572,7 +574,14 @@ func (c *Cache[T]) store_value(index *Index, key string, value T) {
|
|||
if index != nil {
|
||||
// Append item to index a key
|
||||
// was already generated for.
|
||||
index.append(&c.lru, key, item)
|
||||
evicted := index.append(key, item)
|
||||
if evicted != nil {
|
||||
|
||||
// This item is no longer
|
||||
// indexed, remove from list.
|
||||
c.lru.remove(&evicted.elem)
|
||||
free_indexed_item(evicted)
|
||||
}
|
||||
}
|
||||
|
||||
// Get ptr to value data.
|
||||
|
@ -593,9 +602,6 @@ func (c *Cache[T]) store_value(index *Index, key string, value T) {
|
|||
|
||||
// Extract fields comprising index key.
|
||||
parts := extract_fields(ptr, idx.fields)
|
||||
if parts == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Calculate index key.
|
||||
key := idx.key(buf, parts)
|
||||
|
@ -604,15 +610,29 @@ func (c *Cache[T]) store_value(index *Index, key string, value T) {
|
|||
}
|
||||
|
||||
// Append item to this index.
|
||||
idx.append(&c.lru, key, item)
|
||||
evicted := idx.append(key, item)
|
||||
if evicted != nil {
|
||||
|
||||
// This item is no longer
|
||||
// indexed, remove from list.
|
||||
c.lru.remove(&evicted.elem)
|
||||
free_indexed_item(evicted)
|
||||
}
|
||||
}
|
||||
|
||||
// Done with buf.
|
||||
free_buffer(buf)
|
||||
|
||||
if len(item.indexed) == 0 {
|
||||
// Item was not stored under
|
||||
// any index. Drop this item.
|
||||
free_indexed_item(item)
|
||||
return
|
||||
}
|
||||
|
||||
// Add item to main lru list.
|
||||
c.lru.push_front(&item.elem)
|
||||
|
||||
// Done with buf.
|
||||
free_buffer(buf)
|
||||
|
||||
if c.lru.len > c.maxSize {
|
||||
// Cache has hit max size!
|
||||
// Drop the oldest element.
|
||||
|
@ -643,7 +663,14 @@ func (c *Cache[T]) store_error(index *Index, key string, err error) {
|
|||
|
||||
// Append item to index a key
|
||||
// was already generated for.
|
||||
index.append(&c.lru, key, item)
|
||||
evicted := index.append(key, item)
|
||||
if evicted != nil {
|
||||
|
||||
// This item is no longer
|
||||
// indexed, remove from list.
|
||||
c.lru.remove(&evicted.elem)
|
||||
free_indexed_item(evicted)
|
||||
}
|
||||
|
||||
// Add item to main lru list.
|
||||
c.lru.push_front(&item.elem)
|
||||
|
@ -657,19 +684,23 @@ func (c *Cache[T]) store_error(index *Index, key string, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *Cache[T]) delete(item *indexed_item) {
|
||||
for len(item.indexed) != 0 {
|
||||
func (c *Cache[T]) delete(i *indexed_item) {
|
||||
for len(i.indexed) != 0 {
|
||||
// Pop last indexed entry from list.
|
||||
entry := item.indexed[len(item.indexed)-1]
|
||||
item.indexed = item.indexed[:len(item.indexed)-1]
|
||||
entry := i.indexed[len(i.indexed)-1]
|
||||
i.indexed[len(i.indexed)-1] = nil
|
||||
i.indexed = i.indexed[:len(i.indexed)-1]
|
||||
|
||||
// Drop index_entry from index.
|
||||
entry.index.delete_entry(entry)
|
||||
// Get entry's index.
|
||||
index := entry.index
|
||||
|
||||
// Drop this index_entry.
|
||||
index.delete_entry(entry)
|
||||
}
|
||||
|
||||
// Drop entry from lru list.
|
||||
c.lru.remove(&item.elem)
|
||||
// Drop from lru list.
|
||||
c.lru.remove(&i.elem)
|
||||
|
||||
// Free now-unused item.
|
||||
free_indexed_item(item)
|
||||
// Free unused item.
|
||||
free_indexed_item(i)
|
||||
}
|
||||
|
|
|
@ -168,7 +168,7 @@ func (i *Index) init(t reflect.Type, cfg IndexConfig, cap int) {
|
|||
|
||||
// Initialize store for
|
||||
// index_entry lists.
|
||||
i.data.init(cap)
|
||||
i.data.Init(cap)
|
||||
}
|
||||
|
||||
// get_one will fetch one indexed item under key.
|
||||
|
@ -248,7 +248,7 @@ func (i *Index) key(buf *byteutil.Buffer, parts []unsafe.Pointer) string {
|
|||
// doubly-linked-list in index hashmap. this handles case of
|
||||
// overwriting "unique" index entries, and removes from given
|
||||
// outer linked-list in the case that it is no longer indexed.
|
||||
func (i *Index) append(ll *list, key string, item *indexed_item) {
|
||||
func (i *Index) append(key string, item *indexed_item) (evicted *indexed_item) {
|
||||
// Look for existing.
|
||||
l := i.data.Get(key)
|
||||
|
||||
|
@ -267,17 +267,16 @@ func (i *Index) append(ll *list, key string, item *indexed_item) {
|
|||
// Drop index from inner item,
|
||||
// catching the evicted item.
|
||||
e := (*index_entry)(elem.data)
|
||||
evicted := e.item
|
||||
evicted = e.item
|
||||
evicted.drop_index(e)
|
||||
|
||||
// Free unused entry.
|
||||
free_index_entry(e)
|
||||
|
||||
if len(evicted.indexed) == 0 {
|
||||
// Evicted item is not indexed,
|
||||
// remove from outer linked list.
|
||||
ll.remove(&evicted.elem)
|
||||
free_indexed_item(evicted)
|
||||
if len(evicted.indexed) != 0 {
|
||||
// Evicted is still stored
|
||||
// under index, don't return.
|
||||
evicted = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,6 +291,7 @@ func (i *Index) append(ll *list, key string, item *indexed_item) {
|
|||
|
||||
// Add entry to index list.
|
||||
l.push_front(&entry.elem)
|
||||
return
|
||||
}
|
||||
|
||||
// delete will remove all indexed items under key, passing each to hook.
|
||||
|
@ -403,7 +403,7 @@ func new_index_entry() *index_entry {
|
|||
func free_index_entry(entry *index_entry) {
|
||||
if entry.elem.next != nil ||
|
||||
entry.elem.prev != nil {
|
||||
should_not_reach()
|
||||
should_not_reach(false)
|
||||
return
|
||||
}
|
||||
entry.key = ""
|
||||
|
|
|
@ -37,7 +37,7 @@ func free_indexed_item(item *indexed_item) {
|
|||
if len(item.indexed) > 0 ||
|
||||
item.elem.next != nil ||
|
||||
item.elem.prev != nil {
|
||||
should_not_reach()
|
||||
should_not_reach(false)
|
||||
return
|
||||
}
|
||||
item.data = nil
|
||||
|
|
|
@ -33,7 +33,7 @@ func (k Key) Values() []any {
|
|||
|
||||
// Zero indicates a zero value key.
|
||||
func (k Key) Zero() bool {
|
||||
return (k.raw == nil)
|
||||
return (k.key == "")
|
||||
}
|
||||
|
||||
var buf_pool sync.Pool
|
||||
|
|
|
@ -43,7 +43,7 @@ func free_list(list *list) {
|
|||
if list.head != nil ||
|
||||
list.tail != nil ||
|
||||
list.len != 0 {
|
||||
should_not_reach()
|
||||
should_not_reach(false)
|
||||
return
|
||||
}
|
||||
list_pool.Put(list)
|
||||
|
@ -107,6 +107,32 @@ func (l *list) move_back(elem *list_elem) {
|
|||
l.push_back(elem)
|
||||
}
|
||||
|
||||
// insert will insert given element at given location in list.
|
||||
func (l *list) insert(elem *list_elem, at *list_elem) {
|
||||
if elem == at {
|
||||
return
|
||||
}
|
||||
|
||||
// Set new 'next'.
|
||||
oldNext := at.next
|
||||
at.next = elem
|
||||
|
||||
// Link to 'at'.
|
||||
elem.prev = at
|
||||
|
||||
if oldNext == nil {
|
||||
// Set new tail
|
||||
l.tail = elem
|
||||
} else {
|
||||
// Link to 'prev'.
|
||||
oldNext.prev = elem
|
||||
elem.next = oldNext
|
||||
}
|
||||
|
||||
// Incr count
|
||||
l.len++
|
||||
}
|
||||
|
||||
// remove will remove given elem from list.
|
||||
func (l *list) remove(elem *list_elem) {
|
||||
// Get linked elems.
|
||||
|
@ -149,3 +175,11 @@ func (l *list) remove(elem *list_elem) {
|
|||
// Decr count
|
||||
l.len--
|
||||
}
|
||||
|
||||
// func (l *list) range_up(yield func(*list_elem) bool) {
|
||||
|
||||
// }
|
||||
|
||||
// func (l *list) range_down(yield func(*list_elem) bool) {
|
||||
|
||||
// }
|
||||
|
|
|
@ -5,7 +5,7 @@ type hashmap struct {
|
|||
n int
|
||||
}
|
||||
|
||||
func (m *hashmap) init(cap int) {
|
||||
func (m *hashmap) Init(cap int) {
|
||||
m.m = make(map[string]*list, cap)
|
||||
m.n = cap
|
||||
}
|
||||
|
@ -43,6 +43,10 @@ func (m *hashmap) Compact() {
|
|||
// So we apply the inverse/2, once
|
||||
// $maxLoad/2 % of hmap is empty we
|
||||
// compact the map to drop buckets.
|
||||
//
|
||||
// TODO: this is still a relatively
|
||||
// good approximation, but it has
|
||||
// changed a little with swiss maps.
|
||||
if 2*16*diff > m.n*13 {
|
||||
|
||||
// Create new map only big as required.
|
||||
|
|
|
@ -1,180 +0,0 @@
|
|||
package structr
|
||||
|
||||
import "sync"
|
||||
|
||||
type Timeline[StructType any, PK comparable] struct {
|
||||
|
||||
// hook functions.
|
||||
pkey func(StructType) PK
|
||||
gte func(PK, PK) bool
|
||||
lte func(PK, PK) bool
|
||||
copy func(StructType) StructType
|
||||
|
||||
// main underlying
|
||||
// ordered item list.
|
||||
list list
|
||||
|
||||
// indices used in storing passed struct
|
||||
// types by user defined sets of fields.
|
||||
indices []Index
|
||||
|
||||
// protective mutex, guards:
|
||||
// - TODO
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (t *Timeline[T, PK]) Init(config any) {
|
||||
|
||||
}
|
||||
|
||||
func (t *Timeline[T, PK]) Index(name string) *Index {
|
||||
for i := range t.indices {
|
||||
if t.indices[i].name == name {
|
||||
return &t.indices[i]
|
||||
}
|
||||
}
|
||||
panic("unknown index: " + name)
|
||||
}
|
||||
|
||||
func (t *Timeline[T, PK]) Insert(values ...T) {
|
||||
|
||||
}
|
||||
|
||||
func (t *Timeline[T, PK]) LoadTop(min, max PK, length int, load func(min, max PK, length int) ([]T, error)) ([]T, error) {
|
||||
// Allocate expected no. values.
|
||||
values := make([]T, 0, length)
|
||||
|
||||
// Acquire lock.
|
||||
t.mutex.Lock()
|
||||
|
||||
// Wrap unlock to only do once.
|
||||
unlock := once(t.mutex.Unlock)
|
||||
defer unlock()
|
||||
|
||||
// Check init'd.
|
||||
if t.copy == nil {
|
||||
panic("not initialized")
|
||||
}
|
||||
|
||||
// Iterate through linked list from top (i.e. head).
|
||||
for next := t.list.head; next != nil; next = next.next {
|
||||
|
||||
// Check if we've gathered
|
||||
// enough values from timeline.
|
||||
if len(values) >= length {
|
||||
return values, nil
|
||||
}
|
||||
|
||||
item := (*indexed_item)(next.data)
|
||||
value := item.data.(T)
|
||||
pkey := t.pkey(value)
|
||||
|
||||
// Check if below min.
|
||||
if t.lte(pkey, min) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Update min.
|
||||
min = pkey
|
||||
|
||||
// Check if above max.
|
||||
if t.gte(pkey, max) {
|
||||
break
|
||||
}
|
||||
|
||||
// Append value copy.
|
||||
value = t.copy(value)
|
||||
values = append(values, value)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Timeline[T, PK]) LoadBottom(min, max PK, length int, load func(min, max PK, length int) ([]T, error)) ([]T, error) {
|
||||
// Allocate expected no. values.
|
||||
values := make([]T, 0, length)
|
||||
|
||||
// Acquire lock.
|
||||
t.mutex.Lock()
|
||||
|
||||
// Wrap unlock to only do once.
|
||||
unlock := once(t.mutex.Unlock)
|
||||
defer unlock()
|
||||
|
||||
// Check init'd.
|
||||
if t.copy == nil {
|
||||
panic("not initialized")
|
||||
}
|
||||
|
||||
// Iterate through linked list from bottom (i.e. tail).
|
||||
for next := t.list.tail; next != nil; next = next.prev {
|
||||
|
||||
// Check if we've gathered
|
||||
// enough values from timeline.
|
||||
if len(values) >= length {
|
||||
return values, nil
|
||||
}
|
||||
|
||||
item := (*indexed_item)(next.data)
|
||||
value := item.data.(T)
|
||||
pkey := t.pkey(value)
|
||||
|
||||
// Check if above max.
|
||||
if t.gte(pkey, max) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Update max.
|
||||
max = pkey
|
||||
|
||||
// Check if below min.
|
||||
if t.lte(pkey, min) {
|
||||
break
|
||||
}
|
||||
|
||||
// Append value copy.
|
||||
value = t.copy(value)
|
||||
values = append(values, value)
|
||||
}
|
||||
|
||||
// Done with
|
||||
// the lock.
|
||||
unlock()
|
||||
|
||||
// Attempt to load values up to given length.
|
||||
next, err := load(min, max, length-len(values))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Acquire lock.
|
||||
t.mutex.Lock()
|
||||
|
||||
// Store uncached values.
|
||||
for i := range next {
|
||||
t.store_value(
|
||||
nil, "",
|
||||
uncached[i],
|
||||
)
|
||||
}
|
||||
|
||||
// Done with lock.
|
||||
t.mutex.Unlock()
|
||||
|
||||
// Append uncached to return values.
|
||||
values = append(values, next...)
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func (t *Timeline[T, PK]) index(value T) *indexed_item {
|
||||
pk := t.pkey(value)
|
||||
|
||||
switch {
|
||||
case t.list.len == 0:
|
||||
|
||||
case pk < t.list.head.data:
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Timeline[T, PK]) delete(item *indexed_item) {
|
||||
|
||||
}
|
|
@ -57,13 +57,13 @@ func (q *Queue[T]) Init(config QueueConfig[T]) {
|
|||
// Safely copy over
|
||||
// provided config.
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
q.indices = make([]Index, len(config.Indices))
|
||||
for i, cfg := range config.Indices {
|
||||
q.indices[i].ptr = unsafe.Pointer(q)
|
||||
q.indices[i].init(t, cfg, 0)
|
||||
}
|
||||
q.pop = config.Pop
|
||||
q.mutex.Unlock()
|
||||
}
|
||||
|
||||
// Index selects index with given name from queue, else panics.
|
||||
|
@ -133,7 +133,7 @@ func (q *Queue[T]) Pop(index *Index, keys ...Key) []T {
|
|||
value := item.data.(T)
|
||||
values = append(values, value)
|
||||
|
||||
// Delete queued.
|
||||
// Delete item.
|
||||
q.delete(item)
|
||||
})
|
||||
}
|
||||
|
@ -253,7 +253,7 @@ func (q *Queue[T]) pop_n(n int, next func() *list_elem) []T {
|
|||
value := item.data.(T)
|
||||
values = append(values, value)
|
||||
|
||||
// Delete queued.
|
||||
// Delete item.
|
||||
q.delete(item)
|
||||
}
|
||||
|
||||
|
@ -298,9 +298,6 @@ func (q *Queue[T]) index(value T) *indexed_item {
|
|||
|
||||
// Extract fields comprising index key.
|
||||
parts := extract_fields(ptr, idx.fields)
|
||||
if parts == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Calculate index key.
|
||||
key := idx.key(buf, parts)
|
||||
|
@ -309,7 +306,14 @@ func (q *Queue[T]) index(value T) *indexed_item {
|
|||
}
|
||||
|
||||
// Append item to this index.
|
||||
idx.append(&q.queue, key, item)
|
||||
evicted := idx.append(key, item)
|
||||
if evicted != nil {
|
||||
|
||||
// This item is no longer
|
||||
// indexed, remove from list.
|
||||
q.queue.remove(&evicted.elem)
|
||||
free_indexed_item(evicted)
|
||||
}
|
||||
}
|
||||
|
||||
// Done with buf.
|
||||
|
@ -318,11 +322,12 @@ func (q *Queue[T]) index(value T) *indexed_item {
|
|||
return item
|
||||
}
|
||||
|
||||
func (q *Queue[T]) delete(item *indexed_item) {
|
||||
for len(item.indexed) != 0 {
|
||||
func (q *Queue[T]) delete(i *indexed_item) {
|
||||
for len(i.indexed) != 0 {
|
||||
// Pop last indexed entry from list.
|
||||
entry := item.indexed[len(item.indexed)-1]
|
||||
item.indexed = item.indexed[:len(item.indexed)-1]
|
||||
entry := i.indexed[len(i.indexed)-1]
|
||||
i.indexed[len(i.indexed)-1] = nil
|
||||
i.indexed = i.indexed[:len(i.indexed)-1]
|
||||
|
||||
// Get entry's index.
|
||||
index := entry.index
|
||||
|
@ -330,13 +335,13 @@ func (q *Queue[T]) delete(item *indexed_item) {
|
|||
// Drop this index_entry.
|
||||
index.delete_entry(entry)
|
||||
|
||||
// Check compact map.
|
||||
// Compact index map.
|
||||
index.data.Compact()
|
||||
}
|
||||
|
||||
// Drop entry from queue list.
|
||||
q.queue.remove(&item.elem)
|
||||
// Drop from queue list.
|
||||
q.queue.remove(&i.elem)
|
||||
|
||||
// Free now-unused item.
|
||||
free_indexed_item(item)
|
||||
// Free unused item.
|
||||
free_indexed_item(i)
|
||||
}
|
||||
|
|
|
@ -133,7 +133,7 @@ func (q *QueueCtx[T]) pop(ctx context.Context, next func() *list_elem) (T, bool)
|
|||
// Extract item value.
|
||||
value := item.data.(T)
|
||||
|
||||
// Delete queued.
|
||||
// Delete item.
|
||||
q.delete(item)
|
||||
|
||||
// Get func ptrs.
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//go:build go1.22 || go1.23
|
||||
|
||||
package structr
|
||||
|
||||
import (
|
||||
|
@ -36,6 +38,11 @@ type struct_field struct {
|
|||
// offsets defines whereabouts in
|
||||
// memory this field is located.
|
||||
offsets []next_offset
|
||||
|
||||
// determines whether field type
|
||||
// is ptr-like in-memory, and so
|
||||
// requires a further dereference.
|
||||
likeptr bool
|
||||
}
|
||||
|
||||
// next_offset defines a next offset location
|
||||
|
@ -107,6 +114,9 @@ func find_field(t reflect.Type, names []string) (sfield struct_field) {
|
|||
t = field.Type
|
||||
}
|
||||
|
||||
// Check if ptr-like in-memory.
|
||||
sfield.likeptr = like_ptr(t)
|
||||
|
||||
// Set final type.
|
||||
sfield.rtype = t
|
||||
|
||||
|
@ -126,10 +136,14 @@ func find_field(t reflect.Type, names []string) (sfield struct_field) {
|
|||
// extract_fields extracts given structfields from the provided value type,
|
||||
// this is done using predetermined struct field memory offset locations.
|
||||
func extract_fields(ptr unsafe.Pointer, fields []struct_field) []unsafe.Pointer {
|
||||
|
||||
// Prepare slice of field value pointers.
|
||||
ptrs := make([]unsafe.Pointer, len(fields))
|
||||
for i, field := range fields {
|
||||
if len(ptrs) != len(fields) {
|
||||
panic("BCE")
|
||||
}
|
||||
|
||||
for i, field := range fields {
|
||||
// loop scope.
|
||||
fptr := ptr
|
||||
|
||||
|
@ -145,7 +159,7 @@ func extract_fields(ptr unsafe.Pointer, fields []struct_field) []unsafe.Pointer
|
|||
offset.offset)
|
||||
}
|
||||
|
||||
if like_ptr(field.rtype) && fptr != nil {
|
||||
if field.likeptr && fptr != nil {
|
||||
// Further dereference value ptr.
|
||||
fptr = *(*unsafe.Pointer)(fptr)
|
||||
}
|
||||
|
@ -162,9 +176,63 @@ func extract_fields(ptr unsafe.Pointer, fields []struct_field) []unsafe.Pointer
|
|||
return ptrs
|
||||
}
|
||||
|
||||
// like_ptr returns whether type's kind is ptr-like.
|
||||
// pkey_field contains pre-prepared type
|
||||
// information about a primary key struct's
|
||||
// field member, including memory offset.
|
||||
type pkey_field struct {
|
||||
rtype reflect.Type
|
||||
|
||||
// offsets defines whereabouts in
|
||||
// memory this field is located.
|
||||
offsets []next_offset
|
||||
|
||||
// determines whether field type
|
||||
// is ptr-like in-memory, and so
|
||||
// requires a further dereference.
|
||||
likeptr bool
|
||||
}
|
||||
|
||||
// extract_pkey will extract a pointer from 'ptr', to
|
||||
// the primary key struct field defined by 'field'.
|
||||
func extract_pkey(ptr unsafe.Pointer, field pkey_field) unsafe.Pointer {
|
||||
for _, offset := range field.offsets {
|
||||
// Dereference any ptrs to offset.
|
||||
ptr = deref(ptr, offset.derefs)
|
||||
if ptr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Jump forward by offset to next ptr.
|
||||
ptr = unsafe.Pointer(uintptr(ptr) +
|
||||
offset.offset)
|
||||
}
|
||||
|
||||
if field.likeptr && ptr != nil {
|
||||
// Further dereference value ptr.
|
||||
ptr = *(*unsafe.Pointer)(ptr)
|
||||
}
|
||||
|
||||
return ptr
|
||||
}
|
||||
|
||||
// like_ptr returns whether type's kind is ptr-like in-memory,
|
||||
// which indicates it may need a final additional dereference.
|
||||
func like_ptr(t reflect.Type) bool {
|
||||
switch t.Kind() {
|
||||
case reflect.Array:
|
||||
switch n := t.Len(); n {
|
||||
case 1:
|
||||
// specifically single elem arrays
|
||||
// follow like_ptr for contained type.
|
||||
return like_ptr(t.Elem())
|
||||
}
|
||||
case reflect.Struct:
|
||||
switch n := t.NumField(); n {
|
||||
case 1:
|
||||
// specifically single field structs
|
||||
// follow like_ptr for contained type.
|
||||
return like_ptr(t.Field(0).Type)
|
||||
}
|
||||
case reflect.Pointer,
|
||||
reflect.Map,
|
||||
reflect.Chan,
|
||||
|
@ -201,7 +269,7 @@ func panicf(format string, args ...any) {
|
|||
// else it prints callsite info with a BUG report.
|
||||
//
|
||||
//go:noinline
|
||||
func should_not_reach() {
|
||||
func should_not_reach(exit bool) {
|
||||
pcs := make([]uintptr, 1)
|
||||
_ = runtime.Callers(2, pcs)
|
||||
fn := runtime.FuncForPC(pcs[0])
|
||||
|
@ -212,5 +280,9 @@ func should_not_reach() {
|
|||
funcname = funcname[i+1:]
|
||||
}
|
||||
}
|
||||
os.Stderr.WriteString("BUG: assertion failed in " + funcname + "\n")
|
||||
if exit {
|
||||
panic("BUG: assertion failed in " + funcname)
|
||||
} else {
|
||||
os.Stderr.WriteString("BUG: assertion failed in " + funcname + "\n")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
go test -v -tags=structr_32bit_hash .
|
||||
go test -v -tags=structr_48bit_hash .
|
||||
go test -v -tags=structr_64bit_hash .
|
File diff suppressed because it is too large
Load Diff
|
@ -3,29 +3,31 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package context defines the Context type, which carries deadlines,
|
||||
// cancelation signals, and other request-scoped values across API boundaries
|
||||
// cancellation signals, and other request-scoped values across API boundaries
|
||||
// and between processes.
|
||||
// As of Go 1.7 this package is available in the standard library under the
|
||||
// name context. https://golang.org/pkg/context.
|
||||
// name [context], and migrating to it can be done automatically with [go fix].
|
||||
//
|
||||
// Incoming requests to a server should create a Context, and outgoing calls to
|
||||
// servers should accept a Context. The chain of function calls between must
|
||||
// propagate the Context, optionally replacing it with a modified copy created
|
||||
// using WithDeadline, WithTimeout, WithCancel, or WithValue.
|
||||
// Incoming requests to a server should create a [Context], and outgoing
|
||||
// calls to servers should accept a Context. The chain of function
|
||||
// calls between them must propagate the Context, optionally replacing
|
||||
// it with a derived Context created using [WithCancel], [WithDeadline],
|
||||
// [WithTimeout], or [WithValue].
|
||||
//
|
||||
// Programs that use Contexts should follow these rules to keep interfaces
|
||||
// consistent across packages and enable static analysis tools to check context
|
||||
// propagation:
|
||||
//
|
||||
// Do not store Contexts inside a struct type; instead, pass a Context
|
||||
// explicitly to each function that needs it. The Context should be the first
|
||||
// explicitly to each function that needs it. This is discussed further in
|
||||
// https://go.dev/blog/context-and-structs. The Context should be the first
|
||||
// parameter, typically named ctx:
|
||||
//
|
||||
// func DoSomething(ctx context.Context, arg Arg) error {
|
||||
// // ... use ctx ...
|
||||
// }
|
||||
//
|
||||
// Do not pass a nil Context, even if a function permits it. Pass context.TODO
|
||||
// Do not pass a nil [Context], even if a function permits it. Pass [context.TODO]
|
||||
// if you are unsure about which Context to use.
|
||||
//
|
||||
// Use context Values only for request-scoped data that transits processes and
|
||||
|
@ -34,9 +36,30 @@
|
|||
// The same Context may be passed to functions running in different goroutines;
|
||||
// Contexts are safe for simultaneous use by multiple goroutines.
|
||||
//
|
||||
// See http://blog.golang.org/context for example code for a server that uses
|
||||
// See https://go.dev/blog/context for example code for a server that uses
|
||||
// Contexts.
|
||||
package context // import "golang.org/x/net/context"
|
||||
//
|
||||
// [go fix]: https://go.dev/cmd/go#hdr-Update_packages_to_use_new_APIs
|
||||
package context
|
||||
|
||||
import (
|
||||
"context" // standard library's context, as of Go 1.7
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Context carries a deadline, a cancellation signal, and other values across
|
||||
// API boundaries.
|
||||
//
|
||||
// Context's methods may be called by multiple goroutines simultaneously.
|
||||
type Context = context.Context
|
||||
|
||||
// Canceled is the error returned by [Context.Err] when the context is canceled
|
||||
// for some reason other than its deadline passing.
|
||||
var Canceled = context.Canceled
|
||||
|
||||
// DeadlineExceeded is the error returned by [Context.Err] when the context is canceled
|
||||
// due to its deadline passing.
|
||||
var DeadlineExceeded = context.DeadlineExceeded
|
||||
|
||||
// Background returns a non-nil, empty Context. It is never canceled, has no
|
||||
// values, and has no deadline. It is typically used by the main function,
|
||||
|
@ -49,8 +72,73 @@ func Background() Context {
|
|||
// TODO returns a non-nil, empty Context. Code should use context.TODO when
|
||||
// it's unclear which Context to use or it is not yet available (because the
|
||||
// surrounding function has not yet been extended to accept a Context
|
||||
// parameter). TODO is recognized by static analysis tools that determine
|
||||
// whether Contexts are propagated correctly in a program.
|
||||
// parameter).
|
||||
func TODO() Context {
|
||||
return todo
|
||||
}
|
||||
|
||||
var (
|
||||
background = context.Background()
|
||||
todo = context.TODO()
|
||||
)
|
||||
|
||||
// A CancelFunc tells an operation to abandon its work.
|
||||
// A CancelFunc does not wait for the work to stop.
|
||||
// A CancelFunc may be called by multiple goroutines simultaneously.
|
||||
// After the first call, subsequent calls to a CancelFunc do nothing.
|
||||
type CancelFunc = context.CancelFunc
|
||||
|
||||
// WithCancel returns a derived context that points to the parent context
|
||||
// but has a new Done channel. The returned context's Done channel is closed
|
||||
// when the returned cancel function is called or when the parent context's
|
||||
// Done channel is closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this [Context] complete.
|
||||
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
||||
return context.WithCancel(parent)
|
||||
}
|
||||
|
||||
// WithDeadline returns a derived context that points to the parent context
|
||||
// but has the deadline adjusted to be no later than d. If the parent's
|
||||
// deadline is already earlier than d, WithDeadline(parent, d) is semantically
|
||||
// equivalent to parent. The returned [Context.Done] channel is closed when
|
||||
// the deadline expires, when the returned cancel function is called,
|
||||
// or when the parent context's Done channel is closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this [Context] complete.
|
||||
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
|
||||
return context.WithDeadline(parent, d)
|
||||
}
|
||||
|
||||
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this [Context] complete:
|
||||
//
|
||||
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
||||
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
||||
// return slowOperation(ctx)
|
||||
// }
|
||||
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
||||
return context.WithTimeout(parent, timeout)
|
||||
}
|
||||
|
||||
// WithValue returns a derived context that points to the parent Context.
|
||||
// In the derived context, the value associated with key is val.
|
||||
//
|
||||
// Use context Values only for request-scoped data that transits processes and
|
||||
// APIs, not for passing optional parameters to functions.
|
||||
//
|
||||
// The provided key must be comparable and should not be of type
|
||||
// string or any other built-in type to avoid collisions between
|
||||
// packages using context. Users of WithValue should define their own
|
||||
// types for keys. To avoid allocating when assigning to an
|
||||
// interface{}, context keys often have concrete type
|
||||
// struct{}. Alternatively, exported context key variables' static
|
||||
// type should be a pointer or interface.
|
||||
func WithValue(parent Context, key, val interface{}) Context {
|
||||
return context.WithValue(parent, key, val)
|
||||
}
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
// Copyright 2016 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.
|
||||
|
||||
//go:build go1.7
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"context" // standard library's context, as of Go 1.7
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
todo = context.TODO()
|
||||
background = context.Background()
|
||||
)
|
||||
|
||||
// Canceled is the error returned by Context.Err when the context is canceled.
|
||||
var Canceled = context.Canceled
|
||||
|
||||
// DeadlineExceeded is the error returned by Context.Err when the context's
|
||||
// deadline passes.
|
||||
var DeadlineExceeded = context.DeadlineExceeded
|
||||
|
||||
// WithCancel returns a copy of parent with a new Done channel. The returned
|
||||
// context's Done channel is closed when the returned cancel function is called
|
||||
// or when the parent context's Done channel is closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
||||
ctx, f := context.WithCancel(parent)
|
||||
return ctx, f
|
||||
}
|
||||
|
||||
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
||||
// to be no later than d. If the parent's deadline is already earlier than d,
|
||||
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
|
||||
// context's Done channel is closed when the deadline expires, when the returned
|
||||
// cancel function is called, or when the parent context's Done channel is
|
||||
// closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
|
||||
ctx, f := context.WithDeadline(parent, deadline)
|
||||
return ctx, f
|
||||
}
|
||||
|
||||
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete:
|
||||
//
|
||||
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
||||
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
||||
// return slowOperation(ctx)
|
||||
// }
|
||||
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
||||
return WithDeadline(parent, time.Now().Add(timeout))
|
||||
}
|
||||
|
||||
// WithValue returns a copy of parent in which the value associated with key is
|
||||
// val.
|
||||
//
|
||||
// Use context Values only for request-scoped data that transits processes and
|
||||
// APIs, not for passing optional parameters to functions.
|
||||
func WithValue(parent Context, key interface{}, val interface{}) Context {
|
||||
return context.WithValue(parent, key, val)
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright 2017 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.
|
||||
|
||||
//go:build go1.9
|
||||
|
||||
package context
|
||||
|
||||
import "context" // standard library's context, as of Go 1.7
|
||||
|
||||
// A Context carries a deadline, a cancelation signal, and other values across
|
||||
// API boundaries.
|
||||
//
|
||||
// Context's methods may be called by multiple goroutines simultaneously.
|
||||
type Context = context.Context
|
||||
|
||||
// A CancelFunc tells an operation to abandon its work.
|
||||
// A CancelFunc does not wait for the work to stop.
|
||||
// After the first call, subsequent calls to a CancelFunc do nothing.
|
||||
type CancelFunc = context.CancelFunc
|
|
@ -1,300 +0,0 @@
|
|||
// Copyright 2014 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.
|
||||
|
||||
//go:build !go1.7
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
|
||||
// struct{}, since vars of this type must have distinct addresses.
|
||||
type emptyCtx int
|
||||
|
||||
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func (*emptyCtx) Done() <-chan struct{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*emptyCtx) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*emptyCtx) Value(key interface{}) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *emptyCtx) String() string {
|
||||
switch e {
|
||||
case background:
|
||||
return "context.Background"
|
||||
case todo:
|
||||
return "context.TODO"
|
||||
}
|
||||
return "unknown empty Context"
|
||||
}
|
||||
|
||||
var (
|
||||
background = new(emptyCtx)
|
||||
todo = new(emptyCtx)
|
||||
)
|
||||
|
||||
// Canceled is the error returned by Context.Err when the context is canceled.
|
||||
var Canceled = errors.New("context canceled")
|
||||
|
||||
// DeadlineExceeded is the error returned by Context.Err when the context's
|
||||
// deadline passes.
|
||||
var DeadlineExceeded = errors.New("context deadline exceeded")
|
||||
|
||||
// WithCancel returns a copy of parent with a new Done channel. The returned
|
||||
// context's Done channel is closed when the returned cancel function is called
|
||||
// or when the parent context's Done channel is closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
||||
c := newCancelCtx(parent)
|
||||
propagateCancel(parent, c)
|
||||
return c, func() { c.cancel(true, Canceled) }
|
||||
}
|
||||
|
||||
// newCancelCtx returns an initialized cancelCtx.
|
||||
func newCancelCtx(parent Context) *cancelCtx {
|
||||
return &cancelCtx{
|
||||
Context: parent,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// propagateCancel arranges for child to be canceled when parent is.
|
||||
func propagateCancel(parent Context, child canceler) {
|
||||
if parent.Done() == nil {
|
||||
return // parent is never canceled
|
||||
}
|
||||
if p, ok := parentCancelCtx(parent); ok {
|
||||
p.mu.Lock()
|
||||
if p.err != nil {
|
||||
// parent has already been canceled
|
||||
child.cancel(false, p.err)
|
||||
} else {
|
||||
if p.children == nil {
|
||||
p.children = make(map[canceler]bool)
|
||||
}
|
||||
p.children[child] = true
|
||||
}
|
||||
p.mu.Unlock()
|
||||
} else {
|
||||
go func() {
|
||||
select {
|
||||
case <-parent.Done():
|
||||
child.cancel(false, parent.Err())
|
||||
case <-child.Done():
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// parentCancelCtx follows a chain of parent references until it finds a
|
||||
// *cancelCtx. This function understands how each of the concrete types in this
|
||||
// package represents its parent.
|
||||
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
|
||||
for {
|
||||
switch c := parent.(type) {
|
||||
case *cancelCtx:
|
||||
return c, true
|
||||
case *timerCtx:
|
||||
return c.cancelCtx, true
|
||||
case *valueCtx:
|
||||
parent = c.Context
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removeChild removes a context from its parent.
|
||||
func removeChild(parent Context, child canceler) {
|
||||
p, ok := parentCancelCtx(parent)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
p.mu.Lock()
|
||||
if p.children != nil {
|
||||
delete(p.children, child)
|
||||
}
|
||||
p.mu.Unlock()
|
||||
}
|
||||
|
||||
// A canceler is a context type that can be canceled directly. The
|
||||
// implementations are *cancelCtx and *timerCtx.
|
||||
type canceler interface {
|
||||
cancel(removeFromParent bool, err error)
|
||||
Done() <-chan struct{}
|
||||
}
|
||||
|
||||
// A cancelCtx can be canceled. When canceled, it also cancels any children
|
||||
// that implement canceler.
|
||||
type cancelCtx struct {
|
||||
Context
|
||||
|
||||
done chan struct{} // closed by the first cancel call.
|
||||
|
||||
mu sync.Mutex
|
||||
children map[canceler]bool // set to nil by the first cancel call
|
||||
err error // set to non-nil by the first cancel call
|
||||
}
|
||||
|
||||
func (c *cancelCtx) Done() <-chan struct{} {
|
||||
return c.done
|
||||
}
|
||||
|
||||
func (c *cancelCtx) Err() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.err
|
||||
}
|
||||
|
||||
func (c *cancelCtx) String() string {
|
||||
return fmt.Sprintf("%v.WithCancel", c.Context)
|
||||
}
|
||||
|
||||
// cancel closes c.done, cancels each of c's children, and, if
|
||||
// removeFromParent is true, removes c from its parent's children.
|
||||
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
|
||||
if err == nil {
|
||||
panic("context: internal error: missing cancel error")
|
||||
}
|
||||
c.mu.Lock()
|
||||
if c.err != nil {
|
||||
c.mu.Unlock()
|
||||
return // already canceled
|
||||
}
|
||||
c.err = err
|
||||
close(c.done)
|
||||
for child := range c.children {
|
||||
// NOTE: acquiring the child's lock while holding parent's lock.
|
||||
child.cancel(false, err)
|
||||
}
|
||||
c.children = nil
|
||||
c.mu.Unlock()
|
||||
|
||||
if removeFromParent {
|
||||
removeChild(c.Context, c)
|
||||
}
|
||||
}
|
||||
|
||||
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
||||
// to be no later than d. If the parent's deadline is already earlier than d,
|
||||
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
|
||||
// context's Done channel is closed when the deadline expires, when the returned
|
||||
// cancel function is called, or when the parent context's Done channel is
|
||||
// closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
|
||||
if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
|
||||
// The current deadline is already sooner than the new one.
|
||||
return WithCancel(parent)
|
||||
}
|
||||
c := &timerCtx{
|
||||
cancelCtx: newCancelCtx(parent),
|
||||
deadline: deadline,
|
||||
}
|
||||
propagateCancel(parent, c)
|
||||
d := deadline.Sub(time.Now())
|
||||
if d <= 0 {
|
||||
c.cancel(true, DeadlineExceeded) // deadline has already passed
|
||||
return c, func() { c.cancel(true, Canceled) }
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.err == nil {
|
||||
c.timer = time.AfterFunc(d, func() {
|
||||
c.cancel(true, DeadlineExceeded)
|
||||
})
|
||||
}
|
||||
return c, func() { c.cancel(true, Canceled) }
|
||||
}
|
||||
|
||||
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
|
||||
// implement Done and Err. It implements cancel by stopping its timer then
|
||||
// delegating to cancelCtx.cancel.
|
||||
type timerCtx struct {
|
||||
*cancelCtx
|
||||
timer *time.Timer // Under cancelCtx.mu.
|
||||
|
||||
deadline time.Time
|
||||
}
|
||||
|
||||
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
|
||||
return c.deadline, true
|
||||
}
|
||||
|
||||
func (c *timerCtx) String() string {
|
||||
return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now()))
|
||||
}
|
||||
|
||||
func (c *timerCtx) cancel(removeFromParent bool, err error) {
|
||||
c.cancelCtx.cancel(false, err)
|
||||
if removeFromParent {
|
||||
// Remove this timerCtx from its parent cancelCtx's children.
|
||||
removeChild(c.cancelCtx.Context, c)
|
||||
}
|
||||
c.mu.Lock()
|
||||
if c.timer != nil {
|
||||
c.timer.Stop()
|
||||
c.timer = nil
|
||||
}
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete:
|
||||
//
|
||||
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
||||
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
||||
// return slowOperation(ctx)
|
||||
// }
|
||||
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
||||
return WithDeadline(parent, time.Now().Add(timeout))
|
||||
}
|
||||
|
||||
// WithValue returns a copy of parent in which the value associated with key is
|
||||
// val.
|
||||
//
|
||||
// Use context Values only for request-scoped data that transits processes and
|
||||
// APIs, not for passing optional parameters to functions.
|
||||
func WithValue(parent Context, key interface{}, val interface{}) Context {
|
||||
return &valueCtx{parent, key, val}
|
||||
}
|
||||
|
||||
// A valueCtx carries a key-value pair. It implements Value for that key and
|
||||
// delegates all other calls to the embedded Context.
|
||||
type valueCtx struct {
|
||||
Context
|
||||
key, val interface{}
|
||||
}
|
||||
|
||||
func (c *valueCtx) String() string {
|
||||
return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
|
||||
}
|
||||
|
||||
func (c *valueCtx) Value(key interface{}) interface{} {
|
||||
if c.key == key {
|
||||
return c.val
|
||||
}
|
||||
return c.Context.Value(key)
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
// Copyright 2014 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.
|
||||
|
||||
//go:build !go1.9
|
||||
|
||||
package context
|
||||
|
||||
import "time"
|
||||
|
||||
// A Context carries a deadline, a cancelation signal, and other values across
|
||||
// API boundaries.
|
||||
//
|
||||
// Context's methods may be called by multiple goroutines simultaneously.
|
||||
type Context interface {
|
||||
// Deadline returns the time when work done on behalf of this context
|
||||
// should be canceled. Deadline returns ok==false when no deadline is
|
||||
// set. Successive calls to Deadline return the same results.
|
||||
Deadline() (deadline time.Time, ok bool)
|
||||
|
||||
// Done returns a channel that's closed when work done on behalf of this
|
||||
// context should be canceled. Done may return nil if this context can
|
||||
// never be canceled. Successive calls to Done return the same value.
|
||||
//
|
||||
// WithCancel arranges for Done to be closed when cancel is called;
|
||||
// WithDeadline arranges for Done to be closed when the deadline
|
||||
// expires; WithTimeout arranges for Done to be closed when the timeout
|
||||
// elapses.
|
||||
//
|
||||
// Done is provided for use in select statements:
|
||||
//
|
||||
// // Stream generates values with DoSomething and sends them to out
|
||||
// // until DoSomething returns an error or ctx.Done is closed.
|
||||
// func Stream(ctx context.Context, out chan<- Value) error {
|
||||
// for {
|
||||
// v, err := DoSomething(ctx)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// select {
|
||||
// case <-ctx.Done():
|
||||
// return ctx.Err()
|
||||
// case out <- v:
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// See http://blog.golang.org/pipelines for more examples of how to use
|
||||
// a Done channel for cancelation.
|
||||
Done() <-chan struct{}
|
||||
|
||||
// Err returns a non-nil error value after Done is closed. Err returns
|
||||
// Canceled if the context was canceled or DeadlineExceeded if the
|
||||
// context's deadline passed. No other values for Err are defined.
|
||||
// After Done is closed, successive calls to Err return the same value.
|
||||
Err() error
|
||||
|
||||
// Value returns the value associated with this context for key, or nil
|
||||
// if no value is associated with key. Successive calls to Value with
|
||||
// the same key returns the same result.
|
||||
//
|
||||
// Use context values only for request-scoped data that transits
|
||||
// processes and API boundaries, not for passing optional parameters to
|
||||
// functions.
|
||||
//
|
||||
// A key identifies a specific value in a Context. Functions that wish
|
||||
// to store values in Context typically allocate a key in a global
|
||||
// variable then use that key as the argument to context.WithValue and
|
||||
// Context.Value. A key can be any type that supports equality;
|
||||
// packages should define keys as an unexported type to avoid
|
||||
// collisions.
|
||||
//
|
||||
// Packages that define a Context key should provide type-safe accessors
|
||||
// for the values stores using that key:
|
||||
//
|
||||
// // Package user defines a User type that's stored in Contexts.
|
||||
// package user
|
||||
//
|
||||
// import "golang.org/x/net/context"
|
||||
//
|
||||
// // User is the type of value stored in the Contexts.
|
||||
// type User struct {...}
|
||||
//
|
||||
// // key is an unexported type for keys defined in this package.
|
||||
// // This prevents collisions with keys defined in other packages.
|
||||
// type key int
|
||||
//
|
||||
// // userKey is the key for user.User values in Contexts. It is
|
||||
// // unexported; clients use user.NewContext and user.FromContext
|
||||
// // instead of using this key directly.
|
||||
// var userKey key = 0
|
||||
//
|
||||
// // NewContext returns a new Context that carries value u.
|
||||
// func NewContext(ctx context.Context, u *User) context.Context {
|
||||
// return context.WithValue(ctx, userKey, u)
|
||||
// }
|
||||
//
|
||||
// // FromContext returns the User value stored in ctx, if any.
|
||||
// func FromContext(ctx context.Context) (*User, bool) {
|
||||
// u, ok := ctx.Value(userKey).(*User)
|
||||
// return u, ok
|
||||
// }
|
||||
Value(key interface{}) interface{}
|
||||
}
|
||||
|
||||
// A CancelFunc tells an operation to abandon its work.
|
||||
// A CancelFunc does not wait for the work to stop.
|
||||
// After the first call, subsequent calls to a CancelFunc do nothing.
|
||||
type CancelFunc func()
|
|
@ -2233,25 +2233,25 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream
|
|||
func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*responseWriter, *http.Request, error) {
|
||||
sc.serveG.check()
|
||||
|
||||
rp := requestParam{
|
||||
method: f.PseudoValue("method"),
|
||||
scheme: f.PseudoValue("scheme"),
|
||||
authority: f.PseudoValue("authority"),
|
||||
path: f.PseudoValue("path"),
|
||||
protocol: f.PseudoValue("protocol"),
|
||||
rp := httpcommon.ServerRequestParam{
|
||||
Method: f.PseudoValue("method"),
|
||||
Scheme: f.PseudoValue("scheme"),
|
||||
Authority: f.PseudoValue("authority"),
|
||||
Path: f.PseudoValue("path"),
|
||||
Protocol: f.PseudoValue("protocol"),
|
||||
}
|
||||
|
||||
// extended connect is disabled, so we should not see :protocol
|
||||
if disableExtendedConnectProtocol && rp.protocol != "" {
|
||||
if disableExtendedConnectProtocol && rp.Protocol != "" {
|
||||
return nil, nil, sc.countError("bad_connect", streamError(f.StreamID, ErrCodeProtocol))
|
||||
}
|
||||
|
||||
isConnect := rp.method == "CONNECT"
|
||||
isConnect := rp.Method == "CONNECT"
|
||||
if isConnect {
|
||||
if rp.protocol == "" && (rp.path != "" || rp.scheme != "" || rp.authority == "") {
|
||||
if rp.Protocol == "" && (rp.Path != "" || rp.Scheme != "" || rp.Authority == "") {
|
||||
return nil, nil, sc.countError("bad_connect", streamError(f.StreamID, ErrCodeProtocol))
|
||||
}
|
||||
} else if rp.method == "" || rp.path == "" || (rp.scheme != "https" && rp.scheme != "http") {
|
||||
} else if rp.Method == "" || rp.Path == "" || (rp.Scheme != "https" && rp.Scheme != "http") {
|
||||
// See 8.1.2.6 Malformed Requests and Responses:
|
||||
//
|
||||
// Malformed requests or responses that are detected
|
||||
|
@ -2265,15 +2265,16 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
|
|||
return nil, nil, sc.countError("bad_path_method", streamError(f.StreamID, ErrCodeProtocol))
|
||||
}
|
||||
|
||||
rp.header = make(http.Header)
|
||||
header := make(http.Header)
|
||||
rp.Header = header
|
||||
for _, hf := range f.RegularFields() {
|
||||
rp.header.Add(sc.canonicalHeader(hf.Name), hf.Value)
|
||||
header.Add(sc.canonicalHeader(hf.Name), hf.Value)
|
||||
}
|
||||
if rp.authority == "" {
|
||||
rp.authority = rp.header.Get("Host")
|
||||
if rp.Authority == "" {
|
||||
rp.Authority = header.Get("Host")
|
||||
}
|
||||
if rp.protocol != "" {
|
||||
rp.header.Set(":protocol", rp.protocol)
|
||||
if rp.Protocol != "" {
|
||||
header.Set(":protocol", rp.Protocol)
|
||||
}
|
||||
|
||||
rw, req, err := sc.newWriterAndRequestNoBody(st, rp)
|
||||
|
@ -2282,7 +2283,7 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
|
|||
}
|
||||
bodyOpen := !f.StreamEnded()
|
||||
if bodyOpen {
|
||||
if vv, ok := rp.header["Content-Length"]; ok {
|
||||
if vv, ok := rp.Header["Content-Length"]; ok {
|
||||
if cl, err := strconv.ParseUint(vv[0], 10, 63); err == nil {
|
||||
req.ContentLength = int64(cl)
|
||||
} else {
|
||||
|
@ -2298,84 +2299,38 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
|
|||
return rw, req, nil
|
||||
}
|
||||
|
||||
type requestParam struct {
|
||||
method string
|
||||
scheme, authority, path string
|
||||
protocol string
|
||||
header http.Header
|
||||
}
|
||||
|
||||
func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp requestParam) (*responseWriter, *http.Request, error) {
|
||||
func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp httpcommon.ServerRequestParam) (*responseWriter, *http.Request, error) {
|
||||
sc.serveG.check()
|
||||
|
||||
var tlsState *tls.ConnectionState // nil if not scheme https
|
||||
if rp.scheme == "https" {
|
||||
if rp.Scheme == "https" {
|
||||
tlsState = sc.tlsState
|
||||
}
|
||||
|
||||
needsContinue := httpguts.HeaderValuesContainsToken(rp.header["Expect"], "100-continue")
|
||||
if needsContinue {
|
||||
rp.header.Del("Expect")
|
||||
}
|
||||
// Merge Cookie headers into one "; "-delimited value.
|
||||
if cookies := rp.header["Cookie"]; len(cookies) > 1 {
|
||||
rp.header.Set("Cookie", strings.Join(cookies, "; "))
|
||||
}
|
||||
|
||||
// Setup Trailers
|
||||
var trailer http.Header
|
||||
for _, v := range rp.header["Trailer"] {
|
||||
for _, key := range strings.Split(v, ",") {
|
||||
key = http.CanonicalHeaderKey(textproto.TrimString(key))
|
||||
switch key {
|
||||
case "Transfer-Encoding", "Trailer", "Content-Length":
|
||||
// Bogus. (copy of http1 rules)
|
||||
// Ignore.
|
||||
default:
|
||||
if trailer == nil {
|
||||
trailer = make(http.Header)
|
||||
}
|
||||
trailer[key] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
delete(rp.header, "Trailer")
|
||||
|
||||
var url_ *url.URL
|
||||
var requestURI string
|
||||
if rp.method == "CONNECT" && rp.protocol == "" {
|
||||
url_ = &url.URL{Host: rp.authority}
|
||||
requestURI = rp.authority // mimic HTTP/1 server behavior
|
||||
} else {
|
||||
var err error
|
||||
url_, err = url.ParseRequestURI(rp.path)
|
||||
if err != nil {
|
||||
return nil, nil, sc.countError("bad_path", streamError(st.id, ErrCodeProtocol))
|
||||
}
|
||||
requestURI = rp.path
|
||||
res := httpcommon.NewServerRequest(rp)
|
||||
if res.InvalidReason != "" {
|
||||
return nil, nil, sc.countError(res.InvalidReason, streamError(st.id, ErrCodeProtocol))
|
||||
}
|
||||
|
||||
body := &requestBody{
|
||||
conn: sc,
|
||||
stream: st,
|
||||
needsContinue: needsContinue,
|
||||
needsContinue: res.NeedsContinue,
|
||||
}
|
||||
req := &http.Request{
|
||||
Method: rp.method,
|
||||
URL: url_,
|
||||
req := (&http.Request{
|
||||
Method: rp.Method,
|
||||
URL: res.URL,
|
||||
RemoteAddr: sc.remoteAddrStr,
|
||||
Header: rp.header,
|
||||
RequestURI: requestURI,
|
||||
Header: rp.Header,
|
||||
RequestURI: res.RequestURI,
|
||||
Proto: "HTTP/2.0",
|
||||
ProtoMajor: 2,
|
||||
ProtoMinor: 0,
|
||||
TLS: tlsState,
|
||||
Host: rp.authority,
|
||||
Host: rp.Authority,
|
||||
Body: body,
|
||||
Trailer: trailer,
|
||||
}
|
||||
req = req.WithContext(st.ctx)
|
||||
|
||||
Trailer: res.Trailer,
|
||||
}).WithContext(st.ctx)
|
||||
rw := sc.newResponseWriter(st, req)
|
||||
return rw, req, nil
|
||||
}
|
||||
|
@ -3270,12 +3225,12 @@ func (sc *serverConn) startPush(msg *startPushRequest) {
|
|||
// we start in "half closed (remote)" for simplicity.
|
||||
// See further comments at the definition of stateHalfClosedRemote.
|
||||
promised := sc.newStream(promisedID, msg.parent.id, stateHalfClosedRemote)
|
||||
rw, req, err := sc.newWriterAndRequestNoBody(promised, requestParam{
|
||||
method: msg.method,
|
||||
scheme: msg.url.Scheme,
|
||||
authority: msg.url.Host,
|
||||
path: msg.url.RequestURI(),
|
||||
header: cloneHeader(msg.header), // clone since handler runs concurrently with writing the PUSH_PROMISE
|
||||
rw, req, err := sc.newWriterAndRequestNoBody(promised, httpcommon.ServerRequestParam{
|
||||
Method: msg.method,
|
||||
Scheme: msg.url.Scheme,
|
||||
Authority: msg.url.Host,
|
||||
Path: msg.url.RequestURI(),
|
||||
Header: cloneHeader(msg.header), // clone since handler runs concurrently with writing the PUSH_PROMISE
|
||||
})
|
||||
if err != nil {
|
||||
// Should not happen, since we've already validated msg.url.
|
||||
|
|
|
@ -1286,6 +1286,19 @@ func (cc *ClientConn) responseHeaderTimeout() time.Duration {
|
|||
return 0
|
||||
}
|
||||
|
||||
// actualContentLength returns a sanitized version of
|
||||
// req.ContentLength, where 0 actually means zero (not unknown) and -1
|
||||
// means unknown.
|
||||
func actualContentLength(req *http.Request) int64 {
|
||||
if req.Body == nil || req.Body == http.NoBody {
|
||||
return 0
|
||||
}
|
||||
if req.ContentLength != 0 {
|
||||
return req.ContentLength
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (cc *ClientConn) decrStreamReservations() {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
|
@ -1310,7 +1323,7 @@ func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream))
|
|||
reqCancel: req.Cancel,
|
||||
isHead: req.Method == "HEAD",
|
||||
reqBody: req.Body,
|
||||
reqBodyContentLength: httpcommon.ActualContentLength(req),
|
||||
reqBodyContentLength: actualContentLength(req),
|
||||
trace: httptrace.ContextClientTrace(ctx),
|
||||
peerClosed: make(chan struct{}),
|
||||
abort: make(chan struct{}),
|
||||
|
@ -1318,7 +1331,7 @@ func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream))
|
|||
donec: make(chan struct{}),
|
||||
}
|
||||
|
||||
cs.requestedGzip = httpcommon.IsRequestGzip(req, cc.t.disableCompression())
|
||||
cs.requestedGzip = httpcommon.IsRequestGzip(req.Method, req.Header, cc.t.disableCompression())
|
||||
|
||||
go cs.doRequest(req, streamf)
|
||||
|
||||
|
@ -1349,7 +1362,7 @@ func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream))
|
|||
}
|
||||
res.Request = req
|
||||
res.TLS = cc.tlsState
|
||||
if res.Body == noBody && httpcommon.ActualContentLength(req) == 0 {
|
||||
if res.Body == noBody && actualContentLength(req) == 0 {
|
||||
// If there isn't a request or response body still being
|
||||
// written, then wait for the stream to be closed before
|
||||
// RoundTrip returns.
|
||||
|
@ -1596,12 +1609,7 @@ func (cs *clientStream) encodeAndWriteHeaders(req *http.Request) error {
|
|||
// sent by writeRequestBody below, along with any Trailers,
|
||||
// again in form HEADERS{1}, CONTINUATION{0,})
|
||||
cc.hbuf.Reset()
|
||||
res, err := httpcommon.EncodeHeaders(httpcommon.EncodeHeadersParam{
|
||||
Request: req,
|
||||
AddGzipHeader: cs.requestedGzip,
|
||||
PeerMaxHeaderListSize: cc.peerMaxHeaderListSize,
|
||||
DefaultUserAgent: defaultUserAgent,
|
||||
}, func(name, value string) {
|
||||
res, err := encodeRequestHeaders(req, cs.requestedGzip, cc.peerMaxHeaderListSize, func(name, value string) {
|
||||
cc.writeHeader(name, value)
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -1617,6 +1625,22 @@ func (cs *clientStream) encodeAndWriteHeaders(req *http.Request) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func encodeRequestHeaders(req *http.Request, addGzipHeader bool, peerMaxHeaderListSize uint64, headerf func(name, value string)) (httpcommon.EncodeHeadersResult, error) {
|
||||
return httpcommon.EncodeHeaders(req.Context(), httpcommon.EncodeHeadersParam{
|
||||
Request: httpcommon.Request{
|
||||
Header: req.Header,
|
||||
Trailer: req.Trailer,
|
||||
URL: req.URL,
|
||||
Host: req.Host,
|
||||
Method: req.Method,
|
||||
ActualContentLength: actualContentLength(req),
|
||||
},
|
||||
AddGzipHeader: addGzipHeader,
|
||||
PeerMaxHeaderListSize: peerMaxHeaderListSize,
|
||||
DefaultUserAgent: defaultUserAgent,
|
||||
}, headerf)
|
||||
}
|
||||
|
||||
// cleanupWriteRequest performs post-request tasks.
|
||||
//
|
||||
// If err (the result of writeRequest) is non-nil and the stream is not closed,
|
||||
|
@ -2186,6 +2210,13 @@ func (rl *clientConnReadLoop) cleanup() {
|
|||
}
|
||||
cc.cond.Broadcast()
|
||||
cc.mu.Unlock()
|
||||
|
||||
if !cc.seenSettings {
|
||||
// If we have a pending request that wants extended CONNECT,
|
||||
// let it continue and fail with the connection error.
|
||||
cc.extendedConnectAllowed = true
|
||||
close(cc.seenSettingsChan)
|
||||
}
|
||||
}
|
||||
|
||||
// countReadFrameError calls Transport.CountError with a string
|
||||
|
@ -2278,9 +2309,6 @@ func (rl *clientConnReadLoop) run() error {
|
|||
if VerboseLogs {
|
||||
cc.vlogf("http2: Transport conn %p received error from processing frame %v: %v", cc, summarizeFrame(f), err)
|
||||
}
|
||||
if !cc.seenSettings {
|
||||
close(cc.seenSettingsChan)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
package httpcommon
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
@ -82,7 +82,7 @@ func buildCommonHeaderMaps() {
|
|||
commonLowerHeader = make(map[string]string, len(common))
|
||||
commonCanonHeader = make(map[string]string, len(common))
|
||||
for _, v := range common {
|
||||
chk := http.CanonicalHeaderKey(v)
|
||||
chk := textproto.CanonicalMIMEHeaderKey(v)
|
||||
commonLowerHeader[chk] = v
|
||||
commonCanonHeader[v] = chk
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ func CanonicalHeader(v string) string {
|
|||
if s, ok := commonCanonHeader[v]; ok {
|
||||
return s
|
||||
}
|
||||
return http.CanonicalHeaderKey(v)
|
||||
return textproto.CanonicalMIMEHeaderKey(v)
|
||||
}
|
||||
|
||||
// CachedCanonicalHeader returns the canonical form of a well-known header name.
|
||||
|
|
|
@ -5,10 +5,12 @@
|
|||
package httpcommon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -21,9 +23,21 @@ var (
|
|||
ErrRequestHeaderListSize = errors.New("request header list larger than peer's advertised limit")
|
||||
)
|
||||
|
||||
// Request is a subset of http.Request.
|
||||
// It'd be simpler to pass an *http.Request, of course, but we can't depend on net/http
|
||||
// without creating a dependency cycle.
|
||||
type Request struct {
|
||||
URL *url.URL
|
||||
Method string
|
||||
Host string
|
||||
Header map[string][]string
|
||||
Trailer map[string][]string
|
||||
ActualContentLength int64 // 0 means 0, -1 means unknown
|
||||
}
|
||||
|
||||
// EncodeHeadersParam is parameters to EncodeHeaders.
|
||||
type EncodeHeadersParam struct {
|
||||
Request *http.Request
|
||||
Request Request
|
||||
|
||||
// AddGzipHeader indicates that an "accept-encoding: gzip" header should be
|
||||
// added to the request.
|
||||
|
@ -47,11 +61,11 @@ type EncodeHeadersResult struct {
|
|||
// It validates a request and calls headerf with each pseudo-header and header
|
||||
// for the request.
|
||||
// The headerf function is called with the validated, canonicalized header name.
|
||||
func EncodeHeaders(param EncodeHeadersParam, headerf func(name, value string)) (res EncodeHeadersResult, _ error) {
|
||||
func EncodeHeaders(ctx context.Context, param EncodeHeadersParam, headerf func(name, value string)) (res EncodeHeadersResult, _ error) {
|
||||
req := param.Request
|
||||
|
||||
// Check for invalid connection-level headers.
|
||||
if err := checkConnHeaders(req); err != nil {
|
||||
if err := checkConnHeaders(req.Header); err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
|
@ -73,7 +87,10 @@ func EncodeHeaders(param EncodeHeadersParam, headerf func(name, value string)) (
|
|||
|
||||
// isNormalConnect is true if this is a non-extended CONNECT request.
|
||||
isNormalConnect := false
|
||||
protocol := req.Header.Get(":protocol")
|
||||
var protocol string
|
||||
if vv := req.Header[":protocol"]; len(vv) > 0 {
|
||||
protocol = vv[0]
|
||||
}
|
||||
if req.Method == "CONNECT" && protocol == "" {
|
||||
isNormalConnect = true
|
||||
} else if protocol != "" && req.Method != "CONNECT" {
|
||||
|
@ -107,9 +124,7 @@ func EncodeHeaders(param EncodeHeadersParam, headerf func(name, value string)) (
|
|||
return res, fmt.Errorf("invalid HTTP trailer %s", err)
|
||||
}
|
||||
|
||||
contentLength := ActualContentLength(req)
|
||||
|
||||
trailers, err := commaSeparatedTrailers(req)
|
||||
trailers, err := commaSeparatedTrailers(req.Trailer)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
@ -123,7 +138,7 @@ func EncodeHeaders(param EncodeHeadersParam, headerf func(name, value string)) (
|
|||
f(":authority", host)
|
||||
m := req.Method
|
||||
if m == "" {
|
||||
m = http.MethodGet
|
||||
m = "GET"
|
||||
}
|
||||
f(":method", m)
|
||||
if !isNormalConnect {
|
||||
|
@ -198,8 +213,8 @@ func EncodeHeaders(param EncodeHeadersParam, headerf func(name, value string)) (
|
|||
f(k, v)
|
||||
}
|
||||
}
|
||||
if shouldSendReqContentLength(req.Method, contentLength) {
|
||||
f("content-length", strconv.FormatInt(contentLength, 10))
|
||||
if shouldSendReqContentLength(req.Method, req.ActualContentLength) {
|
||||
f("content-length", strconv.FormatInt(req.ActualContentLength, 10))
|
||||
}
|
||||
if param.AddGzipHeader {
|
||||
f("accept-encoding", "gzip")
|
||||
|
@ -225,7 +240,7 @@ func EncodeHeaders(param EncodeHeadersParam, headerf func(name, value string)) (
|
|||
}
|
||||
}
|
||||
|
||||
trace := httptrace.ContextClientTrace(req.Context())
|
||||
trace := httptrace.ContextClientTrace(ctx)
|
||||
|
||||
// Header list size is ok. Write the headers.
|
||||
enumerateHeaders(func(name, value string) {
|
||||
|
@ -243,19 +258,19 @@ func EncodeHeaders(param EncodeHeadersParam, headerf func(name, value string)) (
|
|||
}
|
||||
})
|
||||
|
||||
res.HasBody = contentLength != 0
|
||||
res.HasBody = req.ActualContentLength != 0
|
||||
res.HasTrailers = trailers != ""
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// IsRequestGzip reports whether we should add an Accept-Encoding: gzip header
|
||||
// for a request.
|
||||
func IsRequestGzip(req *http.Request, disableCompression bool) bool {
|
||||
func IsRequestGzip(method string, header map[string][]string, disableCompression bool) bool {
|
||||
// TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
|
||||
if !disableCompression &&
|
||||
req.Header.Get("Accept-Encoding") == "" &&
|
||||
req.Header.Get("Range") == "" &&
|
||||
req.Method != "HEAD" {
|
||||
len(header["Accept-Encoding"]) == 0 &&
|
||||
len(header["Range"]) == 0 &&
|
||||
method != "HEAD" {
|
||||
// Request gzip only, not deflate. Deflate is ambiguous and
|
||||
// not as universally supported anyway.
|
||||
// See: https://zlib.net/zlib_faq.html#faq39
|
||||
|
@ -280,22 +295,22 @@ func IsRequestGzip(req *http.Request, disableCompression bool) bool {
|
|||
//
|
||||
// Certain headers are special-cased as okay but not transmitted later.
|
||||
// For example, we allow "Transfer-Encoding: chunked", but drop the header when encoding.
|
||||
func checkConnHeaders(req *http.Request) error {
|
||||
if v := req.Header.Get("Upgrade"); v != "" {
|
||||
return fmt.Errorf("invalid Upgrade request header: %q", req.Header["Upgrade"])
|
||||
func checkConnHeaders(h map[string][]string) error {
|
||||
if vv := h["Upgrade"]; len(vv) > 0 && (vv[0] != "" && vv[0] != "chunked") {
|
||||
return fmt.Errorf("invalid Upgrade request header: %q", vv)
|
||||
}
|
||||
if vv := req.Header["Transfer-Encoding"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && vv[0] != "chunked") {
|
||||
if vv := h["Transfer-Encoding"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && vv[0] != "chunked") {
|
||||
return fmt.Errorf("invalid Transfer-Encoding request header: %q", vv)
|
||||
}
|
||||
if vv := req.Header["Connection"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && !asciiEqualFold(vv[0], "close") && !asciiEqualFold(vv[0], "keep-alive")) {
|
||||
if vv := h["Connection"]; len(vv) > 0 && (len(vv) > 1 || vv[0] != "" && !asciiEqualFold(vv[0], "close") && !asciiEqualFold(vv[0], "keep-alive")) {
|
||||
return fmt.Errorf("invalid Connection request header: %q", vv)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func commaSeparatedTrailers(req *http.Request) (string, error) {
|
||||
keys := make([]string, 0, len(req.Trailer))
|
||||
for k := range req.Trailer {
|
||||
func commaSeparatedTrailers(trailer map[string][]string) (string, error) {
|
||||
keys := make([]string, 0, len(trailer))
|
||||
for k := range trailer {
|
||||
k = CanonicalHeader(k)
|
||||
switch k {
|
||||
case "Transfer-Encoding", "Trailer", "Content-Length":
|
||||
|
@ -310,19 +325,6 @@ func commaSeparatedTrailers(req *http.Request) (string, error) {
|
|||
return "", nil
|
||||
}
|
||||
|
||||
// ActualContentLength returns a sanitized version of
|
||||
// req.ContentLength, where 0 actually means zero (not unknown) and -1
|
||||
// means unknown.
|
||||
func ActualContentLength(req *http.Request) int64 {
|
||||
if req.Body == nil || req.Body == http.NoBody {
|
||||
return 0
|
||||
}
|
||||
if req.ContentLength != 0 {
|
||||
return req.ContentLength
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// validPseudoPath reports whether v is a valid :path pseudo-header
|
||||
// value. It must be either:
|
||||
//
|
||||
|
@ -340,7 +342,7 @@ func validPseudoPath(v string) bool {
|
|||
return (len(v) > 0 && v[0] == '/') || v == "*"
|
||||
}
|
||||
|
||||
func validateHeaders(hdrs http.Header) string {
|
||||
func validateHeaders(hdrs map[string][]string) string {
|
||||
for k, vv := range hdrs {
|
||||
if !httpguts.ValidHeaderFieldName(k) && k != ":protocol" {
|
||||
return fmt.Sprintf("name %q", k)
|
||||
|
@ -377,3 +379,89 @@ func shouldSendReqContentLength(method string, contentLength int64) bool {
|
|||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// ServerRequestParam is parameters to NewServerRequest.
|
||||
type ServerRequestParam struct {
|
||||
Method string
|
||||
Scheme, Authority, Path string
|
||||
Protocol string
|
||||
Header map[string][]string
|
||||
}
|
||||
|
||||
// ServerRequestResult is the result of NewServerRequest.
|
||||
type ServerRequestResult struct {
|
||||
// Various http.Request fields.
|
||||
URL *url.URL
|
||||
RequestURI string
|
||||
Trailer map[string][]string
|
||||
|
||||
NeedsContinue bool // client provided an "Expect: 100-continue" header
|
||||
|
||||
// If the request should be rejected, this is a short string suitable for passing
|
||||
// to the http2 package's CountError function.
|
||||
// It might be a bit odd to return errors this way rather than returing an error,
|
||||
// but this ensures we don't forget to include a CountError reason.
|
||||
InvalidReason string
|
||||
}
|
||||
|
||||
func NewServerRequest(rp ServerRequestParam) ServerRequestResult {
|
||||
needsContinue := httpguts.HeaderValuesContainsToken(rp.Header["Expect"], "100-continue")
|
||||
if needsContinue {
|
||||
delete(rp.Header, "Expect")
|
||||
}
|
||||
// Merge Cookie headers into one "; "-delimited value.
|
||||
if cookies := rp.Header["Cookie"]; len(cookies) > 1 {
|
||||
rp.Header["Cookie"] = []string{strings.Join(cookies, "; ")}
|
||||
}
|
||||
|
||||
// Setup Trailers
|
||||
var trailer map[string][]string
|
||||
for _, v := range rp.Header["Trailer"] {
|
||||
for _, key := range strings.Split(v, ",") {
|
||||
key = textproto.CanonicalMIMEHeaderKey(textproto.TrimString(key))
|
||||
switch key {
|
||||
case "Transfer-Encoding", "Trailer", "Content-Length":
|
||||
// Bogus. (copy of http1 rules)
|
||||
// Ignore.
|
||||
default:
|
||||
if trailer == nil {
|
||||
trailer = make(map[string][]string)
|
||||
}
|
||||
trailer[key] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
delete(rp.Header, "Trailer")
|
||||
|
||||
// "':authority' MUST NOT include the deprecated userinfo subcomponent
|
||||
// for "http" or "https" schemed URIs."
|
||||
// https://www.rfc-editor.org/rfc/rfc9113.html#section-8.3.1-2.3.8
|
||||
if strings.IndexByte(rp.Authority, '@') != -1 && (rp.Scheme == "http" || rp.Scheme == "https") {
|
||||
return ServerRequestResult{
|
||||
InvalidReason: "userinfo_in_authority",
|
||||
}
|
||||
}
|
||||
|
||||
var url_ *url.URL
|
||||
var requestURI string
|
||||
if rp.Method == "CONNECT" && rp.Protocol == "" {
|
||||
url_ = &url.URL{Host: rp.Authority}
|
||||
requestURI = rp.Authority // mimic HTTP/1 server behavior
|
||||
} else {
|
||||
var err error
|
||||
url_, err = url.ParseRequestURI(rp.Path)
|
||||
if err != nil {
|
||||
return ServerRequestResult{
|
||||
InvalidReason: "bad_path",
|
||||
}
|
||||
}
|
||||
requestURI = rp.Path
|
||||
}
|
||||
|
||||
return ServerRequestResult{
|
||||
URL: url_,
|
||||
NeedsContinue: needsContinue,
|
||||
RequestURI: requestURI,
|
||||
Trailer: trailer,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ func PublicSuffix(domain string) (publicSuffix string, icann bool) {
|
|||
s, suffix, icannNode, wildcard := domain, len(domain), false, false
|
||||
loop:
|
||||
for {
|
||||
dot := strings.LastIndex(s, ".")
|
||||
dot := strings.LastIndexByte(s, '.')
|
||||
if wildcard {
|
||||
icann = icannNode
|
||||
suffix = 1 + dot
|
||||
|
@ -129,7 +129,7 @@ loop:
|
|||
}
|
||||
if suffix == len(domain) {
|
||||
// If no rules match, the prevailing rule is "*".
|
||||
return domain[1+strings.LastIndex(domain, "."):], icann
|
||||
return domain[1+strings.LastIndexByte(domain, '.'):], icann
|
||||
}
|
||||
return domain[suffix:], icann
|
||||
}
|
||||
|
@ -178,26 +178,28 @@ func EffectiveTLDPlusOne(domain string) (string, error) {
|
|||
if domain[i] != '.' {
|
||||
return "", fmt.Errorf("publicsuffix: invalid public suffix %q for domain %q", suffix, domain)
|
||||
}
|
||||
return domain[1+strings.LastIndex(domain[:i], "."):], nil
|
||||
return domain[1+strings.LastIndexByte(domain[:i], '.'):], nil
|
||||
}
|
||||
|
||||
type uint32String string
|
||||
|
||||
func (u uint32String) get(i uint32) uint32 {
|
||||
off := i * 4
|
||||
return (uint32(u[off])<<24 |
|
||||
uint32(u[off+1])<<16 |
|
||||
uint32(u[off+2])<<8 |
|
||||
uint32(u[off+3]))
|
||||
u = u[off:] // help the compiler reduce bounds checks
|
||||
return uint32(u[3]) |
|
||||
uint32(u[2])<<8 |
|
||||
uint32(u[1])<<16 |
|
||||
uint32(u[0])<<24
|
||||
}
|
||||
|
||||
type uint40String string
|
||||
|
||||
func (u uint40String) get(i uint32) uint64 {
|
||||
off := uint64(i * (nodesBits / 8))
|
||||
return uint64(u[off])<<32 |
|
||||
uint64(u[off+1])<<24 |
|
||||
uint64(u[off+2])<<16 |
|
||||
uint64(u[off+3])<<8 |
|
||||
uint64(u[off+4])
|
||||
u = u[off:] // help the compiler reduce bounds checks
|
||||
return uint64(u[4]) |
|
||||
uint64(u[3])<<8 |
|
||||
uint64(u[2])<<16 |
|
||||
uint64(u[1])<<24 |
|
||||
uint64(u[0])<<32
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ codeberg.org/gruf/go-kv/format
|
|||
# codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f
|
||||
## explicit; go 1.21.3
|
||||
codeberg.org/gruf/go-list
|
||||
# codeberg.org/gruf/go-mangler v1.4.1
|
||||
# codeberg.org/gruf/go-mangler v1.4.3
|
||||
## explicit; go 1.19
|
||||
codeberg.org/gruf/go-mangler
|
||||
# codeberg.org/gruf/go-maps v1.0.4
|
||||
|
@ -63,8 +63,8 @@ codeberg.org/gruf/go-storage/disk
|
|||
codeberg.org/gruf/go-storage/internal
|
||||
codeberg.org/gruf/go-storage/memory
|
||||
codeberg.org/gruf/go-storage/s3
|
||||
# codeberg.org/gruf/go-structr v0.8.11
|
||||
## explicit; go 1.21
|
||||
# codeberg.org/gruf/go-structr v0.9.0
|
||||
## explicit; go 1.22
|
||||
codeberg.org/gruf/go-structr
|
||||
# codeberg.org/superseriousbusiness/activity v1.12.0-gts
|
||||
## explicit; go 1.21
|
||||
|
@ -1097,8 +1097,8 @@ golang.org/x/image/webp
|
|||
golang.org/x/mod/internal/lazyregexp
|
||||
golang.org/x/mod/module
|
||||
golang.org/x/mod/semver
|
||||
# golang.org/x/net v0.35.0
|
||||
## explicit; go 1.18
|
||||
# golang.org/x/net v0.36.0
|
||||
## explicit; go 1.23.0
|
||||
golang.org/x/net/bpf
|
||||
golang.org/x/net/context
|
||||
golang.org/x/net/html
|
||||
|
|
|
@ -31,6 +31,15 @@
|
|||
"@babel/highlight" "^7.24.2"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
"@babel/code-frame@^7.26.2":
|
||||
version "7.26.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85"
|
||||
integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==
|
||||
dependencies:
|
||||
"@babel/helper-validator-identifier" "^7.25.9"
|
||||
js-tokens "^4.0.0"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9":
|
||||
version "7.22.20"
|
||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.20.tgz#8df6e96661209623f1975d66c35ffca66f3306d0"
|
||||
|
@ -303,11 +312,21 @@
|
|||
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e"
|
||||
integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==
|
||||
|
||||
"@babel/helper-string-parser@^7.25.9":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c"
|
||||
integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==
|
||||
|
||||
"@babel/helper-validator-identifier@^7.22.20":
|
||||
version "7.22.20"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
|
||||
integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
|
||||
|
||||
"@babel/helper-validator-identifier@^7.25.9":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7"
|
||||
integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==
|
||||
|
||||
"@babel/helper-validator-option@^7.22.15":
|
||||
version "7.22.15"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040"
|
||||
|
@ -327,23 +346,13 @@
|
|||
"@babel/template" "^7.22.15"
|
||||
"@babel/types" "^7.22.19"
|
||||
|
||||
"@babel/helpers@^7.23.0":
|
||||
version "7.23.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.1.tgz#44e981e8ce2b9e99f8f0b703f3326a4636c16d15"
|
||||
integrity sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==
|
||||
"@babel/helpers@^7.23.0", "@babel/helpers@^7.24.4":
|
||||
version "7.26.10"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.10.tgz#6baea3cd62ec2d0c1068778d63cb1314f6637384"
|
||||
integrity sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==
|
||||
dependencies:
|
||||
"@babel/template" "^7.22.15"
|
||||
"@babel/traverse" "^7.23.0"
|
||||
"@babel/types" "^7.23.0"
|
||||
|
||||
"@babel/helpers@^7.24.4":
|
||||
version "7.24.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.4.tgz#dc00907fd0d95da74563c142ef4cd21f2cb856b6"
|
||||
integrity sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==
|
||||
dependencies:
|
||||
"@babel/template" "^7.24.0"
|
||||
"@babel/traverse" "^7.24.1"
|
||||
"@babel/types" "^7.24.0"
|
||||
"@babel/template" "^7.26.9"
|
||||
"@babel/types" "^7.26.10"
|
||||
|
||||
"@babel/highlight@^7.22.13":
|
||||
version "7.22.20"
|
||||
|
@ -374,6 +383,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.4.tgz#234487a110d89ad5a3ed4a8a566c36b9453e8c88"
|
||||
integrity sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==
|
||||
|
||||
"@babel/parser@^7.26.9":
|
||||
version "7.26.10"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.10.tgz#e9bdb82f14b97df6569b0b038edd436839c57749"
|
||||
integrity sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==
|
||||
dependencies:
|
||||
"@babel/types" "^7.26.10"
|
||||
|
||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.4":
|
||||
version "7.24.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.4.tgz#6125f0158543fb4edf1c22f322f3db67f21cb3e1"
|
||||
|
@ -1156,6 +1172,15 @@
|
|||
"@babel/parser" "^7.24.0"
|
||||
"@babel/types" "^7.24.0"
|
||||
|
||||
"@babel/template@^7.26.9":
|
||||
version "7.26.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.9.tgz#4577ad3ddf43d194528cff4e1fa6b232fa609bb2"
|
||||
integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.26.2"
|
||||
"@babel/parser" "^7.26.9"
|
||||
"@babel/types" "^7.26.9"
|
||||
|
||||
"@babel/traverse@^7.23.0":
|
||||
version "7.23.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8"
|
||||
|
@ -1206,6 +1231,14 @@
|
|||
"@babel/helper-validator-identifier" "^7.22.20"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@babel/types@^7.26.10", "@babel/types@^7.26.9":
|
||||
version "7.26.10"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.10.tgz#396382f6335bd4feb65741eacfc808218f859259"
|
||||
integrity sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==
|
||||
dependencies:
|
||||
"@babel/helper-string-parser" "^7.25.9"
|
||||
"@babel/helper-validator-identifier" "^7.25.9"
|
||||
|
||||
"@browserify/envify@^6.0.0":
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@browserify/envify/-/envify-6.0.0.tgz#28c6e0eae714aef403e08d826171d4f8048ccdc4"
|
||||
|
|
Loading…
Reference in New Issue