From 9cc2b04342158efa8240f95776f8d9f6ac594c73 Mon Sep 17 00:00:00 2001 From: ston1th Date: Thu, 17 Mar 2022 00:47:45 +0100 Subject: [PATCH] added disk quota --- .gitignore | 1 + Makefile | 2 +- TODO.txt | 6 +- cmd/cachefs/main.go | 6 +- go.mod | 3 +- go.sum | 7 +- pkg/fs/chunk.go | 12 +- pkg/fs/fs.go | 80 ++-- pkg/fs/metadata.go | 13 + pkg/fs/quota.go | 70 ++++ pkg/srv/interceptor.go | 8 +- pkg/srv/srv.go | 23 +- pkg/srv/templates.go | 2 +- pkg/srv/webdav.go | 8 +- scripts/install_openbsd.sh | 2 +- vendor/golang.org/x/exp/AUTHORS | 3 + vendor/golang.org/x/exp/CONTRIBUTORS | 3 + vendor/golang.org/x/exp/LICENSE | 27 ++ vendor/golang.org/x/exp/PATENTS | 22 ++ .../x/exp/constraints/constraints.go | 50 +++ vendor/golang.org/x/exp/maps/maps.go | 90 +++++ vendor/golang.org/x/exp/slices/slices.go | 213 +++++++++++ vendor/golang.org/x/exp/slices/sort.go | 95 +++++ vendor/golang.org/x/exp/slices/zsortfunc.go | 342 +++++++++++++++++ .../golang.org/x/exp/slices/zsortordered.go | 344 ++++++++++++++++++ vendor/modules.txt | 5 + 26 files changed, 1376 insertions(+), 61 deletions(-) create mode 100644 pkg/fs/quota.go create mode 100644 vendor/golang.org/x/exp/AUTHORS create mode 100644 vendor/golang.org/x/exp/CONTRIBUTORS create mode 100644 vendor/golang.org/x/exp/LICENSE create mode 100644 vendor/golang.org/x/exp/PATENTS create mode 100644 vendor/golang.org/x/exp/constraints/constraints.go create mode 100644 vendor/golang.org/x/exp/maps/maps.go create mode 100644 vendor/golang.org/x/exp/slices/slices.go create mode 100644 vendor/golang.org/x/exp/slices/sort.go create mode 100644 vendor/golang.org/x/exp/slices/zsortfunc.go create mode 100644 vendor/golang.org/x/exp/slices/zsortordered.go diff --git a/.gitignore b/.gitignore index 9633cdf..2c1e3d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /cachefs +debug.txt diff --git a/Makefile b/Makefile index 77def40..5c7cea3 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ clean: gofmt: $(CC) fmt ./... -codeqa: govet misspell staticcheck test +codeqa: govet misspell test govet: $(CC) vet ./... diff --git a/TODO.txt b/TODO.txt index c60034a..419aab5 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,4 +1,2 @@ -* add disk quota - * preload: dont load file if quota exceeded -* add LRU cache -* clear cache if file sizes differ and > 0 +* fix deadlock + * maybe dirEmpty? diff --git a/cmd/cachefs/main.go b/cmd/cachefs/main.go index bc5cd00..c3b7893 100644 --- a/cmd/cachefs/main.go +++ b/cmd/cachefs/main.go @@ -27,10 +27,13 @@ var ( metadata string listenHttp string listenWebdav string + quota int64 log logr.Logger ) +const gib = 1024 * 1024 * 1024 + func main() { klog.InitFlags(nil) flag.StringVar(&src, "src", "", "root directory (NFS share)") @@ -38,12 +41,13 @@ func main() { flag.StringVar(&metadata, "data", "", "path to metadata file") flag.StringVar(&listenHttp, "listen", "127.0.0.1:8080", "listen addr:port") flag.StringVar(&listenWebdav, "webdav", "", "listen addr:port for webdav") + flag.Int64Var("a, "quota", 1, "max disk usage quota in GiB") flag.Parse() log = klogr.New().WithName("main") log.Info("starting cachefs", "version", version) - filesystem, err := fs.NewFS(src, dst, metadata, klogr.New().WithName("fs")) + filesystem, err := fs.NewFS(quota*gib, src, dst, metadata, klogr.New().WithName("fs")) if err != nil { klog.Fatalf("init failed: %s", err) } diff --git a/go.mod b/go.mod index 437ed2a..da52458 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,10 @@ module cachefs -go 1.17 +go 1.18 require ( github.com/go-logr/logr v1.2.2 + golang.org/x/exp v0.0.0-20220314205449-43aec2f8a4e7 golang.org/x/net v0.0.0-20220225172249-27dd8689420f k8s.io/klog/v2 v2.40.1 ) diff --git a/go.sum b/go.sum index 4ba81c6..b35a7e6 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,9 @@ github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +golang.org/x/exp v0.0.0-20220314205449-43aec2f8a4e7 h1:jynE66seADJbyWMUdeOyVTvPtBZt7L6LJHupGwxPZRM= +golang.org/x/exp v0.0.0-20220314205449-43aec2f8a4e7/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= k8s.io/klog/v2 v2.40.1 h1:P4RRucWk/lFOlDdkAr3mc7iWFkgKrZY9qZMAgek06S4= k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= diff --git a/pkg/fs/chunk.go b/pkg/fs/chunk.go index bd85cac..ab03ab0 100644 --- a/pkg/fs/chunk.go +++ b/pkg/fs/chunk.go @@ -2,15 +2,15 @@ package fs -import "sort" +import ( + "golang.org/x/exp/slices" +) type Chunk [2]int64 type Chunks []Chunk -func (cs Chunks) Len() int { return len(cs) } -func (cs Chunks) Less(i, j int) bool { return cs[i][0] < cs[j][0] } -func (cs Chunks) Swap(i, j int) { cs[i], cs[j] = cs[j], cs[i] } +func (Chunks) Less(i, j Chunk) bool { return i[0] < j[0] } func (cs Chunks) Exists(off int64, n int, size int64) bool { end := off + int64(n) @@ -42,7 +42,7 @@ func (cs *Chunks) merge() { if len(c) < 2 { return } - sort.Sort(*cs) + slices.SortFunc(c, c.Less) for i := 0; i < len(c); i++ { if i+1 == len(c) { break @@ -51,7 +51,7 @@ func (cs *Chunks) merge() { if c[i+1][1] > c[i][1] { c[i][1] = c[i+1][1] } - c = append(c[:i+1], c[i+2:]...) + c = slices.Delete(c, i+1, i+2) i-- } } diff --git a/pkg/fs/fs.go b/pkg/fs/fs.go index 0f72c3d..0925933 100644 --- a/pkg/fs/fs.go +++ b/pkg/fs/fs.go @@ -18,6 +18,7 @@ import ( "time" "github.com/go-logr/logr" + "golang.org/x/exp/maps" ) type FS struct { @@ -29,12 +30,13 @@ type FS struct { mdf *os.File jenc *json.Encoder dc *DirCache - mm map[string]*Metadata - prem map[string]func() + q *Quota + md MetadataMap + pm map[string]func() cancel func() } -func NewFS(src, dst, metadata string, log logr.Logger) (fs *FS, err error) { +func NewFS(max int64, src, dst, metadata string, log logr.Logger) (fs *FS, err error) { if !filepath.IsAbs(src) { return nil, errors.New("src path is not absolute") } @@ -58,15 +60,20 @@ func NewFS(src, dst, metadata string, log logr.Logger) (fs *FS, err error) { dst: dst, mdf: mdf, dc: NewDirCache(), - mm: make(map[string]*Metadata), - prem: make(map[string]func()), + md: make(MetadataMap), + pm: make(map[string]func()), jenc: json.NewEncoder(mdf), } - err = json.NewDecoder(mdf).Decode(&fs.mm) + fs.q, err = NewQuota(max, fs, fs.log.WithName("quota")) + if err != nil { + return + } + err = json.NewDecoder(mdf).Decode(&fs.md) if err == io.EOF { err = nil } fs.initMetadata() + fs.q.Init() ctx, cancel := context.WithCancel(context.Background()) fs.cancel = cancel go fs.flusher(ctx) @@ -75,26 +82,25 @@ func NewFS(src, dst, metadata string, log logr.Logger) (fs *FS, err error) { func (fs *FS) initMetadata() { log := fs.log - for k, v := range fs.mm { + maps.DeleteFunc(fs.md, func(k string, v *Metadata) bool { _, err := fs.statDst(k) if errors.Is(err, os.ErrNotExist) { log.V(2).Info("removing not existing file from metadata", "file", k) - delete(fs.mm, k) - continue + return true } if len(v.Chunks) == 0 { log.V(2).Info("removing empty file", "file", k) err := fs.RemoveDst(k) if err != nil { log.Error(err, "error removing empty file", "file", k) - continue + return false } - delete(fs.mm, k) - continue + return true } v.fs = fs v.name = k - } + return false + }) } func (fs *FS) Stat(name string) (fi stdfs.FileInfo, err error) { @@ -126,7 +132,7 @@ func (fs *FS) statDst(name string) (fi stdfs.FileInfo, err error) { } func (fs *FS) CancelPreload(name string) { - if cancel, ok := fs.prem[name]; ok { + if cancel, ok := fs.pm[name]; ok { cancel() } } @@ -146,9 +152,9 @@ func (fs *FS) Preload(name string) { return } ctx, cancel := context.WithCancel(context.Background()) - fs.prem[name] = cancel + fs.pm[name] = cancel unlock := func() { - delete(fs.prem, name) + delete(fs.pm, name) f.md.UnlockPreload() } go f.Preload(ctx, unlock) @@ -199,15 +205,17 @@ func (fs *FS) Open(name string) (f http.File, err error) { offline = true log.V(2).Info("dir offline mode", "path", dp) } - return &Dir{f: sf, dc: fs.dc}, err + f = &Dir{f: sf, dc: fs.dc} + return } md := fs.metadata(name, sfi.Size()) - return &File{ + f = &File{ log: log, f: sf, md: md, offline: offline, - }, nil + } + return } func (fs *FS) openCacheFile(name string, size int64) (df *os.File, err error) { @@ -248,19 +256,29 @@ func (fs *FS) openCacheFile(name string, size int64) (df *os.File, err error) { func (fs *FS) metadata(name string, size int64) (md *Metadata) { fs.mu.Lock() + fs.log.Info("fs.mu.Lock()") defer fs.mu.Unlock() - if md, ok := fs.mm[name]; ok { + defer fs.log.Info("fs.mu.Unlock()") + if md, ok := fs.md[name]; ok { + if size > 0 && md.Size != size { + log := fs.log.WithValues("file", name) + log.V(2).Info("source file changed: deleting cache file", "src", size, "dst", md.Size) + err := md.Delete() + if err != nil { + log.Error(err, "error deleting cache file") + } + } return md } md = &Metadata{Size: size, fs: fs, name: name} - fs.mm[name] = md + fs.md[name] = md return } func (fs *FS) CacheStatus(name string) int { fs.mu.RLock() defer fs.mu.RUnlock() - if md, ok := fs.mm[name]; ok { + if md, ok := fs.md[name]; ok { return int(math.Round(float64(md.Chunks.Size()) / float64(md.Size) * 100)) } return -1 @@ -281,12 +299,11 @@ func (fs *FS) flushMetadata() { log := fs.log fs.mu.Lock() defer fs.mu.Unlock() - mm := make(map[string]*Metadata) - for k, v := range fs.mm { - if v.f != nil || len(v.Chunks) > 0 { - mm[k] = v - } - } + log.V(2).Info("flushing metadata to disk") + md := maps.Clone(fs.md) + maps.DeleteFunc(md, func(_ string, v *Metadata) bool { + return len(v.Chunks) == 0 + }) _, err := fs.mdf.Seek(0, io.SeekStart) if err != nil { log.Error(err, "failure seeking metadata file") @@ -297,7 +314,7 @@ func (fs *FS) flushMetadata() { log.Error(err, "failure truncating metadata file") return } - err = fs.jenc.Encode(mm) + err = fs.jenc.Encode(md) if err != nil { log.Error(err, "failure flushing metadata file") } @@ -305,7 +322,10 @@ func (fs *FS) flushMetadata() { func (fs *FS) Close() { fs.cancel() - for _, md := range fs.mm { + for _, cancel := range fs.pm { + cancel() + } + for _, md := range fs.md { md.f.Sync() md.f.Close() } diff --git a/pkg/fs/metadata.go b/pkg/fs/metadata.go index b2d434d..b450e68 100644 --- a/pkg/fs/metadata.go +++ b/pkg/fs/metadata.go @@ -5,14 +5,23 @@ package fs import ( "os" "sync" + "sync/atomic" + "time" ) +func now() int64 { + return time.Now().UTC().Unix() +} + +type MetadataMap map[string]*Metadata + type Metadata struct { mu sync.RWMutex `json:"-"` fs *FS `json:"-"` f *os.File `json:"-"` name string `json:"-"` Size int64 `json:"s"` + Atime int64 `json:"a"` Chunks Chunks `json:"c"` preload bool `json:"-"` } @@ -23,6 +32,7 @@ func (md *Metadata) Delete() error { md.Chunks = Chunks{} md.f.Close() md.f = nil + atomic.StoreInt64(&md.Atime, now()) return md.fs.RemoveDst(md.name) } @@ -45,6 +55,7 @@ func (md *Metadata) ReadAt(p []byte, pos int64) (int, error) { return 0, err } } + atomic.StoreInt64(&md.Atime, now()) return md.f.ReadAt(p, pos) } @@ -55,6 +66,8 @@ func (md *Metadata) WriteAt(data []byte, pos int64) (int, error) { return 0, err } } + atomic.StoreInt64(&md.Atime, now()) + md.fs.q.Add(len(data)) return md.f.WriteAt(data, pos) } diff --git a/pkg/fs/quota.go b/pkg/fs/quota.go new file mode 100644 index 0000000..c8f7bfa --- /dev/null +++ b/pkg/fs/quota.go @@ -0,0 +1,70 @@ +// Copyright (C) 2022 Marius Schellenberger + +package fs + +import ( + "errors" + "math" + "sync/atomic" + + "github.com/go-logr/logr" +) + +var InvalidMaxQuota = errors.New("invalid max quota") + +type Quota struct { + log logr.Logger + fs *FS + max int64 + cur int64 +} + +func NewQuota(max int64, fs *FS, log logr.Logger) (q *Quota, err error) { + if max <= 0 { + err = InvalidMaxQuota + return + } + q = &Quota{log: log, fs: fs, max: max} + return +} + +func (q *Quota) Init() { + for _, v := range q.fs.md { + q.cur += v.Chunks.Size() + } + q.log.Info("quota usage", "current", q.cur, "max", q.max) +} + +func (q *Quota) cleanup() { + if !q.fs.mu.TryLock() { + return + } + q.log.Info("!q.fs.mu.TryLock()") + q.log.Info("quota usage", "current", q.cur, "max", q.max) + defer q.fs.mu.Unlock() + defer q.log.Info("q.fs.mu.Unlock()") + atime := int64(math.MaxInt64) + var m *Metadata + for _, v := range q.fs.md { + if v.Atime != 0 && v.f != nil && v.Atime < atime { + atime = v.Atime + m = v + } + } + if m != nil { + log := q.log.WithValues("file", m.name) + log.Info("deleting oldest file") + err := m.Delete() + if err != nil { + q.log.Error(err, "error deleting oldest file") + return + } + atomic.AddInt64(&q.cur, -m.Size) + } +} + +func (q *Quota) Add(n int) { + if q.max < atomic.AddInt64(&q.cur, int64(n)) { + q.cleanup() + } +} diff --git a/pkg/srv/interceptor.go b/pkg/srv/interceptor.go index 104d366..ec50cdf 100644 --- a/pkg/srv/interceptor.go +++ b/pkg/srv/interceptor.go @@ -10,6 +10,8 @@ import ( "strings" "cachefs/pkg/fs" + + "golang.org/x/exp/slices" ) type statusInterceptor struct { @@ -51,9 +53,7 @@ type file struct { type files []file -func (f files) Len() int { return len(f) } -func (f files) Less(i, j int) bool { return f[i].Name < f[j].Name } -func (f files) Swap(i, j int) { f[i], f[j] = f[j], f[i] } +func (files) Less(i, j file) bool { return i.Name < j.Name } type responseInterceptor struct { buf bytes.Buffer @@ -98,7 +98,7 @@ func (r *responseInterceptor) GetPaths(path string, fs *fs.FS) (dir dirContents, } } sort.Strings(dir.Dirs) - sort.Sort(dir.Files) + slices.SortFunc(dir.Files, dir.Files.Less) return } diff --git a/pkg/srv/srv.go b/pkg/srv/srv.go index 235818d..2b0c180 100644 --- a/pkg/srv/srv.go +++ b/pkg/srv/srv.go @@ -7,7 +7,9 @@ import ( "io" stdfs "io/fs" "net/http" + "os" "path" + "runtime" "strings" "cachefs/pkg/fs" @@ -15,6 +17,21 @@ import ( "github.com/go-logr/logr" ) +func PrintStack() { + os.Stderr.Write(Stack()) +} + +func Stack() []byte { + buf := make([]byte, 1024) + for { + n := runtime.Stack(buf, true) + if n < len(buf) { + return buf[:n] + } + buf = make([]byte, 2*len(buf)) + } +} + type FileServer struct { log logr.Logger fs *fs.FS @@ -51,13 +68,17 @@ func (fs *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { r.URL.Path = upath } p := path.Clean(upath) + option := r.FormValue("o") + if option == "debug" { + PrintStack() + } d, _, err := fs.fs.StatWithOffline(p) if err != nil { msg, code := toHTTPError(err) http.Error(w, msg, code) return } - option := r.FormValue("o") + //option := r.FormValue("o") if option == "v" { err = video.Execute(w, r.URL.Path) if err != nil { diff --git a/pkg/srv/templates.go b/pkg/srv/templates.go index 382788e..f97a229 100644 --- a/pkg/srv/templates.go +++ b/pkg/srv/templates.go @@ -30,7 +30,7 @@ var ( {{end -}} {{range $s := .Files -}} {{$s.Name}} - [v] [n] [p] [s] {{if ge $s.Status 0}}{{$s.Status}}%{{end}} + [v] [n] [p] [s] {{if ne $s.Status -1}}{{$s.Status}}%{{end}} {{end -}} diff --git a/pkg/srv/webdav.go b/pkg/srv/webdav.go index 250e6cc..405ceb4 100644 --- a/pkg/srv/webdav.go +++ b/pkg/srv/webdav.go @@ -9,6 +9,7 @@ import ( "cachefs/pkg/fs" "github.com/go-logr/logr" + "golang.org/x/exp/slices" "golang.org/x/net/webdav" ) @@ -65,10 +66,5 @@ func skipDavLog(path string) bool { if i > 0 { path = path[i+1:] } - for _, v := range skipDavFiles { - if path == v { - return true - } - } - return false + return slices.Contains(skipDavFiles, path) } diff --git a/scripts/install_openbsd.sh b/scripts/install_openbsd.sh index cc22dec..fa14751 100644 --- a/scripts/install_openbsd.sh +++ b/scripts/install_openbsd.sh @@ -11,7 +11,7 @@ cat <<"EOF">/etc/rc.d/cachefs #!/bin/sh daemon="/usr/local/sbin/cachefs" -daemon_flags="-src /mnt/nfs -dst /mnt/cache -data /var/cachefs/md.json -listen :9090 -webdav :9091 -v 2" +daemon_flags="-src /mnt/nfs -dst /mnt/cache -data /var/cachefs/md.json -listen :9090 -webdav :9091 -quota 10 -v 2" daemon_logger="daemon.info" daemon_user="_cachefs" diff --git a/vendor/golang.org/x/exp/AUTHORS b/vendor/golang.org/x/exp/AUTHORS new file mode 100644 index 0000000..15167cd --- /dev/null +++ b/vendor/golang.org/x/exp/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/vendor/golang.org/x/exp/CONTRIBUTORS b/vendor/golang.org/x/exp/CONTRIBUTORS new file mode 100644 index 0000000..1c4577e --- /dev/null +++ b/vendor/golang.org/x/exp/CONTRIBUTORS @@ -0,0 +1,3 @@ +# This source code was written by the Go contributors. +# The master list of contributors is in the main Go distribution, +# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/vendor/golang.org/x/exp/LICENSE b/vendor/golang.org/x/exp/LICENSE new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/vendor/golang.org/x/exp/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/exp/PATENTS b/vendor/golang.org/x/exp/PATENTS new file mode 100644 index 0000000..7330990 --- /dev/null +++ b/vendor/golang.org/x/exp/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/exp/constraints/constraints.go b/vendor/golang.org/x/exp/constraints/constraints.go new file mode 100644 index 0000000..2c033df --- /dev/null +++ b/vendor/golang.org/x/exp/constraints/constraints.go @@ -0,0 +1,50 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package constraints defines a set of useful constraints to be used +// with type parameters. +package constraints + +// Signed is a constraint that permits any signed integer type. +// If future releases of Go add new predeclared signed integer types, +// this constraint will be modified to include them. +type Signed interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 +} + +// Unsigned is a constraint that permits any unsigned integer type. +// If future releases of Go add new predeclared unsigned integer types, +// this constraint will be modified to include them. +type Unsigned interface { + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr +} + +// Integer is a constraint that permits any integer type. +// If future releases of Go add new predeclared integer types, +// this constraint will be modified to include them. +type Integer interface { + Signed | Unsigned +} + +// Float is a constraint that permits any floating-point type. +// If future releases of Go add new predeclared floating-point types, +// this constraint will be modified to include them. +type Float interface { + ~float32 | ~float64 +} + +// Complex is a constraint that permits any complex numeric type. +// If future releases of Go add new predeclared complex numeric types, +// this constraint will be modified to include them. +type Complex interface { + ~complex64 | ~complex128 +} + +// Ordered is a constraint that permits any ordered type: any type +// that supports the operators < <= >= >. +// If future releases of Go add new ordered types, +// this constraint will be modified to include them. +type Ordered interface { + Integer | Float | ~string +} diff --git a/vendor/golang.org/x/exp/maps/maps.go b/vendor/golang.org/x/exp/maps/maps.go new file mode 100644 index 0000000..fc27cd1 --- /dev/null +++ b/vendor/golang.org/x/exp/maps/maps.go @@ -0,0 +1,90 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package maps defines various functions useful with maps of any type. +package maps + +// Keys returns the keys of the map m. +// The keys will be in an indeterminate order. +func Keys[M ~map[K]V, K comparable, V any](m M) []K { + r := make([]K, 0, len(m)) + for k := range m { + r = append(r, k) + } + return r +} + +// Values returns the values of the map m. +// The values will be in an indeterminate order. +func Values[M ~map[K]V, K comparable, V any](m M) []V { + r := make([]V, 0, len(m)) + for _, v := range m { + r = append(r, v) + } + return r +} + +// Equal reports whether two maps contain the same key/value pairs. +// Values are compared using ==. +func Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool { + if len(m1) != len(m2) { + return false + } + for k, v1 := range m1 { + if v2, ok := m2[k]; !ok || v1 != v2 { + return false + } + } + return true +} + +// EqualFunc is like Equal, but compares values using eq. +// Keys are still compared with ==. +func EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M2, eq func(V1, V2) bool) bool { + if len(m1) != len(m2) { + return false + } + for k, v1 := range m1 { + if v2, ok := m2[k]; !ok || !eq(v1, v2) { + return false + } + } + return true +} + +// Clear removes all entries from m, leaving it empty. +func Clear[M ~map[K]V, K comparable, V any](m M) { + for k := range m { + delete(m, k) + } +} + +// Clone returns a copy of m. This is a shallow clone: +// the new keys and values are set using ordinary assignment. +func Clone[M ~map[K]V, K comparable, V any](m M) M { + r := make(M, len(m)) + for k, v := range m { + r[k] = v + } + return r +} + +// Copy copies all key/value pairs in src adding them to dst. +// When a key in src is already present in dst, +// the value in dst will be overwritten by the value associated +// with the key in src. +func Copy[M ~map[K]V, K comparable, V any](dst, src M) { + for k, v := range src { + dst[k] = v + } +} + +// DeleteFunc deletes any key/value pairs from m for which del returns true. +func DeleteFunc[M ~map[K]V, K comparable, V any](m M, del func(K, V) bool) { + for k, v := range m { + if del(k, v) { + delete(m, k) + } + } +} diff --git a/vendor/golang.org/x/exp/slices/slices.go b/vendor/golang.org/x/exp/slices/slices.go new file mode 100644 index 0000000..df78daf --- /dev/null +++ b/vendor/golang.org/x/exp/slices/slices.go @@ -0,0 +1,213 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package slices defines various functions useful with slices of any type. +// Unless otherwise specified, these functions all apply to the elements +// of a slice at index 0 <= i < len(s). +package slices + +import "golang.org/x/exp/constraints" + +// Equal reports whether two slices are equal: the same length and all +// elements equal. If the lengths are different, Equal returns false. +// Otherwise, the elements are compared in increasing index order, and the +// comparison stops at the first unequal pair. +// Floating point NaNs are not considered equal. +func Equal[E comparable](s1, s2 []E) bool { + if len(s1) != len(s2) { + return false + } + for i := range s1 { + if s1[i] != s2[i] { + return false + } + } + return true +} + +// EqualFunc reports whether two slices are equal using a comparison +// function on each pair of elements. If the lengths are different, +// EqualFunc returns false. Otherwise, the elements are compared in +// increasing index order, and the comparison stops at the first index +// for which eq returns false. +func EqualFunc[E1, E2 any](s1 []E1, s2 []E2, eq func(E1, E2) bool) bool { + if len(s1) != len(s2) { + return false + } + for i, v1 := range s1 { + v2 := s2[i] + if !eq(v1, v2) { + return false + } + } + return true +} + +// Compare compares the elements of s1 and s2. +// The elements are compared sequentially, starting at index 0, +// until one element is not equal to the other. +// The result of comparing the first non-matching elements is returned. +// If both slices are equal until one of them ends, the shorter slice is +// considered less than the longer one. +// The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2. +// Comparisons involving floating point NaNs are ignored. +func Compare[E constraints.Ordered](s1, s2 []E) int { + s2len := len(s2) + for i, v1 := range s1 { + if i >= s2len { + return +1 + } + v2 := s2[i] + switch { + case v1 < v2: + return -1 + case v1 > v2: + return +1 + } + } + if len(s1) < s2len { + return -1 + } + return 0 +} + +// CompareFunc is like Compare but uses a comparison function +// on each pair of elements. The elements are compared in increasing +// index order, and the comparisons stop after the first time cmp +// returns non-zero. +// The result is the first non-zero result of cmp; if cmp always +// returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2), +// and +1 if len(s1) > len(s2). +func CompareFunc[E1, E2 any](s1 []E1, s2 []E2, cmp func(E1, E2) int) int { + s2len := len(s2) + for i, v1 := range s1 { + if i >= s2len { + return +1 + } + v2 := s2[i] + if c := cmp(v1, v2); c != 0 { + return c + } + } + if len(s1) < s2len { + return -1 + } + return 0 +} + +// Index returns the index of the first occurrence of v in s, +// or -1 if not present. +func Index[E comparable](s []E, v E) int { + for i, vs := range s { + if v == vs { + return i + } + } + return -1 +} + +// IndexFunc returns the first index i satisfying f(s[i]), +// or -1 if none do. +func IndexFunc[E any](s []E, f func(E) bool) int { + for i, v := range s { + if f(v) { + return i + } + } + return -1 +} + +// Contains reports whether v is present in s. +func Contains[E comparable](s []E, v E) bool { + return Index(s, v) >= 0 +} + +// Insert inserts the values v... into s at index i, +// returning the modified slice. +// In the returned slice r, r[i] == v[0]. +// Insert panics if i is out of range. +// This function is O(len(s) + len(v)). +func Insert[S ~[]E, E any](s S, i int, v ...E) S { + tot := len(s) + len(v) + if tot <= cap(s) { + s2 := s[:tot] + copy(s2[i+len(v):], s[i:]) + copy(s2[i:], v) + return s2 + } + s2 := make(S, tot) + copy(s2, s[:i]) + copy(s2[i:], v) + copy(s2[i+len(v):], s[i:]) + return s2 +} + +// Delete removes the elements s[i:j] from s, returning the modified slice. +// Delete panics if s[i:j] is not a valid slice of s. +// Delete modifies the contents of the slice s; it does not create a new slice. +// Delete is O(len(s)-(j-i)), so if many items must be deleted, it is better to +// make a single call deleting them all together than to delete one at a time. +func Delete[S ~[]E, E any](s S, i, j int) S { + return append(s[:i], s[j:]...) +} + +// Clone returns a copy of the slice. +// The elements are copied using assignment, so this is a shallow clone. +func Clone[S ~[]E, E any](s S) S { + // Preserve nil in case it matters. + if s == nil { + return nil + } + return append(S([]E{}), s...) +} + +// Compact replaces consecutive runs of equal elements with a single copy. +// This is like the uniq command found on Unix. +// Compact modifies the contents of the slice s; it does not create a new slice. +func Compact[S ~[]E, E comparable](s S) S { + if len(s) == 0 { + return s + } + i := 1 + last := s[0] + for _, v := range s[1:] { + if v != last { + s[i] = v + i++ + last = v + } + } + return s[:i] +} + +// CompactFunc is like Compact but uses a comparison function. +func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S { + if len(s) == 0 { + return s + } + i := 1 + last := s[0] + for _, v := range s[1:] { + if !eq(v, last) { + s[i] = v + i++ + last = v + } + } + return s[:i] +} + +// Grow increases the slice's capacity, if necessary, to guarantee space for +// another n elements. After Grow(n), at least n elements can be appended +// to the slice without another allocation. Grow may modify elements of the +// slice between the length and the capacity. If n is negative or too large to +// allocate the memory, Grow panics. +func Grow[S ~[]E, E any](s S, n int) S { + return append(s, make(S, n)...)[:len(s)] +} + +// Clip removes unused capacity from the slice, returning s[:len(s):len(s)]. +func Clip[S ~[]E, E any](s S) S { + return s[:len(s):len(s)] +} diff --git a/vendor/golang.org/x/exp/slices/sort.go b/vendor/golang.org/x/exp/slices/sort.go new file mode 100644 index 0000000..ed9f41a --- /dev/null +++ b/vendor/golang.org/x/exp/slices/sort.go @@ -0,0 +1,95 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices + +import "golang.org/x/exp/constraints" + +// Sort sorts a slice of any ordered type in ascending order. +func Sort[Elem constraints.Ordered](x []Elem) { + n := len(x) + quickSortOrdered(x, 0, n, maxDepth(n)) +} + +// Sort sorts the slice x in ascending order as determined by the less function. +// This sort is not guaranteed to be stable. +func SortFunc[Elem any](x []Elem, less func(a, b Elem) bool) { + n := len(x) + quickSortLessFunc(x, 0, n, maxDepth(n), less) +} + +// SortStable sorts the slice x while keeping the original order of equal +// elements, using less to compare elements. +func SortStableFunc[Elem any](x []Elem, less func(a, b Elem) bool) { + stableLessFunc(x, len(x), less) +} + +// IsSorted reports whether x is sorted in ascending order. +func IsSorted[Elem constraints.Ordered](x []Elem) bool { + for i := len(x) - 1; i > 0; i-- { + if x[i] < x[i-1] { + return false + } + } + return true +} + +// IsSortedFunc reports whether x is sorted in ascending order, with less as the +// comparison function. +func IsSortedFunc[Elem any](x []Elem, less func(a, b Elem) bool) bool { + for i := len(x) - 1; i > 0; i-- { + if less(x[i], x[i-1]) { + return false + } + } + return true +} + +// BinarySearch searches for target in a sorted slice and returns the smallest +// index at which target is found. If the target is not found, the index at +// which it could be inserted into the slice is returned; therefore, if the +// intention is to find target itself a separate check for equality with the +// element at the returned index is required. +func BinarySearch[Elem constraints.Ordered](x []Elem, target Elem) int { + return search(len(x), func(i int) bool { return x[i] >= target }) +} + +// BinarySearchFunc uses binary search to find and return the smallest index i +// in [0, n) at which ok(i) is true, assuming that on the range [0, n), +// ok(i) == true implies ok(i+1) == true. That is, BinarySearchFunc requires +// that ok is false for some (possibly empty) prefix of the input range [0, n) +// and then true for the (possibly empty) remainder; BinarySearchFunc returns +// the first true index. If there is no such index, BinarySearchFunc returns n. +// (Note that the "not found" return value is not -1 as in, for instance, +// strings.Index.) Search calls ok(i) only for i in the range [0, n). +func BinarySearchFunc[Elem any](x []Elem, ok func(Elem) bool) int { + return search(len(x), func(i int) bool { return ok(x[i]) }) +} + +// maxDepth returns a threshold at which quicksort should switch +// to heapsort. It returns 2*ceil(lg(n+1)). +func maxDepth(n int) int { + var depth int + for i := n; i > 0; i >>= 1 { + depth++ + } + return depth * 2 +} + +func search(n int, f func(int) bool) int { + // Define f(-1) == false and f(n) == true. + // Invariant: f(i-1) == false, f(j) == true. + i, j := 0, n + for i < j { + h := int(uint(i+j) >> 1) // avoid overflow when computing h + // i ≤ h < j + if !f(h) { + i = h + 1 // preserves f(i-1) == false + } else { + j = h // preserves f(j) == true + } + } + // i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i. + return i +} diff --git a/vendor/golang.org/x/exp/slices/zsortfunc.go b/vendor/golang.org/x/exp/slices/zsortfunc.go new file mode 100644 index 0000000..82f156f --- /dev/null +++ b/vendor/golang.org/x/exp/slices/zsortfunc.go @@ -0,0 +1,342 @@ +// Code generated by gen_sort_variants.go; DO NOT EDIT. + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices + +// insertionSortLessFunc sorts data[a:b] using insertion sort. +func insertionSortLessFunc[Elem any](data []Elem, a, b int, less func(a, b Elem) bool) { + for i := a + 1; i < b; i++ { + for j := i; j > a && less(data[j], data[j-1]); j-- { + data[j], data[j-1] = data[j-1], data[j] + } + } +} + +// siftDownLessFunc implements the heap property on data[lo:hi]. +// first is an offset into the array where the root of the heap lies. +func siftDownLessFunc[Elem any](data []Elem, lo, hi, first int, less func(a, b Elem) bool) { + root := lo + for { + child := 2*root + 1 + if child >= hi { + break + } + if child+1 < hi && less(data[first+child], data[first+child+1]) { + child++ + } + if !less(data[first+root], data[first+child]) { + return + } + data[first+root], data[first+child] = data[first+child], data[first+root] + root = child + } +} + +func heapSortLessFunc[Elem any](data []Elem, a, b int, less func(a, b Elem) bool) { + first := a + lo := 0 + hi := b - a + + // Build heap with greatest element at top. + for i := (hi - 1) / 2; i >= 0; i-- { + siftDownLessFunc(data, i, hi, first, less) + } + + // Pop elements, largest first, into end of data. + for i := hi - 1; i >= 0; i-- { + data[first], data[first+i] = data[first+i], data[first] + siftDownLessFunc(data, lo, i, first, less) + } +} + +// Quicksort, loosely following Bentley and McIlroy, +// "Engineering a Sort Function" SP&E November 1993. + +// medianOfThreeLessFunc moves the median of the three values data[m0], data[m1], data[m2] into data[m1]. +func medianOfThreeLessFunc[Elem any](data []Elem, m1, m0, m2 int, less func(a, b Elem) bool) { + // sort 3 elements + if less(data[m1], data[m0]) { + data[m1], data[m0] = data[m0], data[m1] + } + // data[m0] <= data[m1] + if less(data[m2], data[m1]) { + data[m2], data[m1] = data[m1], data[m2] + // data[m0] <= data[m2] && data[m1] < data[m2] + if less(data[m1], data[m0]) { + data[m1], data[m0] = data[m0], data[m1] + } + } + // now data[m0] <= data[m1] <= data[m2] +} + +func swapRangeLessFunc[Elem any](data []Elem, a, b, n int, less func(a, b Elem) bool) { + for i := 0; i < n; i++ { + data[a+i], data[b+i] = data[b+i], data[a+i] + } +} + +func doPivotLessFunc[Elem any](data []Elem, lo, hi int, less func(a, b Elem) bool) (midlo, midhi int) { + m := int(uint(lo+hi) >> 1) // Written like this to avoid integer overflow. + if hi-lo > 40 { + // Tukey's "Ninther" median of three medians of three. + s := (hi - lo) / 8 + medianOfThreeLessFunc(data, lo, lo+s, lo+2*s, less) + medianOfThreeLessFunc(data, m, m-s, m+s, less) + medianOfThreeLessFunc(data, hi-1, hi-1-s, hi-1-2*s, less) + } + medianOfThreeLessFunc(data, lo, m, hi-1, less) + + // Invariants are: + // data[lo] = pivot (set up by ChoosePivot) + // data[lo < i < a] < pivot + // data[a <= i < b] <= pivot + // data[b <= i < c] unexamined + // data[c <= i < hi-1] > pivot + // data[hi-1] >= pivot + pivot := lo + a, c := lo+1, hi-1 + + for ; a < c && less(data[a], data[pivot]); a++ { + } + b := a + for { + for ; b < c && !less(data[pivot], data[b]); b++ { // data[b] <= pivot + } + for ; b < c && less(data[pivot], data[c-1]); c-- { // data[c-1] > pivot + } + if b >= c { + break + } + // data[b] > pivot; data[c-1] <= pivot + data[b], data[c-1] = data[c-1], data[b] + b++ + c-- + } + // If hi-c<3 then there are duplicates (by property of median of nine). + // Let's be a bit more conservative, and set border to 5. + protect := hi-c < 5 + if !protect && hi-c < (hi-lo)/4 { + // Lets test some points for equality to pivot + dups := 0 + if !less(data[pivot], data[hi-1]) { // data[hi-1] = pivot + data[c], data[hi-1] = data[hi-1], data[c] + c++ + dups++ + } + if !less(data[b-1], data[pivot]) { // data[b-1] = pivot + b-- + dups++ + } + // m-lo = (hi-lo)/2 > 6 + // b-lo > (hi-lo)*3/4-1 > 8 + // ==> m < b ==> data[m] <= pivot + if !less(data[m], data[pivot]) { // data[m] = pivot + data[m], data[b-1] = data[b-1], data[m] + b-- + dups++ + } + // if at least 2 points are equal to pivot, assume skewed distribution + protect = dups > 1 + } + if protect { + // Protect against a lot of duplicates + // Add invariant: + // data[a <= i < b] unexamined + // data[b <= i < c] = pivot + for { + for ; a < b && !less(data[b-1], data[pivot]); b-- { // data[b] == pivot + } + for ; a < b && less(data[a], data[pivot]); a++ { // data[a] < pivot + } + if a >= b { + break + } + // data[a] == pivot; data[b-1] < pivot + data[a], data[b-1] = data[b-1], data[a] + a++ + b-- + } + } + // Swap pivot into middle + data[pivot], data[b-1] = data[b-1], data[pivot] + return b - 1, c +} + +func quickSortLessFunc[Elem any](data []Elem, a, b, maxDepth int, less func(a, b Elem) bool) { + for b-a > 12 { // Use ShellSort for slices <= 12 elements + if maxDepth == 0 { + heapSortLessFunc(data, a, b, less) + return + } + maxDepth-- + mlo, mhi := doPivotLessFunc(data, a, b, less) + // Avoiding recursion on the larger subproblem guarantees + // a stack depth of at most lg(b-a). + if mlo-a < b-mhi { + quickSortLessFunc(data, a, mlo, maxDepth, less) + a = mhi // i.e., quickSortLessFunc(data, mhi, b) + } else { + quickSortLessFunc(data, mhi, b, maxDepth, less) + b = mlo // i.e., quickSortLessFunc(data, a, mlo) + } + } + if b-a > 1 { + // Do ShellSort pass with gap 6 + // It could be written in this simplified form cause b-a <= 12 + for i := a + 6; i < b; i++ { + if less(data[i], data[i-6]) { + data[i], data[i-6] = data[i-6], data[i] + } + } + insertionSortLessFunc(data, a, b, less) + } +} + +func stableLessFunc[Elem any](data []Elem, n int, less func(a, b Elem) bool) { + blockSize := 20 // must be > 0 + a, b := 0, blockSize + for b <= n { + insertionSortLessFunc(data, a, b, less) + a = b + b += blockSize + } + insertionSortLessFunc(data, a, n, less) + + for blockSize < n { + a, b = 0, 2*blockSize + for b <= n { + symMergeLessFunc(data, a, a+blockSize, b, less) + a = b + b += 2 * blockSize + } + if m := a + blockSize; m < n { + symMergeLessFunc(data, a, m, n, less) + } + blockSize *= 2 + } +} + +// symMergeLessFunc merges the two sorted subsequences data[a:m] and data[m:b] using +// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum +// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz +// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in +// Computer Science, pages 714-723. Springer, 2004. +// +// Let M = m-a and N = b-n. Wolog M < N. +// The recursion depth is bound by ceil(log(N+M)). +// The algorithm needs O(M*log(N/M + 1)) calls to data.Less. +// The algorithm needs O((M+N)*log(M)) calls to data.Swap. +// +// The paper gives O((M+N)*log(M)) as the number of assignments assuming a +// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation +// in the paper carries through for Swap operations, especially as the block +// swapping rotate uses only O(M+N) Swaps. +// +// symMerge assumes non-degenerate arguments: a < m && m < b. +// Having the caller check this condition eliminates many leaf recursion calls, +// which improves performance. +func symMergeLessFunc[Elem any](data []Elem, a, m, b int, less func(a, b Elem) bool) { + // Avoid unnecessary recursions of symMerge + // by direct insertion of data[a] into data[m:b] + // if data[a:m] only contains one element. + if m-a == 1 { + // Use binary search to find the lowest index i + // such that data[i] >= data[a] for m <= i < b. + // Exit the search loop with i == b in case no such index exists. + i := m + j := b + for i < j { + h := int(uint(i+j) >> 1) + if less(data[h], data[a]) { + i = h + 1 + } else { + j = h + } + } + // Swap values until data[a] reaches the position before i. + for k := a; k < i-1; k++ { + data[k], data[k+1] = data[k+1], data[k] + } + return + } + + // Avoid unnecessary recursions of symMerge + // by direct insertion of data[m] into data[a:m] + // if data[m:b] only contains one element. + if b-m == 1 { + // Use binary search to find the lowest index i + // such that data[i] > data[m] for a <= i < m. + // Exit the search loop with i == m in case no such index exists. + i := a + j := m + for i < j { + h := int(uint(i+j) >> 1) + if !less(data[m], data[h]) { + i = h + 1 + } else { + j = h + } + } + // Swap values until data[m] reaches the position i. + for k := m; k > i; k-- { + data[k], data[k-1] = data[k-1], data[k] + } + return + } + + mid := int(uint(a+b) >> 1) + n := mid + m + var start, r int + if m > mid { + start = n - b + r = mid + } else { + start = a + r = m + } + p := n - 1 + + for start < r { + c := int(uint(start+r) >> 1) + if !less(data[p-c], data[c]) { + start = c + 1 + } else { + r = c + } + } + + end := n - start + if start < m && m < end { + rotateLessFunc(data, start, m, end, less) + } + if a < start && start < mid { + symMergeLessFunc(data, a, start, mid, less) + } + if mid < end && end < b { + symMergeLessFunc(data, mid, end, b, less) + } +} + +// rotateLessFunc rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data: +// Data of the form 'x u v y' is changed to 'x v u y'. +// rotate performs at most b-a many calls to data.Swap, +// and it assumes non-degenerate arguments: a < m && m < b. +func rotateLessFunc[Elem any](data []Elem, a, m, b int, less func(a, b Elem) bool) { + i := m - a + j := b - m + + for i != j { + if i > j { + swapRangeLessFunc(data, m-i, m, j, less) + i -= j + } else { + swapRangeLessFunc(data, m-i, m+j-i, i, less) + j -= i + } + } + // i == j + swapRangeLessFunc(data, m-i, m, i, less) +} diff --git a/vendor/golang.org/x/exp/slices/zsortordered.go b/vendor/golang.org/x/exp/slices/zsortordered.go new file mode 100644 index 0000000..6fa64a2 --- /dev/null +++ b/vendor/golang.org/x/exp/slices/zsortordered.go @@ -0,0 +1,344 @@ +// Code generated by gen_sort_variants.go; DO NOT EDIT. + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package slices + +import "golang.org/x/exp/constraints" + +// insertionSortOrdered sorts data[a:b] using insertion sort. +func insertionSortOrdered[Elem constraints.Ordered](data []Elem, a, b int) { + for i := a + 1; i < b; i++ { + for j := i; j > a && (data[j] < data[j-1]); j-- { + data[j], data[j-1] = data[j-1], data[j] + } + } +} + +// siftDownOrdered implements the heap property on data[lo:hi]. +// first is an offset into the array where the root of the heap lies. +func siftDownOrdered[Elem constraints.Ordered](data []Elem, lo, hi, first int) { + root := lo + for { + child := 2*root + 1 + if child >= hi { + break + } + if child+1 < hi && (data[first+child] < data[first+child+1]) { + child++ + } + if !(data[first+root] < data[first+child]) { + return + } + data[first+root], data[first+child] = data[first+child], data[first+root] + root = child + } +} + +func heapSortOrdered[Elem constraints.Ordered](data []Elem, a, b int) { + first := a + lo := 0 + hi := b - a + + // Build heap with greatest element at top. + for i := (hi - 1) / 2; i >= 0; i-- { + siftDownOrdered(data, i, hi, first) + } + + // Pop elements, largest first, into end of data. + for i := hi - 1; i >= 0; i-- { + data[first], data[first+i] = data[first+i], data[first] + siftDownOrdered(data, lo, i, first) + } +} + +// Quicksort, loosely following Bentley and McIlroy, +// "Engineering a Sort Function" SP&E November 1993. + +// medianOfThreeOrdered moves the median of the three values data[m0], data[m1], data[m2] into data[m1]. +func medianOfThreeOrdered[Elem constraints.Ordered](data []Elem, m1, m0, m2 int) { + // sort 3 elements + if data[m1] < data[m0] { + data[m1], data[m0] = data[m0], data[m1] + } + // data[m0] <= data[m1] + if data[m2] < data[m1] { + data[m2], data[m1] = data[m1], data[m2] + // data[m0] <= data[m2] && data[m1] < data[m2] + if data[m1] < data[m0] { + data[m1], data[m0] = data[m0], data[m1] + } + } + // now data[m0] <= data[m1] <= data[m2] +} + +func swapRangeOrdered[Elem constraints.Ordered](data []Elem, a, b, n int) { + for i := 0; i < n; i++ { + data[a+i], data[b+i] = data[b+i], data[a+i] + } +} + +func doPivotOrdered[Elem constraints.Ordered](data []Elem, lo, hi int) (midlo, midhi int) { + m := int(uint(lo+hi) >> 1) // Written like this to avoid integer overflow. + if hi-lo > 40 { + // Tukey's "Ninther" median of three medians of three. + s := (hi - lo) / 8 + medianOfThreeOrdered(data, lo, lo+s, lo+2*s) + medianOfThreeOrdered(data, m, m-s, m+s) + medianOfThreeOrdered(data, hi-1, hi-1-s, hi-1-2*s) + } + medianOfThreeOrdered(data, lo, m, hi-1) + + // Invariants are: + // data[lo] = pivot (set up by ChoosePivot) + // data[lo < i < a] < pivot + // data[a <= i < b] <= pivot + // data[b <= i < c] unexamined + // data[c <= i < hi-1] > pivot + // data[hi-1] >= pivot + pivot := lo + a, c := lo+1, hi-1 + + for ; a < c && (data[a] < data[pivot]); a++ { + } + b := a + for { + for ; b < c && !(data[pivot] < data[b]); b++ { // data[b] <= pivot + } + for ; b < c && (data[pivot] < data[c-1]); c-- { // data[c-1] > pivot + } + if b >= c { + break + } + // data[b] > pivot; data[c-1] <= pivot + data[b], data[c-1] = data[c-1], data[b] + b++ + c-- + } + // If hi-c<3 then there are duplicates (by property of median of nine). + // Let's be a bit more conservative, and set border to 5. + protect := hi-c < 5 + if !protect && hi-c < (hi-lo)/4 { + // Lets test some points for equality to pivot + dups := 0 + if !(data[pivot] < data[hi-1]) { // data[hi-1] = pivot + data[c], data[hi-1] = data[hi-1], data[c] + c++ + dups++ + } + if !(data[b-1] < data[pivot]) { // data[b-1] = pivot + b-- + dups++ + } + // m-lo = (hi-lo)/2 > 6 + // b-lo > (hi-lo)*3/4-1 > 8 + // ==> m < b ==> data[m] <= pivot + if !(data[m] < data[pivot]) { // data[m] = pivot + data[m], data[b-1] = data[b-1], data[m] + b-- + dups++ + } + // if at least 2 points are equal to pivot, assume skewed distribution + protect = dups > 1 + } + if protect { + // Protect against a lot of duplicates + // Add invariant: + // data[a <= i < b] unexamined + // data[b <= i < c] = pivot + for { + for ; a < b && !(data[b-1] < data[pivot]); b-- { // data[b] == pivot + } + for ; a < b && (data[a] < data[pivot]); a++ { // data[a] < pivot + } + if a >= b { + break + } + // data[a] == pivot; data[b-1] < pivot + data[a], data[b-1] = data[b-1], data[a] + a++ + b-- + } + } + // Swap pivot into middle + data[pivot], data[b-1] = data[b-1], data[pivot] + return b - 1, c +} + +func quickSortOrdered[Elem constraints.Ordered](data []Elem, a, b, maxDepth int) { + for b-a > 12 { // Use ShellSort for slices <= 12 elements + if maxDepth == 0 { + heapSortOrdered(data, a, b) + return + } + maxDepth-- + mlo, mhi := doPivotOrdered(data, a, b) + // Avoiding recursion on the larger subproblem guarantees + // a stack depth of at most lg(b-a). + if mlo-a < b-mhi { + quickSortOrdered(data, a, mlo, maxDepth) + a = mhi // i.e., quickSortOrdered(data, mhi, b) + } else { + quickSortOrdered(data, mhi, b, maxDepth) + b = mlo // i.e., quickSortOrdered(data, a, mlo) + } + } + if b-a > 1 { + // Do ShellSort pass with gap 6 + // It could be written in this simplified form cause b-a <= 12 + for i := a + 6; i < b; i++ { + if data[i] < data[i-6] { + data[i], data[i-6] = data[i-6], data[i] + } + } + insertionSortOrdered(data, a, b) + } +} + +func stableOrdered[Elem constraints.Ordered](data []Elem, n int) { + blockSize := 20 // must be > 0 + a, b := 0, blockSize + for b <= n { + insertionSortOrdered(data, a, b) + a = b + b += blockSize + } + insertionSortOrdered(data, a, n) + + for blockSize < n { + a, b = 0, 2*blockSize + for b <= n { + symMergeOrdered(data, a, a+blockSize, b) + a = b + b += 2 * blockSize + } + if m := a + blockSize; m < n { + symMergeOrdered(data, a, m, n) + } + blockSize *= 2 + } +} + +// symMergeOrdered merges the two sorted subsequences data[a:m] and data[m:b] using +// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum +// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz +// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in +// Computer Science, pages 714-723. Springer, 2004. +// +// Let M = m-a and N = b-n. Wolog M < N. +// The recursion depth is bound by ceil(log(N+M)). +// The algorithm needs O(M*log(N/M + 1)) calls to data.Less. +// The algorithm needs O((M+N)*log(M)) calls to data.Swap. +// +// The paper gives O((M+N)*log(M)) as the number of assignments assuming a +// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation +// in the paper carries through for Swap operations, especially as the block +// swapping rotate uses only O(M+N) Swaps. +// +// symMerge assumes non-degenerate arguments: a < m && m < b. +// Having the caller check this condition eliminates many leaf recursion calls, +// which improves performance. +func symMergeOrdered[Elem constraints.Ordered](data []Elem, a, m, b int) { + // Avoid unnecessary recursions of symMerge + // by direct insertion of data[a] into data[m:b] + // if data[a:m] only contains one element. + if m-a == 1 { + // Use binary search to find the lowest index i + // such that data[i] >= data[a] for m <= i < b. + // Exit the search loop with i == b in case no such index exists. + i := m + j := b + for i < j { + h := int(uint(i+j) >> 1) + if data[h] < data[a] { + i = h + 1 + } else { + j = h + } + } + // Swap values until data[a] reaches the position before i. + for k := a; k < i-1; k++ { + data[k], data[k+1] = data[k+1], data[k] + } + return + } + + // Avoid unnecessary recursions of symMerge + // by direct insertion of data[m] into data[a:m] + // if data[m:b] only contains one element. + if b-m == 1 { + // Use binary search to find the lowest index i + // such that data[i] > data[m] for a <= i < m. + // Exit the search loop with i == m in case no such index exists. + i := a + j := m + for i < j { + h := int(uint(i+j) >> 1) + if !(data[m] < data[h]) { + i = h + 1 + } else { + j = h + } + } + // Swap values until data[m] reaches the position i. + for k := m; k > i; k-- { + data[k], data[k-1] = data[k-1], data[k] + } + return + } + + mid := int(uint(a+b) >> 1) + n := mid + m + var start, r int + if m > mid { + start = n - b + r = mid + } else { + start = a + r = m + } + p := n - 1 + + for start < r { + c := int(uint(start+r) >> 1) + if !(data[p-c] < data[c]) { + start = c + 1 + } else { + r = c + } + } + + end := n - start + if start < m && m < end { + rotateOrdered(data, start, m, end) + } + if a < start && start < mid { + symMergeOrdered(data, a, start, mid) + } + if mid < end && end < b { + symMergeOrdered(data, mid, end, b) + } +} + +// rotateOrdered rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data: +// Data of the form 'x u v y' is changed to 'x v u y'. +// rotate performs at most b-a many calls to data.Swap, +// and it assumes non-degenerate arguments: a < m && m < b. +func rotateOrdered[Elem constraints.Ordered](data []Elem, a, m, b int) { + i := m - a + j := b - m + + for i != j { + if i > j { + swapRangeOrdered(data, m-i, m, j) + i -= j + } else { + swapRangeOrdered(data, m-i, m+j-i, i) + j -= i + } + } + // i == j + swapRangeOrdered(data, m-i, m, i) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index d322cfd..c79f186 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,6 +1,11 @@ # github.com/go-logr/logr v1.2.2 ## explicit; go 1.16 github.com/go-logr/logr +# golang.org/x/exp v0.0.0-20220314205449-43aec2f8a4e7 +## explicit; go 1.18 +golang.org/x/exp/constraints +golang.org/x/exp/maps +golang.org/x/exp/slices # golang.org/x/net v0.0.0-20220225172249-27dd8689420f ## explicit; go 1.17 golang.org/x/net/webdav