mirror of
1
Fork 0

[bugfix] Fix remote media pruning failing if media already gone (#548)

* fix error check of prune to allow missing files

* update go-store library, add test for pruning item with db entry but no file

Signed-off-by: kim <grufwub@gmail.com>

* remove now-unneccessary error check

Signed-off-by: kim <grufwub@gmail.com>

Co-authored-by: kim <grufwub@gmail.com>
This commit is contained in:
tobi 2022-05-08 19:49:45 +02:00 committed by GitHub
parent 26b74aefaf
commit 5004e0a9da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 4682 additions and 1785 deletions

15
go.mod
View File

@ -4,10 +4,9 @@ go 1.18
require (
codeberg.org/gruf/go-debug v1.1.2
codeberg.org/gruf/go-errors v1.0.5
codeberg.org/gruf/go-mutexes v1.1.2
codeberg.org/gruf/go-runners v1.2.0
codeberg.org/gruf/go-store v1.3.6
codeberg.org/gruf/go-runners v1.2.1
codeberg.org/gruf/go-store v1.3.7
github.com/ReneKroon/ttlcache v1.7.0
github.com/buckket/go-blurhash v1.1.0
github.com/coreos/go-oidc/v3 v3.1.0
@ -51,12 +50,14 @@ require (
)
require (
codeberg.org/gruf/go-bitutil v1.0.0 // indirect
codeberg.org/gruf/go-bytes v1.0.2 // indirect
codeberg.org/gruf/go-byteutil v1.0.0 // indirect
codeberg.org/gruf/go-errors/v2 v2.0.1 // indirect
codeberg.org/gruf/go-fastcopy v1.1.1 // indirect
codeberg.org/gruf/go-fastpath v1.0.2 // indirect
codeberg.org/gruf/go-format v1.0.3 // indirect
codeberg.org/gruf/go-hashenc v1.0.1 // indirect
codeberg.org/gruf/go-pools v1.0.2 // indirect
codeberg.org/gruf/go-fastpath v1.0.3 // indirect
codeberg.org/gruf/go-hashenc v1.0.2 // indirect
codeberg.org/gruf/go-pools v1.1.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dsoprea/go-exif/v3 v3.0.0-20210625224831-a6301f85c82b // indirect

38
go.sum
View File

@ -35,35 +35,37 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
codeberg.org/gruf/go-bitutil v1.0.0 h1:pM3KeDACgQ0ARbdIVEuRyAi3XDyUQdoSR0X24nbCZt8=
codeberg.org/gruf/go-bitutil v1.0.0/go.mod h1:sb8IjlDnjVTz8zPK/8lmHesKxY0Yb3iqHWjUM/SkphA=
codeberg.org/gruf/go-bytes v1.0.0/go.mod h1:1v/ibfaosfXSZtRdW2rWaVrDXMc9E3bsi/M9Ekx39cg=
codeberg.org/gruf/go-bytes v1.0.1/go.mod h1:1v/ibfaosfXSZtRdW2rWaVrDXMc9E3bsi/M9Ekx39cg=
codeberg.org/gruf/go-bytes v1.0.2 h1:malqE42Ni+h1nnYWBUAJaDDtEzF4aeN4uPN8DfMNNvo=
codeberg.org/gruf/go-bytes v1.0.2/go.mod h1:1v/ibfaosfXSZtRdW2rWaVrDXMc9E3bsi/M9Ekx39cg=
codeberg.org/gruf/go-byteutil v1.0.0 h1:xgKFNj/gH1r3yRo7gnyR4qrAKyeWCXs6B19ISX0DUAY=
codeberg.org/gruf/go-byteutil v1.0.0/go.mod h1:cWM3tgMCroSzqoBXUXMhvxTxYJp+TbCr6ioISRY5vSU=
codeberg.org/gruf/go-cache v1.1.2/go.mod h1:/Dbc+xU72Op3hMn6x2PXF3NE9uIDFeS+sXPF00hN/7o=
codeberg.org/gruf/go-debug v1.1.2 h1:7Tqkktg60M/4WtXTTNUFH2T/6irBw4tI4viv7IRLZDE=
codeberg.org/gruf/go-debug v1.1.2/go.mod h1:N+vSy9uJBQgpQcJUqjctvqFz7tBHJf+S/PIjLILzpLg=
codeberg.org/gruf/go-errors v1.0.5 h1:rxV70oQkfasUdggLHxOX2QAoJOMFM7XWxHQR45Zx/Fg=
codeberg.org/gruf/go-errors v1.0.5/go.mod h1:n03EpmvcmfzU3/xJKC0XXtleXXJUNFpT2fgISODvZ1Y=
codeberg.org/gruf/go-errors/v2 v2.0.0/go.mod h1:ZRhbdhvgoUA3Yw6e56kd9Ox984RrvbEFC2pOXyHDJP4=
codeberg.org/gruf/go-errors/v2 v2.0.1 h1:Y2LNtwoXAk1LUcIPzbowXOi7YTLmzsHKPe2QRbMH1OU=
codeberg.org/gruf/go-errors/v2 v2.0.1/go.mod h1:6sI75OmvXE2AtRm4WUyGMEyqEOKTsfe+CA+aBXwbtJY=
codeberg.org/gruf/go-fastcopy v1.1.1 h1:HhPCeFdVR5pwiSVDnQEGJ+J2ny9b5QgfiESc0zrWQAY=
codeberg.org/gruf/go-fastcopy v1.1.1/go.mod h1:GDDYR0Cnb3U/AIfGM3983V/L+GN+vuwVMvrmVABo21s=
codeberg.org/gruf/go-fastpath v1.0.1/go.mod h1:edveE/Kp3Eqi0JJm0lXYdkVrB28cNUkcb/bRGFTPqeI=
codeberg.org/gruf/go-fastpath v1.0.2 h1:O3nuYPMXnN89dsgAwVFU5iCGINtPJdITWmbRe2an/iQ=
codeberg.org/gruf/go-fastpath v1.0.2/go.mod h1:edveE/Kp3Eqi0JJm0lXYdkVrB28cNUkcb/bRGFTPqeI=
codeberg.org/gruf/go-format v1.0.3 h1:WoUGzTwZe6SIhILNvtr0qNIA7BOOCgdBlk5bUrfeiio=
codeberg.org/gruf/go-format v1.0.3/go.mod h1:k3TLXp1dqAXdDqxlon0yEM+3FFHdNn0D6BVJTwTy5As=
codeberg.org/gruf/go-hashenc v1.0.1 h1:EBvNe2wW8IPMUqT1XihB6/IM6KMJDLMFBxIUvmsy1f8=
codeberg.org/gruf/go-hashenc v1.0.1/go.mod h1:IfHhPCVScOiYmJLqdCQT9bYVS1nxNTV4ewMUvFWDPtc=
codeberg.org/gruf/go-fastpath v1.0.3 h1:3Iftz9Z2suCEgTLkQMucew+2+4Oe46JPbAM2JEhnjTU=
codeberg.org/gruf/go-fastpath v1.0.3/go.mod h1:edveE/Kp3Eqi0JJm0lXYdkVrB28cNUkcb/bRGFTPqeI=
codeberg.org/gruf/go-hashenc v1.0.2 h1:U3jH6zMXZiL96czD/qaJd8OR2h7LlBzGv/2WxnMHI/g=
codeberg.org/gruf/go-hashenc v1.0.2/go.mod h1:eK+A8clLcEN/m1nftNsRId0kfYDQnETnuIfBGZ8Gvsg=
codeberg.org/gruf/go-mutexes v1.1.2 h1:AMC1CFV6kMi+iBjR3yQv8yIagG3lWm68U6sQHYFHEf4=
codeberg.org/gruf/go-mutexes v1.1.2/go.mod h1:1j/6/MBeBQUedAtAtysLLnBKogfOZAxdym0E3wlaBD8=
codeberg.org/gruf/go-nowish v1.0.0/go.mod h1:70nvICNcqQ9OHpF07N614Dyk7cpL5ToWU1K1ZVCec2s=
codeberg.org/gruf/go-nowish v1.1.0/go.mod h1:70nvICNcqQ9OHpF07N614Dyk7cpL5ToWU1K1ZVCec2s=
codeberg.org/gruf/go-pools v1.0.2 h1:B0X6yoCL9FVmnvyoizb1SYRwMYPWwEJBjPnBMM5ILos=
codeberg.org/gruf/go-pools v1.0.2/go.mod h1:MjUV3H6IASyBeBPCyCr7wjPpSNu8E2N87LG4r4TAyok=
codeberg.org/gruf/go-nowish v1.1.2/go.mod h1:70nvICNcqQ9OHpF07N614Dyk7cpL5ToWU1K1ZVCec2s=
codeberg.org/gruf/go-pools v1.1.0 h1:LbYP24eQLl/YI1fSU2pafiwhGol1Z1zPjRrMsXpF88s=
codeberg.org/gruf/go-pools v1.1.0/go.mod h1:ZMYpt/DjQWYC3zFD3T97QWSFKs62zAUGJ/tzvgB9D68=
codeberg.org/gruf/go-runners v1.1.1/go.mod h1:9gTrmMnO3d+50C+hVzcmGBf+zTuswReS278E2EMvnmw=
codeberg.org/gruf/go-runners v1.2.0 h1:tkoPrwYMkVg1o/C4PGTR1YbC11XX4r06uLPOYajBsH4=
codeberg.org/gruf/go-runners v1.2.0/go.mod h1:9gTrmMnO3d+50C+hVzcmGBf+zTuswReS278E2EMvnmw=
codeberg.org/gruf/go-store v1.3.6 h1:OKzdvfUC+nvsWV5FiSKdk+85yvxF2Tb7K5ZtRqlDBDU=
codeberg.org/gruf/go-store v1.3.6/go.mod h1:a4vJtZf61UyrsejskX8q+s0lZeNGy7cJLUZt+fH00wo=
codeberg.org/gruf/go-runners v1.2.1 h1:eXXofOMkLTkrdqI0qFYm00hP9IW42JZbc+IRKgVyQ28=
codeberg.org/gruf/go-runners v1.2.1/go.mod h1:9gTrmMnO3d+50C+hVzcmGBf+zTuswReS278E2EMvnmw=
codeberg.org/gruf/go-store v1.3.7 h1:w1nUVXDpdgqXMBRA+8S9MautFhCJXU8H+lQasS5KYXw=
codeberg.org/gruf/go-store v1.3.7/go.mod h1:vaWKWMUFgS7b4NcCo/vS+7K0uh9ZATOI9P60gRMKUSw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@ -546,6 +548,9 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/blake3 v0.2.1/go.mod h1:TSQ0KjMH+pht+bRyvVooJ1rBpvvngSGaPISafq9MxJk=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@ -718,6 +723,7 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201014080544-cc95f250f6bc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -7,7 +7,6 @@ import (
"testing"
"time"
"codeberg.org/gruf/go-errors"
"github.com/gin-contrib/sessions"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/api/client/auth"
@ -26,8 +25,7 @@ type authorizeHandlerTestCase struct {
}
func (suite *AuthAuthorizeTestSuite) TestAccountAuthorizeHandler() {
var tests = []authorizeHandlerTestCase{
tests := []authorizeHandlerTestCase{
{
description: "user has their email unconfirmed",
mutateUserAccount: func(user *gtsmodel.User, account *gtsmodel.Account) {
@ -80,7 +78,7 @@ func (suite *AuthAuthorizeTestSuite) TestAccountAuthorizeHandler() {
testSession.Set(sessionUserID, user.ID)
testSession.Set(sessionClientID, suite.testApplications["application_1"].ClientID)
if err := testSession.Save(); err != nil {
panic(errors.WrapMsgf(err, "failed on case: %s", testCase.description))
panic(fmt.Errorf("failed on case %s: %w", testCase.description, err))
}
testCase.mutateUserAccount(user, account)

View File

@ -69,10 +69,8 @@ func (suite *PruneRemoteTestSuite) TestPruneAndRecache() {
// media should no longer be stored
_, err = suite.storage.Get(testAttachment.File.Path)
suite.Error(err)
suite.ErrorIs(err, storage.ErrNotFound)
_, err = suite.storage.Get(testAttachment.Thumbnail.Path)
suite.Error(err)
suite.ErrorIs(err, storage.ErrNotFound)
// now recache the image....
@ -106,6 +104,23 @@ func (suite *PruneRemoteTestSuite) TestPruneAndRecache() {
suite.NoError(err)
}
func (suite *PruneRemoteTestSuite) TestPruneOneNonExistent() {
ctx := context.Background()
testAttachment := suite.testAttachments["remote_account_1_status_1_attachment_1"]
// Delete this attachment cached on disk
media, err := suite.db.GetAttachmentByID(ctx, testAttachment.ID)
suite.NoError(err)
suite.True(media.Cached)
err = suite.storage.Delete(media.File.Path)
suite.NoError(err)
// Now attempt to prune remote for item with db entry no file
totalPruned, err := suite.manager.PruneRemote(ctx, 1)
suite.NoError(err)
suite.Equal(1, totalPruned)
}
func TestPruneRemoteTestSuite(t *testing.T) {
suite.Run(t, &PruneRemoteTestSuite{})
}

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 gruf
Copyright (c) 2022 gruf
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

3
vendor/codeberg.org/gruf/go-bitutil/README.md generated vendored Normal file
View File

@ -0,0 +1,3 @@
# go-bitutil
This library provides helpful methods and types for performing typical bitwise operations on integers, e.g. packing/unpacking, bit flags.

3084
vendor/codeberg.org/gruf/go-bitutil/flag.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

94
vendor/codeberg.org/gruf/go-bitutil/flag.tpl generated vendored Normal file
View File

@ -0,0 +1,94 @@
package bitutil
import (
"strings"
"codeberg.org/gruf/go-bytes"
)
{{ range $idx, $size := . }}
// Flags{{ $size.Size }} is a type-casted unsigned integer with helper
// methods for easily managing up to {{ $size.Size }} bit flags.
type Flags{{ $size.Size }} uint{{ $size.Size }}
// Get will fetch the flag bit value at index 'bit'.
func (f Flags{{ $size.Size }}) Get(bit uint8) bool {
mask := Flags{{ $size.Size }}(1) << bit
return (f & mask != 0)
}
// Set will set the flag bit value at index 'bit'.
func (f Flags{{ $size.Size }}) Set(bit uint8) Flags{{ $size.Size }} {
mask := Flags{{ $size.Size }}(1) << bit
return f | mask
}
// Unset will unset the flag bit value at index 'bit'.
func (f Flags{{ $size.Size }}) Unset(bit uint8) Flags{{ $size.Size }} {
mask := Flags{{ $size.Size }}(1) << bit
return f & ^mask
}
{{ range $idx := $size.Bits }}
// Get{{ $idx }} will fetch the flag bit value at index {{ $idx }}.
func (f Flags{{ $size.Size }}) Get{{ $idx }}() bool {
const mask = Flags{{ $size.Size }}(1) << {{ $idx }}
return (f & mask != 0)
}
// Set{{ $idx }} will set the flag bit value at index {{ $idx }}.
func (f Flags{{ $size.Size }}) Set{{ $idx }}() Flags{{ $size.Size }} {
const mask = Flags{{ $size.Size }}(1) << {{ $idx }}
return f | mask
}
// Unset{{ $idx }} will unset the flag bit value at index {{ $idx }}.
func (f Flags{{ $size.Size }}) Unset{{ $idx }}() Flags{{ $size.Size }} {
const mask = Flags{{ $size.Size }}(1) << {{ $idx }}
return f & ^mask
}
{{ end }}
// String returns a human readable representation of Flags{{ $size.Size }}.
func (f Flags{{ $size.Size }}) String() string {
var val bool
var buf bytes.Buffer
buf.WriteByte('{')
{{ range $idx := .Bits }}
val = f.Get{{ $idx }}()
buf.WriteString(bool2str(val) + " ")
{{ end }}
buf.Truncate(1)
buf.WriteByte('}')
return buf.String()
}
// GoString returns a more verbose human readable representation of Flags{{ $size.Size }}.
func (f Flags{{ $size.Size }})GoString() string {
var val bool
var buf bytes.Buffer
buf.WriteString("bitutil.Flags{{ $size.Size }}{")
{{ range $idx := .Bits }}
val = f.Get{{ $idx }}()
buf.WriteString("{{ $idx }}="+bool2str(val)+" ")
{{ end }}
buf.Truncate(1)
buf.WriteByte('}')
return buf.String()
}
{{ end }}
func bool2str(b bool) string {
if b {
return "true"
}
return "false"
}

98
vendor/codeberg.org/gruf/go-bitutil/flag_test.tpl generated vendored Normal file
View File

@ -0,0 +1,98 @@
package bitutil_test
import (
"strings"
"testing"
"codeberg.org/gruf/go-bytes"
)
{{ range $idx, $size := . }}
func TestFlags{{ $size.Size }}Get(t *testing.T) {
var mask, flags bitutil.Flags{{ $size.Size }}
{{ range $idx := $size.Bits }}
mask = bitutil.Flags{{ $size.Size }}(1) << {{ $idx }}
flags = 0
flags |= mask
if !flags.Get({{ $idx }}) {
t.Error("failed .Get() set Flags{{ $size.Size }} bit at index {{ $idx }}")
}
flags = ^bitutil.Flags{{ $size.Size }}(0)
flags &= ^mask
if flags.Get({{ $idx }}) {
t.Error("failed .Get() unset Flags{{ $size.Size }} bit at index {{ $idx }}")
}
flags = 0
flags |= mask
if !flags.Get{{ $idx }}() {
t.Error("failed .Get{{ $idx }}() set Flags{{ $size.Size }} bit at index {{ $idx }}")
}
flags = ^bitutil.Flags{{ $size.Size }}(0)
flags &= ^mask
if flags.Get{{ $idx }}() {
t.Error("failed .Get{{ $idx }}() unset Flags{{ $size.Size }} bit at index {{ $idx }}")
}
{{ end }}
}
func TestFlags{{ $size.Size }}Set(t *testing.T) {
var mask, flags bitutil.Flags{{ $size.Size }}
{{ range $idx := $size.Bits }}
mask = bitutil.Flags{{ $size.Size }}(1) << {{ $idx }}
flags = 0
flags = flags.Set({{ $idx }})
if flags & mask == 0 {
t.Error("failed .Set() Flags{{ $size.Size }} bit at index {{ $idx }}")
}
flags = 0
flags = flags.Set{{ $idx }}()
if flags & mask == 0 {
t.Error("failed .Set{{ $idx }}() Flags{{ $size.Size }} bit at index {{ $idx }}")
}
{{ end }}
}
func TestFlags{{ $size.Size }}Unset(t *testing.T) {
var mask, flags bitutil.Flags{{ $size.Size }}
{{ range $idx := $size.Bits }}
mask = bitutil.Flags{{ $size.Size }}(1) << {{ $idx }}
flags = ^bitutil.Flags{{ $size.Size }}(0)
flags = flags.Unset({{ $idx }})
if flags & mask != 0 {
t.Error("failed .Unset() Flags{{ $size.Size }} bit at index {{ $idx }}")
}
flags = ^bitutil.Flags{{ $size.Size }}(0)
flags = flags.Unset{{ $idx }}()
if flags & mask != 0 {
t.Error("failed .Unset{{ $idx }}() Flags{{ $size.Size }} bit at index {{ $idx }}")
}
{{ end }}
}
{{ end }}

85
vendor/codeberg.org/gruf/go-bitutil/pack.go generated vendored Normal file
View File

@ -0,0 +1,85 @@
package bitutil
// PackInt8s will pack two signed 8bit integers into an unsigned 16bit integer.
func PackInt8s(i1, i2 int8) uint16 {
const bits = 8
const mask = (1 << bits) - 1
return uint16(i1)<<bits | uint16(i2)&mask
}
// UnpackInt8s will unpack two signed 8bit integers from an unsigned 16bit integer.
func UnpackInt8s(i uint16) (int8, int8) {
const bits = 8
const mask = (1 << bits) - 1
return int8(i >> bits), int8(i & mask)
}
// PackInt16s will pack two signed 16bit integers into an unsigned 32bit integer.
func PackInt16s(i1, i2 int16) uint32 {
const bits = 16
const mask = (1 << bits) - 1
return uint32(i1)<<bits | uint32(i2)&mask
}
// UnpackInt16s will unpack two signed 16bit integers from an unsigned 32bit integer.
func UnpackInt16s(i uint32) (int16, int16) {
const bits = 16
const mask = (1 << bits) - 1
return int16(i >> bits), int16(i & mask)
}
// PackInt32s will pack two signed 32bit integers into an unsigned 64bit integer.
func PackInt32s(i1, i2 int32) uint64 {
const bits = 32
const mask = (1 << bits) - 1
return uint64(i1)<<bits | uint64(i2)&mask
}
// UnpackInt32s will unpack two signed 32bit integers from an unsigned 64bit integer.
func UnpackInt32s(i uint64) (int32, int32) {
const bits = 32
const mask = (1 << bits) - 1
return int32(i >> bits), int32(i & mask)
}
// PackUint8s will pack two unsigned 8bit integers into an unsigned 16bit integer.
func PackUint8s(u1, u2 uint8) uint16 {
const bits = 8
const mask = (1 << bits) - 1
return uint16(u1)<<bits | uint16(u2)&mask
}
// UnpackUint8s will unpack two unsigned 8bit integers from an unsigned 16bit integer.
func UnpackUint8s(u uint16) (uint8, uint8) {
const bits = 8
const mask = (1 << bits) - 1
return uint8(u >> bits), uint8(u & mask)
}
// PackUint16s will pack two unsigned 16bit integers into an unsigned 32bit integer.
func PackUint16s(u1, u2 uint16) uint32 {
const bits = 16
const mask = (1 << bits) - 1
return uint32(u1)<<bits | uint32(u2)&mask
}
// UnpackUint16s will unpack two unsigned 16bit integers from an unsigned 32bit integer.
func UnpackUint16s(u uint32) (uint16, uint16) {
const bits = 16
const mask = (1 << bits) - 1
return uint16(u >> bits), uint16(u & mask)
}
// PackUint32s will pack two unsigned 32bit integers into an unsigned 64bit integer.
func PackUint32s(u1, u2 uint32) uint64 {
const bits = 32
const mask = (1 << bits) - 1
return uint64(u1)<<bits | uint64(u2)&mask
}
// UnpackUint32s will unpack two unsigned 32bit integers from an unsigned 64bit integer.
func UnpackUint32s(u uint64) (uint32, uint32) {
const bits = 32
const mask = (1 << bits) - 1
return uint32(u >> bits), uint32(u & mask)
}

60
vendor/codeberg.org/gruf/go-bitutil/test.tpl generated vendored Normal file
View File

@ -0,0 +1,60 @@
package atomics_test
import (
"atomic"
"unsafe"
"testing"
"codeberg.org/gruf/go-atomics"
)
func Test{{ .Name }}StoreLoad(t *testing.T) {
for _, test := range {{ .Name }}Tests {
val := atomics.New{{ .Name }}()
val.Store(test.V1)
if !({{ call .Compare "val.Load()" "test.V1" }}) {
t.Fatalf("failed testing .Store and .Load: expect=%v actual=%v", val.Load(), test.V1)
}
val.Store(test.V2)
if !({{ call .Compare "val.Load()" "test.V2" }}) {
t.Fatalf("failed testing .Store and .Load: expect=%v actual=%v", val.Load(), test.V2)
}
}
}
func Test{{ .Name }}CAS(t *testing.T) {
for _, test := range {{ .Name }}Tests {
val := atomics.New{{ .Name }}()
val.Store(test.V1)
if val.CAS(test.V2, test.V1) {
t.Fatalf("failed testing negative .CAS: test=%+v state=%v", test, val.Load())
}
if !val.CAS(test.V1, test.V2) {
t.Fatalf("failed testing positive .CAS: test=%+v state=%v", test, val.Load())
}
}
}
func Test{{ .Name }}Swap(t *testing.T) {
for _, test := range {{ .Name }}Tests {
val := atomics.New{{ .Name }}()
val.Store(test.V1)
if !({{ call .Compare "val.Swap(test.V2)" "test.V1" }}) {
t.Fatal("failed testing .Swap")
}
if !({{ call .Compare "val.Swap(test.V1)" "test.V2" }}) {
t.Fatal("failed testing .Swap")
}
}
}

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 gruf
Copyright (c) 2022 gruf
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

3
vendor/codeberg.org/gruf/go-byteutil/README.md generated vendored Normal file
View File

@ -0,0 +1,3 @@
# go-byteutil
A useful package of byte utilities.

129
vendor/codeberg.org/gruf/go-byteutil/buffer.go generated vendored Normal file
View File

@ -0,0 +1,129 @@
package byteutil
import (
"errors"
"io"
"unicode/utf8"
)
var (
// ensure we conform to interfaces.
_ interface {
io.Writer
io.ByteWriter
WriteRune(rune) (int, error)
io.StringWriter
io.WriterAt
WriteStringAt(string, int64) (int, error)
} = (*Buffer)(nil)
// ErrBeyondBufferLen is returned if .WriteAt() is attempted beyond buffer length.
ErrBeyondBufferLen = errors.New("start beyond buffer length")
)
// Buffer is a simple wrapper around a byte slice.
type Buffer struct{ B []byte }
// WriteByte will append given byte to buffer, fulfilling io.ByteWriter.
func (buf *Buffer) WriteByte(c byte) error {
buf.B = append(buf.B, c)
return nil
}
// WriteRune will append given rune to buffer.
func (buf *Buffer) WriteRune(r rune) (int, error) {
// Check for single-byte rune
if r < utf8.RuneSelf {
buf.B = append(buf.B, byte(r))
return 1, nil
}
// Before-len
l := len(buf.B)
// Grow to max size rune
buf.Grow(utf8.UTFMax)
// Write encoded rune to buffer
n := utf8.EncodeRune(buf.B[l:len(buf.B)], r)
buf.B = buf.B[:l+n]
return n, nil
}
// Write will append given byte slice to buffer, fulfilling io.Writer.
func (buf *Buffer) Write(b []byte) (int, error) {
buf.B = append(buf.B, b...)
return len(b), nil
}
// WriteString will append given string to buffer, fulfilling io.StringWriter.
func (buf *Buffer) WriteString(s string) (int, error) {
buf.B = append(buf.B, s...)
return len(s), nil
}
// WriteAt will append given byte slice to buffer at index 'start', fulfilling io.WriterAt.
func (buf *Buffer) WriteAt(b []byte, start int64) (int, error) {
if start > int64(len(buf.B)) {
return 0, ErrBeyondBufferLen
}
buf.Grow(len(b) - int(int64(len(buf.B))-start))
return copy(buf.B[start:], b), nil
}
// WriteStringAt will append given string to buffer at index 'start'.
func (buf *Buffer) WriteStringAt(s string, start int64) (int, error) {
if start > int64(len(buf.B)) {
return 0, ErrBeyondBufferLen
}
buf.Grow(len(s) - int(int64(len(buf.B))-start))
return copy(buf.B[start:], s), nil
}
// Len returns the length of the buffer's underlying byte slice.
func (buf *Buffer) Len() int {
return len(buf.B)
}
// Cap returns the capacity of the buffer's underlying byte slice.
func (buf *Buffer) Cap() int {
return cap(buf.B)
}
// Grow will increase the buffers length by 'sz', and the capacity by at least this.
func (buf *Buffer) Grow(sz int) {
buf.Guarantee(sz)
buf.B = buf.B[:len(buf.B)+sz]
}
// Guarantee will guarantee buffer containers at least 'sz' remaining capacity.
func (buf *Buffer) Guarantee(sz int) {
if sz > cap(buf.B)-len(buf.B) {
nb := make([]byte, 2*cap(buf.B)+sz)
copy(nb, buf.B)
buf.B = nb[:len(buf.B)]
}
}
// Truncate will reduce the length of the buffer by 'n'.
func (buf *Buffer) Truncate(n int) {
if n > len(buf.B) {
n = len(buf.B)
}
buf.B = buf.B[:len(buf.B)-n]
}
// Reset will reset the buffer length to 0 (retains capacity).
func (buf *Buffer) Reset() {
buf.B = buf.B[:0]
}
// String returns the underlying byte slice as a string. Please note
// this value is tied directly to the underlying byte slice, if you
// write to the buffer then returned string values will also change.
//
// To get an immutable string from buffered data, use string(buf.B).
func (buf *Buffer) String() string {
return B2S(buf.B)
}

65
vendor/codeberg.org/gruf/go-byteutil/bytes.go generated vendored Normal file
View File

@ -0,0 +1,65 @@
package byteutil
import (
"reflect"
"unsafe"
)
// Copy returns a copy of []byte.
func Copy(b []byte) []byte {
if b == nil {
return nil
}
p := make([]byte, len(b))
copy(p, b)
return p
}
// B2S returns a string representation of []byte without allocation.
func B2S(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
// S2B returns a []byte representation of string without allocation (minus slice header).
func S2B(s string) []byte {
var b []byte
// Get byte + string headers
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
// Manually set bytes to string
bh.Data = sh.Data
bh.Len = sh.Len
bh.Cap = sh.Len
return b
}
// ToUpper offers a faster ToUpper implementation using a lookup table.
func ToUpper(b []byte) {
const toUpperTable = "\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~" +
"\u007f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96" +
"\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf" +
"\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8" +
"\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1" +
"\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
for i := 0; i < len(b); i++ {
b[i] = toUpperTable[b[i]]
}
}
// ToLower offers a faster ToLower implementation using a lookup table.
func ToLower(b []byte) {
const toLowerTable = "\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" +
" !\"#$%&'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" +
"\u007f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96" +
"\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf" +
"\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8" +
"\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1" +
"\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
for i := 0; i < len(b); i++ {
b[i] = toLowerTable[b[i]]
}
}

View File

@ -1 +0,0 @@
simple but powerful errors library that allows providing context information with errors

View File

@ -1,107 +0,0 @@
package errors
import (
"fmt"
"sync"
"codeberg.org/gruf/go-format"
)
// KV is a structure for setting key-value pairs in ErrorData.
type KV struct {
Key string
Value interface{}
}
// ErrorData defines a way to set and access contextual error data.
// The default implementation of this is thread-safe.
type ErrorData interface {
// Value will attempt to fetch value for given key in ErrorData
Value(string) (interface{}, bool)
// Append adds the supplied key-values to ErrorData, similar keys DO overwrite
Append(...KV)
// Implement byte slice representation formatter.
format.Formattable
// Implement string representation formatter.
fmt.Stringer
}
// NewData returns a new ErrorData implementation.
func NewData() ErrorData {
return &errorData{
data: make([]KV, 0, 10),
}
}
// errorData is our ErrorData implementation, this is essentially
// just a thread-safe string-interface map implementation.
type errorData struct {
data []KV
mu sync.Mutex
}
func (d *errorData) set(key string, value interface{}) {
for i := range d.data {
if d.data[i].Key == key {
// Found existing, update!
d.data[i].Value = value
return
}
}
// Add new KV entry to slice
d.data = append(d.data, KV{
Key: key,
Value: value,
})
}
func (d *errorData) Value(key string) (interface{}, bool) {
d.mu.Lock()
for i := range d.data {
if d.data[i].Key == key {
v := d.data[i].Value
d.mu.Unlock()
return v, true
}
}
d.mu.Unlock()
return nil, false
}
func (d *errorData) Append(kvs ...KV) {
d.mu.Lock()
for i := range kvs {
d.set(kvs[i].Key, kvs[i].Value)
}
d.mu.Unlock()
}
func (d *errorData) AppendFormat(b []byte) []byte {
buf := format.Buffer{B: b}
d.mu.Lock()
buf.B = append(buf.B, '{')
// Append data as kv pairs
for i := range d.data {
key := d.data[i].Key
val := d.data[i].Value
format.Appendf(&buf, "{:k}={:v} ", key, val)
}
// Drop trailing space
if len(d.data) > 0 {
buf.Truncate(1)
}
buf.B = append(buf.B, '}')
d.mu.Unlock()
return buf.B
}
func (d *errorData) String() string {
return string(d.AppendFormat(nil))
}

View File

@ -1,199 +0,0 @@
package errors
import "fmt"
// ErrorContext defines a wrappable error with the ability to hold extra contextual information
type ErrorContext interface {
// implement base error interface
error
// Is identifies whether the receiver contains / is the target
Is(error) bool
// Unwrap reveals the underlying wrapped error (if any!)
Unwrap() error
// Value attempts to fetch contextual data for given key from this ErrorContext
Value(string) (interface{}, bool)
// Append allows adding contextual data to this ErrorContext
Append(...KV) ErrorContext
// Data returns the contextual data structure associated with this ErrorContext
Data() ErrorData
}
// New returns a new ErrorContext created from string
func New(msg string) ErrorContext {
return stringError(msg)
}
// Newf returns a new ErrorContext created from format string
func Newf(s string, a ...interface{}) ErrorContext {
return stringError(fmt.Sprintf(s, a...))
}
// Wrap ensures supplied error is an ErrorContext, wrapping if necessary
func Wrap(err error) ErrorContext {
// Nil error, do nothing
if err == nil {
return nil
}
// Check if this is already wrapped somewhere in stack
if xerr, ok := err.(*errorContext); ok {
return xerr
} else if As(err, &xerr) {
// This is really not an ideal situation,
// but we try to make do by salvaging the
// contextual error data from earlier in
// stack, setting current error to the top
// and setting the unwrapped error to inner
return &errorContext{
data: xerr.data,
innr: Unwrap(err),
err: err,
}
}
// Return new Error type
return &errorContext{
data: NewData(),
innr: nil,
err: err,
}
}
// WrapMsg wraps supplied error as inner, returning an ErrorContext
// with a new outer error made from the supplied message string
func WrapMsg(err error, msg string) ErrorContext {
// Nil error, do nothing
if err == nil {
return nil
}
// Check if this is already wrapped
var xerr *errorContext
if As(err, &xerr) {
return &errorContext{
data: xerr.data,
innr: err,
err: New(msg),
}
}
// Return new wrapped error
return &errorContext{
data: NewData(),
innr: err,
err: stringError(msg),
}
}
// WrapMsgf wraps supplied error as inner, returning an ErrorContext with
// a new outer error made from the supplied message format string
func WrapMsgf(err error, msg string, a ...interface{}) ErrorContext {
return WrapMsg(err, fmt.Sprintf(msg, a...))
}
// ErrorData attempts fetch ErrorData from supplied error, returns nil otherwise
func Data(err error) ErrorData {
x, ok := err.(ErrorContext)
if ok {
return x.Data()
}
return nil
}
// UnwrapAll fully unwraps an error stack to produce a string output.
func UnwrapAll(err error) string {
if err == nil {
return ""
}
// Start error output
out := err.Error()
err = Unwrap(err)
// Unwrap and append each
for err != nil {
out += ": " + err.Error()
err = Unwrap(err)
}
return out
}
// stringError is the simplest ErrorContext implementation
type stringError string
func (e stringError) Error() string {
return string(e)
}
func (e stringError) Is(err error) bool {
se, ok := err.(stringError)
return ok && e == se
}
func (e stringError) Unwrap() error {
return nil
}
func (e stringError) Value(key string) (interface{}, bool) {
return nil, false
}
func (e stringError) Append(kvs ...KV) ErrorContext {
data := NewData()
data.Append(kvs...)
return &errorContext{
data: data,
innr: nil,
err: e,
}
}
func (e stringError) Data() ErrorData {
return nil
}
// errorContext is the *actual* ErrorContext implementation
type errorContext struct {
// data contains any appended context data, there will only ever be one
// instance of data within an ErrorContext stack
data ErrorData
// innr is the inner wrapped error in this structure, it is only accessible
// via .Unwrap() or via .Is()
innr error
// err is the top-level error in this wrapping structure, we identify
// as this error type via .Is() and return its error message
err error
}
func (e *errorContext) Error() string {
return e.err.Error()
}
func (e *errorContext) Is(err error) bool {
return Is(e.err, err) || Is(e.innr, err)
}
func (e *errorContext) Unwrap() error {
return e.innr
}
func (e *errorContext) Value(key string) (interface{}, bool) {
return e.data.Value(key)
}
func (e *errorContext) Append(kvs ...KV) ErrorContext {
e.data.Append(kvs...)
return e
}
func (e *errorContext) Data() ErrorData {
return e.data
}

View File

@ -1,18 +0,0 @@
package errors
import "errors"
// Is wraps "errors".Is()
func Is(err, target error) bool {
return errors.Is(err, target)
}
// As wraps "errors".As()
func As(err error, target interface{}) bool {
return errors.As(err, target)
}
// Unwrap wraps "errors".Unwrap()
func Unwrap(err error) error {
return errors.Unwrap(err)
}

9
vendor/codeberg.org/gruf/go-errors/v2/LICENSE generated vendored Normal file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2022 gruf
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

5
vendor/codeberg.org/gruf/go-errors/v2/README.md generated vendored Normal file
View File

@ -0,0 +1,5 @@
# go-errors
simple but powerful errors library that allows easy wrapping and stacktracing of errors.
to disable stacktraces set the `notrace` build tag.

102
vendor/codeberg.org/gruf/go-errors/v2/callers.go generated vendored Normal file
View File

@ -0,0 +1,102 @@
package errors
import (
"encoding/json"
"runtime"
"strconv"
"strings"
"unsafe"
)
// Callers is a stacktrace of caller PCs.
type Callers []uintptr
// GetCallers returns a Callers slice of PCs, of at most 'depth'.
func GetCallers(skip int, depth int) Callers {
rpc := make([]uintptr, depth)
n := runtime.Callers(skip+1, rpc)
return Callers(rpc[0:n])
}
// Frames fetches runtime frames for a slice of caller PCs.
func (f Callers) Frames() []runtime.Frame {
// Allocate expected frames slice
frames := make([]runtime.Frame, 0, len(f))
// Get frames iterator for PCs
iter := runtime.CallersFrames(f)
for {
// Get next frame in iter
frame, ok := iter.Next()
if !ok {
break
}
// Append to frames slice
frames = append(frames, frame)
}
return frames
}
// MarshalJSON implements json.Marshaler to provide an easy, simply default.
func (f Callers) MarshalJSON() ([]byte, error) {
// JSON-able frame type
type frame struct {
Func string `json:"func"`
File string `json:"file"`
Line int `json:"line"`
}
// Allocate expected frames slice
frames := make([]frame, 0, len(f))
// Get frames iterator for PCs
iter := runtime.CallersFrames(f)
for {
// Get next frame
f, ok := iter.Next()
if !ok {
break
}
// Append to frames slice
frames = append(frames, frame{
Func: funcname(f.Function),
File: f.File,
Line: f.Line,
})
}
// marshal converted frames
return json.Marshal(frames)
}
// String will return a simple string representation of receiving Callers slice.
func (f Callers) String() string {
// Guess-timate to reduce allocs
buf := make([]byte, 0, 64*len(f))
// Convert to frames
frames := f.Frames()
for i := 0; i < len(frames); i++ {
frame := frames[i]
// Append formatted caller info
funcname := funcname(frame.Function)
buf = append(buf, funcname+"()\n\t"+frame.File+":"...)
buf = strconv.AppendInt(buf, int64(frame.Line), 10)
buf = append(buf, '\n')
}
return *(*string)(unsafe.Pointer(&buf))
}
// funcname splits a function name with pkg from its path prefix.
func funcname(name string) string {
i := strings.LastIndex(name, "/")
return name[i+1:]
}

35
vendor/codeberg.org/gruf/go-errors/v2/error.go generated vendored Normal file
View File

@ -0,0 +1,35 @@
//go:build !notrace
// +build !notrace
package errors
type errormsg struct {
msg string
wrap error
stack Callers
}
func create(msg string, wrap error) *errormsg {
return &errormsg{
msg: msg,
wrap: wrap,
stack: GetCallers(2, 10),
}
}
func (err *errormsg) Error() string {
return err.msg
}
func (err *errormsg) Is(target error) bool {
other, ok := target.(*errormsg)
return ok && (err.msg == other.msg)
}
func (err *errormsg) Unwrap() error {
return err.wrap
}
func (err *errormsg) Stacktrace() Callers {
return err.stack
}

33
vendor/codeberg.org/gruf/go-errors/v2/error_notrace.go generated vendored Normal file
View File

@ -0,0 +1,33 @@
//go:build notrace
// +build notrace
package errors
type errormsg struct {
msg string
wrap error
}
func create(msg string, wrap error) *errormsg {
return &errormsg{
msg: msg,
wrap: wrap,
}
}
func (err *errormsg) Error() string {
return err.msg
}
func (err *errormsg) Is(target error) bool {
other, ok := target.(*errormsg)
return ok && (err.msg == other.msg)
}
func (err *errormsg) Unwrap() error {
return err.wrap
}
func (err *errormsg) Stacktrace() Callers {
return nil
}

36
vendor/codeberg.org/gruf/go-errors/v2/errors.go generated vendored Normal file
View File

@ -0,0 +1,36 @@
package errors
import (
"fmt"
)
// New returns a new error created from message.
func New(msg string) error {
return create(msg, nil)
}
// Newf returns a new error created from message format and args.
func Newf(msgf string, args ...interface{}) error {
return create(fmt.Sprintf(msgf, args...), nil)
}
// Wrap will wrap supplied error within a new error created from message.
func Wrap(err error, msg string) error {
return create(msg, err)
}
// Wrapf will wrap supplied error within a new error created from message format and args.
func Wrapf(err error, msgf string, args ...interface{}) error {
return create(fmt.Sprintf(msgf, args...), err)
}
// Stacktrace fetches a stored stacktrace of callers from an error, or returns nil.
func Stacktrace(err error) Callers {
var callers Callers
if err, ok := err.(interface { //nolint
Stacktrace() Callers
}); ok {
callers = err.Stacktrace()
}
return callers
}

View File

@ -5,19 +5,18 @@ import (
"unsafe"
)
// OnceError is an error structure that supports safe multi-threaded
// usage and setting only once (until reset)
type OnceError struct {
err unsafe.Pointer
}
// OnceError is an error structure that supports safe multi
// threaded usage and setting only once (until reset).
type OnceError struct{ err unsafe.Pointer }
// NewOnce returns a new OnceError instance
// NewOnce returns a new OnceError instance.
func NewOnce() OnceError {
return OnceError{
err: nil,
}
}
// Store will safely set the OnceError to value, no-op if nil.
func (e *OnceError) Store(err error) {
// Nothing to do
if err == nil {
@ -32,14 +31,17 @@ func (e *OnceError) Store(err error) {
)
}
// Load will load the currently stored error.
func (e *OnceError) Load() error {
return *(*error)(atomic.LoadPointer(&e.err))
}
// IsSet returns whether OnceError has been set.
func (e *OnceError) IsSet() bool {
return (atomic.LoadPointer(&e.err) != nil)
}
// Reset will reset the OnceError value.
func (e *OnceError) Reset() {
atomic.StorePointer(&e.err, nil)
}

99
vendor/codeberg.org/gruf/go-errors/v2/standard.go generated vendored Normal file
View File

@ -0,0 +1,99 @@
package errors
import (
"errors"
"reflect"
"codeberg.org/gruf/go-bitutil"
)
// Is reports whether any error in err's chain matches any of targets
// (up to a max of 64 targets).
//
// The chain consists of err itself followed by the sequence of errors obtained by
// repeatedly calling Unwrap.
//
// An error is considered to match a target if it is equal to that target or if
// it implements a method Is(error) bool such that Is(target) returns true.
func Is(err error, targets ...error) bool {
var flags bitutil.Flags64
if len(targets) > 64 {
panic("too many targets")
}
// Determine if each of targets are comparable
for i := 0; i < len(targets); {
// Drop nil errors
if targets[i] == nil {
targets = append(targets[:i], targets[i+1:]...)
continue
}
// Check if this error is directly comparable
if reflect.TypeOf(targets[i]).Comparable() {
flags = flags.Set(uint8(i))
}
i++
}
for err != nil {
var errorIs func(error) bool
// Check if this layer supports .Is interface
is, ok := err.(interface{ Is(error) bool })
if ok {
errorIs = is.Is
} else {
errorIs = neveris
}
for i := 0; i < len(targets); i++ {
// Try directly compare errors
if flags.Get(uint8(i)) &&
err == targets[i] {
return true
}
// Try use .Is() interface
if errorIs(targets[i]) {
return true
}
}
// Unwrap to next layer
err = errors.Unwrap(err)
}
return false
}
// As finds the first error in err's chain that matches target, and if one is found, sets
// target to that error value and returns true. Otherwise, it returns false.
//
// The chain consists of err itself followed by the sequence of errors obtained by
// repeatedly calling Unwrap.
//
// An error matches target if the error's concrete value is assignable to the value
// pointed to by target, or if the error has a method As(interface{}) bool such that
// As(target) returns true. In the latter case, the As method is responsible for
// setting target.
//
// An error type might provide an As method so it can be treated as if it were a
// different error type.
//
// As panics if target is not a non-nil pointer to either a type that implements
// error, or to any interface type.
func As(err error, target interface{}) bool {
return errors.As(err, target)
}
// Unwrap returns the result of calling the Unwrap method on err, if err's
// type contains an Unwrap method returning error. Otherwise, Unwrap returns nil.
func Unwrap(err error) error {
return errors.Unwrap(err)
}
// neveris fits the .Is(error) bool interface function always returning false.
func neveris(error) bool { return false }

View File

@ -6,20 +6,19 @@ import (
// allocate these just once
var (
dot = []byte(".")
dotStr = ".'"
dot = []byte(dotStr)
dotStr = "."
)
type Builder struct {
B []byte // B is the underlying byte buffer
dd int // pos of last '..' appended to builder
abs bool // abs stores whether path passed to first .Append() is absolute
set bool // set stores whether b.abs has been set i.e. not first call to .Append()
}
// NewBuilder returns a new Builder object using the supplied byte
// slice as the underlying buffer
// NewBuilder returns a new Builder object using the
// supplied byte slice as the underlying buffer
func NewBuilder(b []byte) Builder {
if b != nil {
b = b[:0]
@ -53,7 +52,7 @@ func (b *Builder) Cap() int {
// Bytes returns the accumulated path bytes.
func (b *Builder) Bytes() []byte {
if b.Len() < 1 {
if len(b.B) < 1 {
return dot
}
return b.B
@ -61,7 +60,7 @@ func (b *Builder) Bytes() []byte {
// String returns the accumulated path string.
func (b *Builder) String() string {
if b.Len() < 1 {
if len(b.B) < 1 {
return dotStr
}
return string(b.B)
@ -74,7 +73,7 @@ func (b *Builder) String() string {
// returned string changing. Consider using .String() if
// this is undesired behaviour.
func (b *Builder) StringPtr() string {
if b.Len() < 1 {
if len(b.B) < 1 {
return dotStr
}
return *(*string)(unsafe.Pointer(&b.B))
@ -89,9 +88,9 @@ func (b *Builder) Absolute() bool {
func (b *Builder) SetAbsolute(val bool) {
if !b.set {
if val {
// .Append() has not be called,
// add a '/' and set abs
b.guarantee(1)
// .Append() has not been
// called, add a '/' and set abs
b.Guarantee(1)
b.appendByte('/')
b.abs = true
}
@ -107,7 +106,7 @@ func (b *Builder) SetAbsolute(val bool) {
// If not empty (i.e. not just '/'),
// then shift bytes 1 left
if b.Len() > 1 {
if len(b.B) > 1 {
copy(b.B, b.B[1:])
}
@ -119,16 +118,16 @@ func (b *Builder) SetAbsolute(val bool) {
b.abs = true
// Guarantee 1 byte available
b.guarantee(1)
b.Guarantee(1)
// If empty, just append '/'
if b.Len() < 1 {
if len(b.B) < 1 {
b.appendByte('/')
return
}
// Increase length
l := b.Len()
l := len(b.B)
b.B = b.B[:l+1]
// Shift bytes 1 right
@ -151,9 +150,9 @@ func (b *Builder) Append(p []byte) {
// to accomodate the extra path length
func (b *Builder) AppendString(path string) {
defer func() {
// If buffer is empty, and an absolute path,
// ensure it starts with a '/'
if b.Len() < 1 && b.abs {
// If buffer is empty, and an absolute
// path, ensure it starts with a '/'
if len(b.B) < 1 && b.abs {
b.appendByte('/')
}
}()
@ -165,7 +164,7 @@ func (b *Builder) AppendString(path string) {
// Guarantee at least the total length
// of supplied path available in the buffer
b.guarantee(len(path))
b.Guarantee(len(path))
// Try store if absolute
if !b.set {
@ -193,7 +192,7 @@ func (b *Builder) AppendString(path string) {
// our current state of the buffer. i.e. is
// our buffer length longer than the last
// '..' we placed?
case b.Len() > b.dd:
case len(b.B) > b.dd:
b.backtrack()
// b.cp = b.lp
// b.lp = 0
@ -202,22 +201,22 @@ func (b *Builder) AppendString(path string) {
// we can append '..' to the path buffer,
// which is ONLY when path is NOT absolute
case !b.abs:
if b.Len() > 0 {
if len(b.B) > 0 {
b.appendByte('/')
}
b.appendByte('.')
b.appendByte('.')
b.dd = b.Len()
b.dd = len(b.B)
// b.lp = lp - 2
// b.cp = b.dd
}
default:
if (b.abs && b.Len() != 1) || (!b.abs && b.Len() > 0) {
if (b.abs && len(b.B) != 1) || (!b.abs && len(b.B) > 0) {
b.appendByte('/')
}
// b.lp = b.cp
// b.cp = b.Len()
// b.cp = len(b.B)
i += b.appendSlice(path[i:])
}
}
@ -236,24 +235,29 @@ func (b *Builder) Clean(path string) string {
// performing this operation and returning the shortest possible combination
// of all the supplied paths. The builder object is NOT reset after return
func (b *Builder) Join(base string, paths ...string) string {
empty := (len(base) < 1)
b.Reset()
b.AppendString(base)
for _, path := range paths {
b.AppendString(path)
empty = empty && (len(path) < 1)
size := len(base)
for i := 0; i < len(paths); i++ {
b.AppendString(paths[i])
size += len(paths[i])
}
if empty {
if size < 1 {
return ""
} else if len(b.B) < 1 {
return dotStr
}
return b.String()
return string(b.B)
}
// Guarantee ensures there is at least the requested size
// free bytes available in the buffer, reallocating if
// necessary
// free bytes available in the buffer, reallocating if necessary
func (b *Builder) Guarantee(size int) {
b.guarantee(size)
if size > cap(b.B)-len(b.B) {
nb := make([]byte, 2*cap(b.B)+size)
copy(nb, b.B)
b.B = nb[:len(b.B)]
}
}
// Truncate reduces the length of the buffer by the requested
@ -261,7 +265,7 @@ func (b *Builder) Guarantee(size int) {
// byte (i.e. '/') will never be truncated
func (b *Builder) Truncate(size int) {
// If absolute and just '/', do nothing
if b.abs && b.Len() == 1 {
if b.abs && len(b.B) == 1 {
return
}
@ -269,21 +273,10 @@ func (b *Builder) Truncate(size int) {
b.truncate(size)
}
// truncate reduces the length of the buffer by the requested size,
// no sanity checks are performed
// truncate reduces the length of the buffer by the requested
// size, no sanity checks are performed
func (b *Builder) truncate(size int) {
b.B = b.B[:b.Len()-size]
}
// guarantee ensures there is at least the requested size
// free bytes available in the buffer, reallocating if necessary.
// no sanity checks are performed
func (b *Builder) guarantee(size int) {
if size > b.Cap()-b.Len() {
nb := make([]byte, 2*b.Cap()+size)
copy(nb, b.B)
b.B = nb[:b.Len()]
}
b.B = b.B[:len(b.B)-size]
}
// appendByte appends the supplied byte to the end of
@ -291,8 +284,8 @@ func (b *Builder) guarantee(size int) {
// buffer and setting the next byte-at-index, this is safe as guarantee()
// will have been called beforehand
func (b *Builder) appendByte(c byte) {
b.B = b.B[:b.Len()+1]
b.B[b.Len()-1] = c
b.B = b.B[:len(b.B)+1]
b.B[len(b.B)-1] = c
}
// appendSlice appends the supplied string slice to
@ -304,8 +297,8 @@ func (b *Builder) appendByte(c byte) {
func (b *Builder) appendSlice(slice string) int {
i := 0
for i < len(slice) && slice[i] != '/' {
b.B = b.B[:b.Len()+1]
b.B[b.Len()-1] = slice[i]
b.B = b.B[:len(b.B)+1]
b.B[len(b.B)-1] = slice[i]
i++
}
return i
@ -314,13 +307,13 @@ func (b *Builder) appendSlice(slice string) int {
// backtrack reduces the end of the buffer back to the last
// separating '/', or end of buffer
func (b *Builder) backtrack() {
b.B = b.B[:b.Len()-1]
b.B = b.B[:len(b.B)-1]
for b.Len()-1 > b.dd && b.B[b.Len()-1] != '/' {
b.B = b.B[:b.Len()-1]
for len(b.B)-1 > b.dd && b.B[len(b.B)-1] != '/' {
b.B = b.B[:len(b.B)-1]
}
if b.Len() > 0 {
b.B = b.B[:b.Len()-1]
if len(b.B) > 0 {
b.B = b.B[:len(b.B)-1]
}
}

View File

@ -1,16 +0,0 @@
# go-format
String formatting package using Rust-style formatting directives.
Output is generally more visually-friendly than `"fmt"`, while performance is neck-and-neck.
README is WIP.
## todos
- improved verbose printing of number types
- more test cases
- improved verbose printing of string ptr types

View File

@ -1,81 +0,0 @@
package format
import (
"io"
"unicode/utf8"
"unsafe"
)
// ensure we conform to io.Writer.
var _ io.Writer = (*Buffer)(nil)
// Buffer is a simple wrapper around a byte slice.
type Buffer struct {
B []byte
}
// Write will append given byte slice to buffer, fulfilling io.Writer.
func (buf *Buffer) Write(b []byte) (int, error) {
buf.B = append(buf.B, b...)
return len(b), nil
}
// AppendByte appends given byte to the buffer.
func (buf *Buffer) AppendByte(b byte) {
buf.B = append(buf.B, b)
}
// AppendRune appends given rune to the buffer.
func (buf *Buffer) AppendRune(r rune) {
if r < utf8.RuneSelf {
buf.B = append(buf.B, byte(r))
return
}
l := buf.Len()
for i := 0; i < utf8.UTFMax; i++ {
buf.B = append(buf.B, 0)
}
n := utf8.EncodeRune(buf.B[l:buf.Len()], r)
buf.B = buf.B[:l+n]
}
// Append will append given byte slice to the buffer.
func (buf *Buffer) Append(b []byte) {
buf.B = append(buf.B, b...)
}
// AppendString appends given string to the buffer.
func (buf *Buffer) AppendString(s string) {
buf.B = append(buf.B, s...)
}
// Len returns the length of the buffer's underlying byte slice.
func (buf *Buffer) Len() int {
return len(buf.B)
}
// Cap returns the capacity of the buffer's underlying byte slice.
func (buf *Buffer) Cap() int {
return cap(buf.B)
}
// Truncate will reduce the length of the buffer by 'n'.
func (buf *Buffer) Truncate(n int) {
if n > len(buf.B) {
n = len(buf.B)
}
buf.B = buf.B[:buf.Len()-n]
}
// Reset will reset the buffer length to 0 (retains capacity).
func (buf *Buffer) Reset() {
buf.B = buf.B[:0]
}
// String returns the underlying byte slice as a string. Please note
// this value is tied directly to the underlying byte slice, if you
// write to the buffer then returned string values will also change.
func (buf *Buffer) String() string {
return *(*string)(unsafe.Pointer(&buf.B))
}

View File

@ -1,565 +0,0 @@
package format
import (
"reflect"
"strconv"
"unsafe"
)
// Formattable defines a type capable of being formatted and appended to a byte buffer.
type Formattable interface {
AppendFormat([]byte) []byte
}
// format is the object passed among the append___ formatting functions.
type format struct {
flags uint8 // 'isKey' and 'verbose' flags
drefs uint8 // current value deref count
curd uint8 // current depth
maxd uint8 // maximum depth
buf *Buffer // out buffer
}
const (
// flag bit constants.
isKeyBit = uint8(1) << 0
isValBit = uint8(1) << 1
vboseBit = uint8(1) << 2
panicBit = uint8(1) << 3
)
// AtMaxDepth returns whether format is currently at max depth.
func (f format) AtMaxDepth() bool {
return f.curd > f.maxd
}
// Derefs returns no. times current value has been dereferenced.
func (f format) Derefs() uint8 {
return f.drefs
}
// IsKey returns whether the isKey flag is set.
func (f format) IsKey() bool {
return (f.flags & isKeyBit) != 0
}
// IsValue returns whether the isVal flag is set.
func (f format) IsValue() bool {
return (f.flags & isValBit) != 0
}
// Verbose returns whether the verbose flag is set.
func (f format) Verbose() bool {
return (f.flags & vboseBit) != 0
}
// Panic returns whether the panic flag is set.
func (f format) Panic() bool {
return (f.flags & panicBit) != 0
}
// SetIsKey returns format instance with the isKey bit set to value.
func (f format) SetIsKey() format {
return format{
flags: f.flags & ^isValBit | isKeyBit,
curd: f.curd,
maxd: f.maxd,
buf: f.buf,
}
}
// SetIsValue returns format instance with the isVal bit set to value.
func (f format) SetIsValue() format {
return format{
flags: f.flags & ^isKeyBit | isValBit,
curd: f.curd,
maxd: f.maxd,
buf: f.buf,
}
}
// SetPanic returns format instance with the panic bit set to value.
func (f format) SetPanic() format {
return format{
flags: f.flags | panicBit /* handle panic as value */ | isValBit & ^isKeyBit,
curd: f.curd,
maxd: f.maxd,
buf: f.buf,
}
}
// IncrDepth returns format instance with depth incremented.
func (f format) IncrDepth() format {
return format{
flags: f.flags,
curd: f.curd + 1,
maxd: f.maxd,
buf: f.buf,
}
}
// IncrDerefs returns format instance with dereference count incremented.
func (f format) IncrDerefs() format {
return format{
flags: f.flags,
drefs: f.drefs + 1,
curd: f.curd,
maxd: f.maxd,
buf: f.buf,
}
}
// appendType appends a type using supplied type str.
func appendType(fmt format, t string) {
for i := uint8(0); i < fmt.Derefs(); i++ {
fmt.buf.AppendByte('*')
}
fmt.buf.AppendString(t)
}
// appendNilType Appends nil to buf, type included if verbose.
func appendNilType(fmt format, t string) {
if fmt.Verbose() {
fmt.buf.AppendByte('(')
appendType(fmt, t)
fmt.buf.AppendString(`)(nil)`)
} else {
fmt.buf.AppendString(`nil`)
}
}
// appendByte Appends a single byte to buf.
func appendByte(fmt format, b byte) {
if fmt.IsValue() || fmt.Verbose() {
fmt.buf.AppendString(`'` + string(b) + `'`)
} else {
fmt.buf.AppendByte(b)
}
}
// appendBytes Appends a quoted byte slice to buf.
func appendBytes(fmt format, b []byte) {
if b == nil {
// Bytes CAN be nil formatted
appendNilType(fmt, `[]byte`)
} else {
// Append bytes as slice
fmt.buf.AppendByte('[')
for _, b := range b {
fmt.buf.AppendByte(b)
fmt.buf.AppendByte(',')
}
if len(b) > 0 {
fmt.buf.Truncate(1)
}
fmt.buf.AppendByte(']')
}
}
// appendString Appends an escaped, double-quoted string to buf.
func appendString(fmt format, s string) {
switch {
// Key in a key-value pair
case fmt.IsKey():
if !strconv.CanBackquote(s) {
// Requires quoting AND escaping
fmt.buf.B = strconv.AppendQuote(fmt.buf.B, s)
} else if containsSpaceOrTab(s) {
// Contains space, needs quotes
fmt.buf.AppendString(`"` + s + `"`)
} else {
// All else write as-is
fmt.buf.AppendString(s)
}
// Value in a key-value pair (always escape+quote)
case fmt.IsValue():
fmt.buf.B = strconv.AppendQuote(fmt.buf.B, s)
// Verbose but neither key nor value (always quote)
case fmt.Verbose():
fmt.buf.AppendString(`"` + s + `"`)
// All else
default:
fmt.buf.AppendString(s)
}
}
// appendBool Appends a formatted bool to buf.
func appendBool(fmt format, b bool) {
fmt.buf.B = strconv.AppendBool(fmt.buf.B, b)
}
// appendInt Appends a formatted int to buf.
func appendInt(fmt format, i int64) {
fmt.buf.B = strconv.AppendInt(fmt.buf.B, i, 10)
}
// appendUint Appends a formatted uint to buf.
func appendUint(fmt format, u uint64) {
fmt.buf.B = strconv.AppendUint(fmt.buf.B, u, 10)
}
// appendFloat Appends a formatted float to buf.
func appendFloat(fmt format, f float64) {
fmt.buf.B = strconv.AppendFloat(fmt.buf.B, f, 'G', -1, 64)
}
// appendComplex Appends a formatted complex128 to buf.
func appendComplex(fmt format, c complex128) {
appendFloat(fmt, real(c))
fmt.buf.AppendByte('+')
appendFloat(fmt, imag(c))
fmt.buf.AppendByte('i')
}
// isNil will safely check if 'v' is nil without dealing with weird Go interface nil bullshit.
func isNil(i interface{}) bool {
e := *(*struct {
_ unsafe.Pointer // type
v unsafe.Pointer // value
})(unsafe.Pointer(&i))
return (e.v == nil)
}
// appendIfaceOrReflectValue will attempt to append as interface, falling back to reflection.
func appendIfaceOrRValue(fmt format, i interface{}) {
if !appendIface(fmt, i) {
appendRValue(fmt, reflect.ValueOf(i))
}
}
// appendValueNext checks for interface methods before performing appendRValue, checking + incr depth.
func appendRValueOrIfaceNext(fmt format, v reflect.Value) {
// Check we haven't hit max
if fmt.AtMaxDepth() {
fmt.buf.AppendString("...")
return
}
// Incr the depth
fmt = fmt.IncrDepth()
// Make actual call
if !v.CanInterface() || !appendIface(fmt, v.Interface()) {
appendRValue(fmt, v)
}
}
// appendIface parses and Appends a formatted interface value to buf.
func appendIface(fmt format, i interface{}) (ok bool) {
ok = true // default
catchPanic := func() {
if r := recover(); r != nil {
// DON'T recurse catchPanic()
if fmt.Panic() {
panic(r)
}
// Attempt to decode panic into buf
fmt.buf.AppendString(`!{PANIC=`)
appendIfaceOrRValue(fmt.SetPanic(), r)
fmt.buf.AppendByte('}')
// Ensure return
ok = true
}
}
switch i := i.(type) {
// Nil type
case nil:
fmt.buf.AppendString(`nil`)
// Reflect types
case reflect.Type:
if isNil(i) /* safer nil check */ {
appendNilType(fmt, `reflect.Type`)
} else {
appendType(fmt, `reflect.Type`)
fmt.buf.AppendString(`(` + i.String() + `)`)
}
case reflect.Value:
appendType(fmt, `reflect.Value`)
fmt.buf.AppendByte('(')
fmt.flags |= vboseBit
appendRValue(fmt, i)
fmt.buf.AppendByte(')')
// Bytes and string types
case byte:
appendByte(fmt, i)
case []byte:
appendBytes(fmt, i)
case string:
appendString(fmt, i)
// Int types
case int:
appendInt(fmt, int64(i))
case int8:
appendInt(fmt, int64(i))
case int16:
appendInt(fmt, int64(i))
case int32:
appendInt(fmt, int64(i))
case int64:
appendInt(fmt, i)
// Uint types
case uint:
appendUint(fmt, uint64(i))
// case uint8 :: this is 'byte'
case uint16:
appendUint(fmt, uint64(i))
case uint32:
appendUint(fmt, uint64(i))
case uint64:
appendUint(fmt, i)
// Float types
case float32:
appendFloat(fmt, float64(i))
case float64:
appendFloat(fmt, i)
// Bool type
case bool:
appendBool(fmt, i)
// Complex types
case complex64:
appendComplex(fmt, complex128(i))
case complex128:
appendComplex(fmt, i)
// Method types
case error:
switch {
case fmt.Verbose():
ok = false
case isNil(i) /* use safer nil check */ :
appendNilType(fmt, reflect.TypeOf(i).String())
default:
defer catchPanic()
appendString(fmt, i.Error())
}
case Formattable:
switch {
case fmt.Verbose():
ok = false
case isNil(i) /* use safer nil check */ :
appendNilType(fmt, reflect.TypeOf(i).String())
default:
defer catchPanic()
fmt.buf.B = i.AppendFormat(fmt.buf.B)
}
case interface{ String() string }:
switch {
case fmt.Verbose():
ok = false
case isNil(i) /* use safer nil check */ :
appendNilType(fmt, reflect.TypeOf(i).String())
default:
defer catchPanic()
appendString(fmt, i.String())
}
// No quick handler
default:
ok = false
}
return ok
}
// appendReflectValue will safely append a reflected value.
func appendRValue(fmt format, v reflect.Value) {
switch v.Kind() {
// String and byte types
case reflect.Uint8:
appendByte(fmt, byte(v.Uint()))
case reflect.String:
appendString(fmt, v.String())
// Float tpyes
case reflect.Float32, reflect.Float64:
appendFloat(fmt, v.Float())
// Int types
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
appendInt(fmt, v.Int())
// Uint types
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
appendUint(fmt, v.Uint())
// Complex types
case reflect.Complex64, reflect.Complex128:
appendComplex(fmt, v.Complex())
// Bool type
case reflect.Bool:
appendBool(fmt, v.Bool())
// Slice and array types
case reflect.Array:
appendArrayType(fmt, v)
case reflect.Slice:
if v.IsNil() {
appendNilType(fmt, v.Type().String())
} else {
appendArrayType(fmt, v)
}
// Map types
case reflect.Map:
if v.IsNil() {
appendNilType(fmt, v.Type().String())
} else {
appendMapType(fmt, v)
}
// Struct types
case reflect.Struct:
appendStructType(fmt, v)
// Deref'able ptr types
case reflect.Ptr, reflect.Interface:
if v.IsNil() {
appendNilType(fmt, v.Type().String())
} else {
appendRValue(fmt.IncrDerefs(), v.Elem())
}
// 'raw' pointer types
case reflect.UnsafePointer:
appendType(fmt, `unsafe.Pointer`)
fmt.buf.AppendByte('(')
if u := v.Pointer(); u != 0 {
fmt.buf.AppendString("0x")
fmt.buf.B = strconv.AppendUint(fmt.buf.B, uint64(u), 16)
} else {
fmt.buf.AppendString(`nil`)
}
fmt.buf.AppendByte(')')
case reflect.Uintptr:
appendType(fmt, `uintptr`)
fmt.buf.AppendByte('(')
if u := v.Uint(); u != 0 {
fmt.buf.AppendString("0x")
fmt.buf.B = strconv.AppendUint(fmt.buf.B, u, 16)
} else {
fmt.buf.AppendString(`nil`)
}
fmt.buf.AppendByte(')')
// Generic types we don't *exactly* handle
case reflect.Func, reflect.Chan:
if v.IsNil() {
appendNilType(fmt, v.Type().String())
} else {
fmt.buf.AppendString(v.String())
}
// Unhandled kind
default:
fmt.buf.AppendString(v.String())
}
}
// appendArrayType Appends an array of unknown type (parsed by reflection) to buf, unlike appendSliceType does NOT catch nil slice.
func appendArrayType(fmt format, v reflect.Value) {
// get no. elements
n := v.Len()
fmt.buf.AppendByte('[')
// Append values
for i := 0; i < n; i++ {
appendRValueOrIfaceNext(fmt.SetIsValue(), v.Index(i))
fmt.buf.AppendByte(',')
}
// Drop last comma
if n > 0 {
fmt.buf.Truncate(1)
}
fmt.buf.AppendByte(']')
}
// appendMapType Appends a map of unknown types (parsed by reflection) to buf.
func appendMapType(fmt format, v reflect.Value) {
// Prepend type if verbose
if fmt.Verbose() {
appendType(fmt, v.Type().String())
}
// Get a map iterator
r := v.MapRange()
n := v.Len()
fmt.buf.AppendByte('{')
// Iterate pairs
for r.Next() {
appendRValueOrIfaceNext(fmt.SetIsKey(), r.Key())
fmt.buf.AppendByte('=')
appendRValueOrIfaceNext(fmt.SetIsValue(), r.Value())
fmt.buf.AppendByte(' ')
}
// Drop last space
if n > 0 {
fmt.buf.Truncate(1)
}
fmt.buf.AppendByte('}')
}
// appendStructType Appends a struct (as a set of key-value fields) to buf.
func appendStructType(fmt format, v reflect.Value) {
// Get value type & no. fields
t := v.Type()
n := v.NumField()
// Prepend type if verbose
if fmt.Verbose() {
appendType(fmt, v.Type().String())
}
fmt.buf.AppendByte('{')
// Iterate fields
for i := 0; i < n; i++ {
vfield := v.Field(i)
tfield := t.Field(i)
// Append field name
fmt.buf.AppendString(tfield.Name)
fmt.buf.AppendByte('=')
appendRValueOrIfaceNext(fmt.SetIsValue(), vfield)
// Iter written count
fmt.buf.AppendByte(' ')
}
// Drop last space
if n > 0 {
fmt.buf.Truncate(1)
}
fmt.buf.AppendByte('}')
}
// containsSpaceOrTab checks if "s" contains space or tabs.
func containsSpaceOrTab(s string) bool {
for _, r := range s {
if r == ' ' || r == '\t' {
return true
}
}
return false
}

View File

@ -1,352 +0,0 @@
package format
import (
"strings"
)
// Formatter allows configuring value and string formatting.
type Formatter struct {
// MaxDepth specifies the max depth of fields the formatter will iterate.
// Once max depth is reached, value will simply be formatted as "...".
// e.g.
//
// MaxDepth=1
// type A struct{
// Nested B
// }
// type B struct{
// Nested C
// }
// type C struct{
// Field string
// }
//
// Append(&buf, A{}) => {Nested={Nested={Field=...}}}
MaxDepth uint8
}
// Append will append formatted form of supplied values into 'buf'.
func (f Formatter) Append(buf *Buffer, v ...interface{}) {
for _, v := range v {
appendIfaceOrRValue(format{maxd: f.MaxDepth, buf: buf}, v)
buf.AppendByte(' ')
}
if len(v) > 0 {
buf.Truncate(1)
}
}
// Appendf will append the formatted string with supplied values into 'buf'.
// Supported format directives:
// - '{}' => format supplied arg, in place
// - '{0}' => format arg at index 0 of supplied, in place
// - '{:?}' => format supplied arg verbosely, in place
// - '{:k}' => format supplied arg as key, in place
// - '{:v}' => format supplied arg as value, in place
//
// To escape either of '{}' simply append an additional brace e.g.
// - '{{' => '{'
// - '}}' => '}'
// - '{{}}' => '{}'
// - '{{:?}}' => '{:?}'
//
// More formatting directives might be included in the future.
func (f Formatter) Appendf(buf *Buffer, s string, a ...interface{}) {
const (
// ground state
modeNone = uint8(0)
// prev reached '{'
modeOpen = uint8(1)
// prev reached '}'
modeClose = uint8(2)
// parsing directive index
modeIdx = uint8(3)
// parsing directive operands
modeOp = uint8(4)
)
var (
// mode is current parsing mode
mode uint8
// arg is the current arg index
arg int
// carg is current directive-set arg index
carg int
// last is the trailing cursor to see slice windows
last int
// idx is the current index in 's'
idx int
// fmt is the base argument formatter
fmt = format{
maxd: f.MaxDepth,
buf: buf,
}
// NOTE: these functions are defined here as function
// locals as it turned out to be better for performance
// doing it this way, than encapsulating their logic in
// some kind of parsing structure. Maybe if the parser
// was pooled along with the buffers it might work out
// better, but then it makes more internal functions i.e.
// .Append() .Appendf() less accessible outside package.
//
// Currently, passing '-gcflags "-l=4"' causes a not
// insignificant decrease in ns/op, which is likely due
// to more aggressive function inlining, which this
// function can obviously stand to benefit from :)
// Str returns current string window slice, and updates
// the trailing cursor 'last' to current 'idx'
Str = func() string {
str := s[last:idx]
last = idx
return str
}
// MoveUp moves the trailing cursor 'last' just past 'idx'
MoveUp = func() {
last = idx + 1
}
// MoveUpTo moves the trailing cursor 'last' either up to
// closest '}', or current 'idx', whichever is furthest
MoveUpTo = func() {
if i := strings.IndexByte(s[idx:], '}'); i >= 0 {
idx += i
}
MoveUp()
}
// ParseIndex parses an integer from the current string
// window, updating 'last' to 'idx'. The string window
// is ASSUMED to contain only valid ASCII numbers. This
// only returns false if number exceeds platform int size
ParseIndex = func() bool {
// Get current window
str := Str()
if len(str) < 1 {
return true
}
// Index HAS to fit within platform int
if !can32bitInt(str) && !can64bitInt(str) {
return false
}
// Build integer from string
carg = 0
for _, c := range []byte(str) {
carg = carg*10 + int(c-'0')
}
return true
}
// ParseOp parses operands from the current string
// window, updating 'last' to 'idx'. The string window
// is ASSUMED to contain only valid operand ASCII. This
// returns success on parsing of operand logic
ParseOp = func() bool {
// Get current window
str := Str()
if len(str) < 1 {
return true
}
// (for now) only
// accept length = 1
if len(str) > 1 {
return false
}
switch str[0] {
case 'k':
fmt.flags |= isKeyBit
case 'v':
fmt.flags |= isValBit
case '?':
fmt.flags |= vboseBit
}
return true
}
// AppendArg will take either the directive-set, or
// iterated arg index, check within bounds of 'a' and
// append the that argument formatted to the buffer.
// On failure, it will append an error string
AppendArg = func() {
// Look for idx
if carg < 0 {
carg = arg
}
// Incr idx
arg++
if carg < len(a) {
// Append formatted argument value
appendIfaceOrRValue(fmt, a[carg])
} else {
// No argument found for index
buf.AppendString(`!{MISSING_ARG}`)
}
}
// Reset will reset the mode to ground, the flags
// to empty and parsed 'carg' to empty
Reset = func() {
mode = modeNone
fmt.flags = 0
carg = -1
}
)
for idx = 0; idx < len(s); idx++ {
// Get next char
c := s[idx]
switch mode {
// Ground mode
case modeNone:
switch c {
case '{':
// Enter open mode
buf.AppendString(Str())
mode = modeOpen
MoveUp()
case '}':
// Enter close mode
buf.AppendString(Str())
mode = modeClose
MoveUp()
}
// Encountered open '{'
case modeOpen:
switch c {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
// Starting index
mode = modeIdx
MoveUp()
case '{':
// Escaped bracket
buf.AppendByte('{')
mode = modeNone
MoveUp()
case '}':
// Format arg
AppendArg()
Reset()
MoveUp()
case ':':
// Starting operands
mode = modeOp
MoveUp()
default:
// Bad char, missing a close
buf.AppendString(`!{MISSING_CLOSE}`)
mode = modeNone
MoveUpTo()
}
// Encountered close '}'
case modeClose:
switch c {
case '}':
// Escaped close bracket
buf.AppendByte('}')
mode = modeNone
MoveUp()
default:
// Missing an open bracket
buf.AppendString(`!{MISSING_OPEN}`)
mode = modeNone
MoveUp()
}
// Preparing index
case modeIdx:
switch c {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
case ':':
if !ParseIndex() {
// Unable to parse an integer
buf.AppendString(`!{BAD_INDEX}`)
mode = modeNone
MoveUpTo()
} else {
// Starting operands
mode = modeOp
MoveUp()
}
case '}':
if !ParseIndex() {
// Unable to parse an integer
buf.AppendString(`!{BAD_INDEX}`)
} else {
// Format arg
AppendArg()
}
Reset()
MoveUp()
default:
// Not a valid index character
buf.AppendString(`!{BAD_INDEX}`)
mode = modeNone
MoveUpTo()
}
// Preparing operands
case modeOp:
switch c {
case 'k', 'v', '?':
// TODO: set flags as received
case '}':
if !ParseOp() {
// Unable to parse operands
buf.AppendString(`!{BAD_OPERAND}`)
} else {
// Format arg
AppendArg()
}
Reset()
MoveUp()
default:
// Not a valid operand char
buf.AppendString(`!{BAD_OPERAND}`)
Reset()
MoveUpTo()
}
}
}
// Append any remaining
buf.AppendString(s[last:])
}
// formatter is the default formatter instance.
var formatter = Formatter{
MaxDepth: 10,
}
// Append will append formatted form of supplied values into 'buf' using default formatter.
// See Formatter.Append() for more documentation.
func Append(buf *Buffer, v ...interface{}) {
formatter.Append(buf, v...)
}
// Appendf will append the formatted string with supplied values into 'buf' using default formatter.
// See Formatter.Appendf() for more documentation.
func Appendf(buf *Buffer, s string, a ...interface{}) {
formatter.Appendf(buf, s, a...)
}

View File

@ -1,88 +0,0 @@
package format
import (
"io"
"os"
"sync"
)
// pool is the global printer buffer pool.
var pool = sync.Pool{
New: func() interface{} {
return &Buffer{}
},
}
// getBuf fetches a buffer from pool.
func getBuf() *Buffer {
return pool.Get().(*Buffer)
}
// putBuf places a Buffer back in pool.
func putBuf(buf *Buffer) {
if buf.Cap() > 64<<10 {
return // drop large
}
buf.Reset()
pool.Put(buf)
}
// Sprint will format supplied values, returning this string.
func Sprint(v ...interface{}) string {
buf := Buffer{}
Append(&buf, v...)
return buf.String()
}
// Sprintf will format supplied format string and args, returning this string.
// See Formatter.Appendf() for more documentation.
func Sprintf(s string, a ...interface{}) string {
buf := Buffer{}
Appendf(&buf, s, a...)
return buf.String()
}
// Print will format supplied values, print this to os.Stdout.
func Print(v ...interface{}) {
Fprint(os.Stdout, v...) //nolint
}
// Printf will format supplied format string and args, printing this to os.Stdout.
// See Formatter.Appendf() for more documentation.
func Printf(s string, a ...interface{}) {
Fprintf(os.Stdout, s, a...) //nolint
}
// Println will format supplied values, append a trailing newline and print this to os.Stdout.
func Println(v ...interface{}) {
Fprintln(os.Stdout, v...) //nolint
}
// Fprint will format supplied values, writing this to an io.Writer.
func Fprint(w io.Writer, v ...interface{}) (int, error) {
buf := getBuf()
Append(buf, v...)
n, err := w.Write(buf.B)
putBuf(buf)
return n, err
}
// Fprintf will format supplied format string and args, writing this to an io.Writer.
// See Formatter.Appendf() for more documentation.
func Fprintf(w io.Writer, s string, a ...interface{}) (int, error) {
buf := getBuf()
Appendf(buf, s, a...)
n, err := w.Write(buf.B)
putBuf(buf)
return n, err
}
// Println will format supplied values, append a trailing newline and writer this to an io.Writer.
func Fprintln(w io.Writer, v ...interface{}) (int, error) {
buf := getBuf()
Append(buf, v...)
buf.AppendByte('\n')
n, err := w.Write(buf.B)
putBuf(buf)
return n, err
}

View File

@ -1,13 +0,0 @@
package format
import "strconv"
// can32bitInt returns whether it's possible for 's' to contain an int on 32bit platforms.
func can32bitInt(s string) bool {
return strconv.IntSize == 32 && (0 < len(s) && len(s) < 10)
}
// can64bitInt returns whether it's possible for 's' to contain an int on 64bit platforms.
func can64bitInt(s string) bool {
return strconv.IntSize == 64 && (0 < len(s) && len(s) < 19)
}

View File

@ -6,7 +6,7 @@ import (
"encoding/hex"
)
// Encoder defines an interface for encoding binary data
// Encoder defines an interface for encoding binary data.
type Encoder interface {
// Encode encodes the data at src into dst
Encode(dst []byte, src []byte)
@ -15,22 +15,22 @@ type Encoder interface {
EncodedLen(int) int
}
// Base32 returns a new base32 Encoder
// Base32 returns a new base32 Encoder (StdEncoding, no padding).
func Base32() Encoder {
return base32.StdEncoding.WithPadding(base64.NoPadding)
}
// Base64 returns a new base64 Encoder
// Base64 returns a new base64 Encoder (URLEncoding, no padding).
func Base64() Encoder {
return base64.URLEncoding.WithPadding(base64.NoPadding)
}
// Hex returns a new hex Encoder
// Hex returns a new hex Encoder.
func Hex() Encoder {
return &hexEncoder{}
}
// hexEncoder simply provides an empty receiver to satisfy Encoder
// hexEncoder simply provides an empty receiver to satisfy Encoder.
type hexEncoder struct{}
func (*hexEncoder) Encode(dst []byte, src []byte) {

View File

@ -1,136 +0,0 @@
package hashenc
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"hash"
"sync"
)
// Hash defines a pooled hash.Hash implementation
type Hash interface {
// Hash ensures we implement the base hash.Hash implementation
hash.Hash
// Release resets the Hash and places it back in the pool
Release()
}
// poolHash is our Hash implementation, providing a hash.Hash and a pool to return to
type poolHash struct {
hash.Hash
pool *sync.Pool
}
func (h *poolHash) Release() {
h.Reset()
h.pool.Put(h)
}
// SHA512Pool defines a pool of SHA512 hashes
type SHA512Pool interface {
// SHA512 returns a Hash implementing the SHA512 hashing algorithm
SHA512() Hash
}
// NewSHA512Pool returns a new SHA512Pool implementation
func NewSHA512Pool() SHA512Pool {
p := &sha512Pool{}
p.New = func() interface{} {
return &poolHash{
Hash: sha512.New(),
pool: &p.Pool,
}
}
return p
}
// sha512Pool is our SHA512Pool implementation, simply wrapping sync.Pool
type sha512Pool struct {
sync.Pool
}
func (p *sha512Pool) SHA512() Hash {
return p.Get().(Hash)
}
// SHA256Pool defines a pool of SHA256 hashes
type SHA256Pool interface {
// SHA256 returns a Hash implementing the SHA256 hashing algorithm
SHA256() Hash
}
// NewSHA256Pool returns a new SHA256Pool implementation
func NewSHA256Pool() SHA256Pool {
p := &sha256Pool{}
p.New = func() interface{} {
return &poolHash{
Hash: sha256.New(),
pool: &p.Pool,
}
}
return p
}
// sha256Pool is our SHA256Pool implementation, simply wrapping sync.Pool
type sha256Pool struct {
sync.Pool
}
func (p *sha256Pool) SHA256() Hash {
return p.Get().(Hash)
}
// SHA1Pool defines a pool of SHA1 hashes
type SHA1Pool interface {
SHA1() Hash
}
// NewSHA1Pool returns a new SHA1Pool implementation
func NewSHA1Pool() SHA1Pool {
p := &sha1Pool{}
p.New = func() interface{} {
return &poolHash{
Hash: sha1.New(),
pool: &p.Pool,
}
}
return p
}
// sha1Pool is our SHA1Pool implementation, simply wrapping sync.Pool
type sha1Pool struct {
sync.Pool
}
func (p *sha1Pool) SHA1() Hash {
return p.Get().(Hash)
}
// MD5Pool defines a pool of MD5 hashes
type MD5Pool interface {
MD5() Hash
}
// NewMD5Pool returns a new MD5 implementation
func NewMD5Pool() MD5Pool {
p := &md5Pool{}
p.New = func() interface{} {
return &poolHash{
Hash: md5.New(),
pool: &p.Pool,
}
}
return p
}
// md5Pool is our MD5Pool implementation, simply wrapping sync.Pool
type md5Pool struct {
sync.Pool
}
func (p *md5Pool) MD5() Hash {
return p.Get().(Hash)
}

View File

@ -18,7 +18,7 @@ type HashEncoder interface {
Size() int
}
// New returns a new HashEncoder instance based on supplied hash.Hash and Encoder supplying functions
// New returns a new HashEncoder instance based on supplied hash.Hash and Encoder supplying functions.
func New(hash hash.Hash, enc Encoder) HashEncoder {
hashSize := hash.Size()
return &henc{
@ -29,7 +29,7 @@ func New(hash hash.Hash, enc Encoder) HashEncoder {
}
}
// henc is the HashEncoder implementation
// henc is the HashEncoder implementation.
type henc struct {
hash hash.Hash
hbuf []byte

View File

@ -6,7 +6,7 @@ import (
"sync"
)
// BufioReaderPool is a pooled allocator for bufio.Reader objects
// BufioReaderPool is a pooled allocator for bufio.Reader objects.
type BufioReaderPool interface {
// Get fetches a bufio.Reader from pool and resets to supplied reader
Get(io.Reader) *bufio.Reader
@ -15,32 +15,39 @@ type BufioReaderPool interface {
Put(*bufio.Reader)
}
// NewBufioReaderPool returns a newly instantiated bufio.Reader pool
// NewBufioReaderPool returns a newly instantiated bufio.Reader pool.
func NewBufioReaderPool(size int) BufioReaderPool {
return &bufioReaderPool{
Pool: sync.Pool{
pool: sync.Pool{
New: func() interface{} {
return bufio.NewReaderSize(nil, size)
},
},
size: size,
}
}
// bufioReaderPool is our implementation of BufioReaderPool
type bufioReaderPool struct{ sync.Pool }
// bufioReaderPool is our implementation of BufioReaderPool.
type bufioReaderPool struct {
pool sync.Pool
size int
}
func (p *bufioReaderPool) Get(r io.Reader) *bufio.Reader {
br := p.Pool.Get().(*bufio.Reader)
br := p.pool.Get().(*bufio.Reader)
br.Reset(r)
return br
}
func (p *bufioReaderPool) Put(br *bufio.Reader) {
if br.Size() < p.size {
return
}
br.Reset(nil)
p.Pool.Put(br)
p.pool.Put(br)
}
// BufioWriterPool is a pooled allocator for bufio.Writer objects
// BufioWriterPool is a pooled allocator for bufio.Writer objects.
type BufioWriterPool interface {
// Get fetches a bufio.Writer from pool and resets to supplied writer
Get(io.Writer) *bufio.Writer
@ -49,27 +56,34 @@ type BufioWriterPool interface {
Put(*bufio.Writer)
}
// NewBufioWriterPool returns a newly instantiated bufio.Writer pool
// NewBufioWriterPool returns a newly instantiated bufio.Writer pool.
func NewBufioWriterPool(size int) BufioWriterPool {
return &bufioWriterPool{
Pool: sync.Pool{
pool: sync.Pool{
New: func() interface{} {
return bufio.NewWriterSize(nil, size)
},
},
size: size,
}
}
// bufioWriterPool is our implementation of BufioWriterPool
type bufioWriterPool struct{ sync.Pool }
// bufioWriterPool is our implementation of BufioWriterPool.
type bufioWriterPool struct {
pool sync.Pool
size int
}
func (p *bufioWriterPool) Get(w io.Writer) *bufio.Writer {
bw := p.Pool.Get().(*bufio.Writer)
bw := p.pool.Get().(*bufio.Writer)
bw.Reset(w)
return bw
}
func (p *bufioWriterPool) Put(bw *bufio.Writer) {
if bw.Size() < p.size {
return
}
bw.Reset(nil)
p.Pool.Put(bw)
p.pool.Put(bw)
}

View File

@ -3,16 +3,16 @@ package pools
import (
"sync"
"codeberg.org/gruf/go-bytes"
"codeberg.org/gruf/go-byteutil"
)
// BufferPool is a pooled allocator for bytes.Buffer objects
type BufferPool interface {
// Get fetches a bytes.Buffer from pool
Get() *bytes.Buffer
Get() *byteutil.Buffer
// Put places supplied bytes.Buffer in pool
Put(*bytes.Buffer)
Put(*byteutil.Buffer)
}
// NewBufferPool returns a newly instantiated bytes.Buffer pool
@ -20,7 +20,7 @@ func NewBufferPool(size int) BufferPool {
return &bufferPool{
pool: sync.Pool{
New: func() interface{} {
return &bytes.Buffer{B: make([]byte, 0, size)}
return &byteutil.Buffer{B: make([]byte, 0, size)}
},
},
size: size,
@ -33,11 +33,11 @@ type bufferPool struct {
size int
}
func (p *bufferPool) Get() *bytes.Buffer {
return p.pool.Get().(*bytes.Buffer)
func (p *bufferPool) Get() *byteutil.Buffer {
return p.pool.Get().(*byteutil.Buffer)
}
func (p *bufferPool) Put(buf *bytes.Buffer) {
func (p *bufferPool) Put(buf *byteutil.Buffer) {
if buf.Cap() < p.size {
return
}

46
vendor/codeberg.org/gruf/go-pools/henc.go generated vendored Normal file
View File

@ -0,0 +1,46 @@
package pools
import (
"hash"
"sync"
"codeberg.org/gruf/go-hashenc"
)
// HashEncoderPool is a pooled allocator for hashenc.HashEncoder objects.
type HashEncoderPool interface {
// Get fetches a hashenc.HashEncoder from pool
Get() hashenc.HashEncoder
// Put places supplied hashenc.HashEncoder back in pool
Put(hashenc.HashEncoder)
}
// NewHashEncoderPool returns a newly instantiated hashenc.HashEncoder pool.
func NewHashEncoderPool(hash func() hash.Hash, enc func() hashenc.Encoder) HashEncoderPool {
return &hencPool{
pool: sync.Pool{
New: func() interface{} {
return hashenc.New(hash(), enc())
},
},
size: hashenc.New(hash(), enc()).Size(),
}
}
// hencPool is our implementation of HashEncoderPool.
type hencPool struct {
pool sync.Pool
size int
}
func (p *hencPool) Get() hashenc.HashEncoder {
return p.pool.Get().(hashenc.HashEncoder)
}
func (p *hencPool) Put(henc hashenc.HashEncoder) {
if henc.Size() < p.size {
return
}
p.pool.Put(henc)
}

387
vendor/codeberg.org/gruf/go-pools/pool.go generated vendored Normal file
View File

@ -0,0 +1,387 @@
package pools
import (
"runtime"
"sync"
"sync/atomic"
"unsafe"
)
type Pool struct {
// New is used to instantiate new items
New func() interface{}
// Evict is called on evicted items during pool .Clean()
Evict func(interface{})
local unsafe.Pointer // ptr to []_ppool
localSz int64 // count of all elems in local
victim unsafe.Pointer // ptr to []_ppool
victimSz int64 // count of all elems in victim
mutex sync.Mutex // mutex protects new cleanups, and new allocations of local
}
// Get attempts to fetch an item from the pool, failing that allocates with supplied .New() function
func (p *Pool) Get() interface{} {
// Get local pool for proc
// (also pins proc)
pool, pid := p.pin()
if v := pool.getPrivate(); v != nil {
// local _ppool private elem acquired
runtime_procUnpin()
atomic.AddInt64(&p.localSz, -1)
return v
}
if v := pool.get(); v != nil {
// local _ppool queue elem acquired
runtime_procUnpin()
atomic.AddInt64(&p.localSz, -1)
return v
}
// Unpin before attempting slow
runtime_procUnpin()
if v := p.getSlow(pid); v != nil {
// note size decrementing
// is handled within p.getSlow()
// as we don't know if it came
// from the local or victim pools
return v
}
// Alloc new
return p.New()
}
// Put places supplied item in the proc local pool
func (p *Pool) Put(v interface{}) {
// Don't store nil
if v == nil {
return
}
// Get proc local pool
// (also pins proc)
pool, _ := p.pin()
// first try private, then queue
if !pool.setPrivate(v) {
pool.put(v)
}
runtime_procUnpin()
// Increment local pool size
atomic.AddInt64(&p.localSz, 1)
}
// Clean will drop the current victim pools, move the current local pools to its
// place and reset the local pools ptr in order to be regenerated
func (p *Pool) Clean() {
p.mutex.Lock()
// victim becomes local, local becomes nil
localPtr := atomic.SwapPointer(&p.local, nil)
victimPtr := atomic.SwapPointer(&p.victim, localPtr)
localSz := atomic.SwapInt64(&p.localSz, 0)
atomic.StoreInt64(&p.victimSz, localSz)
var victim []ppool
if victimPtr != nil {
victim = *(*[]ppool)(victimPtr)
}
// drain each of the vict _ppool items
for i := 0; i < len(victim); i++ {
ppool := &victim[i]
ppool.evict(p.Evict)
}
p.mutex.Unlock()
}
// LocalSize returns the total number of elements in all the proc-local pools
func (p *Pool) LocalSize() int64 {
return atomic.LoadInt64(&p.localSz)
}
// VictimSize returns the total number of elements in all the victim (old proc-local) pools
func (p *Pool) VictimSize() int64 {
return atomic.LoadInt64(&p.victimSz)
}
// getSlow is the slow path for fetching an element, attempting to steal from other proc's
// local pools, and failing that, from the aging-out victim pools. pid is still passed so
// not all procs start iterating from the same index
func (p *Pool) getSlow(pid int) interface{} {
// get local pools
local := p.localPools()
// Try to steal from other proc locals
for i := 0; i < len(local); i++ {
pool := &local[(pid+i+1)%len(local)]
if v := pool.get(); v != nil {
atomic.AddInt64(&p.localSz, -1)
return v
}
}
// get victim pools
victim := p.victimPools()
// Attempt to steal from victim pools
for i := 0; i < len(victim); i++ {
pool := &victim[(pid+i+1)%len(victim)]
if v := pool.get(); v != nil {
atomic.AddInt64(&p.victimSz, -1)
return v
}
}
// Set victim pools to nil (none found)
atomic.StorePointer(&p.victim, nil)
return nil
}
// localPools safely loads slice of local _ppools
func (p *Pool) localPools() []ppool {
local := atomic.LoadPointer(&p.local)
if local == nil {
return nil
}
return *(*[]ppool)(local)
}
// victimPools safely loads slice of victim _ppools
func (p *Pool) victimPools() []ppool {
victim := atomic.LoadPointer(&p.victim)
if victim == nil {
return nil
}
return *(*[]ppool)(victim)
}
// pin will get fetch pin proc to PID, fetch proc-local _ppool and current PID we're pinned to
func (p *Pool) pin() (*ppool, int) {
for {
// get local pools
local := p.localPools()
if len(local) > 0 {
// local already initialized
// pin to current proc
pid := runtime_procPin()
// check for pid local pool
if pid < len(local) {
return &local[pid], pid
}
// unpin from proc
runtime_procUnpin()
} else {
// local not yet initialized
// Check functions are set
if p.New == nil {
panic("new func must not be nil")
}
if p.Evict == nil {
panic("evict func must not be nil")
}
}
// allocate local
p.allocLocal()
}
}
// allocLocal allocates a new local pool slice, with the old length passed to check
// if pool was previously nil, or whether a change in GOMAXPROCS occurred
func (p *Pool) allocLocal() {
// get pool lock
p.mutex.Lock()
// Calculate new size to use
size := runtime.GOMAXPROCS(0)
local := p.localPools()
if len(local) != size {
// GOMAXPROCS changed, reallocate
pools := make([]ppool, size)
atomic.StorePointer(&p.local, unsafe.Pointer(&pools))
// Evict old local elements
for i := 0; i < len(local); i++ {
pool := &local[i]
pool.evict(p.Evict)
}
}
// Unlock pool
p.mutex.Unlock()
}
// _ppool is a proc local pool
type _ppool struct {
// root is the root element of the _ppool queue,
// and protects concurrent access to the queue
root unsafe.Pointer
// private is a proc private member accessible
// only to the pid this _ppool is assigned to,
// except during evict (hence the unsafe pointer)
private unsafe.Pointer
}
// ppool wraps _ppool with pad.
type ppool struct {
_ppool
// Prevents false sharing on widespread platforms with
// 128 mod (cache line size) = 0 .
pad [128 - unsafe.Sizeof(_ppool{})%128]byte
}
// getPrivate gets the proc private member
func (pp *_ppool) getPrivate() interface{} {
ptr := atomic.SwapPointer(&pp.private, nil)
if ptr == nil {
return nil
}
return *(*interface{})(ptr)
}
// setPrivate sets the proc private member (only if unset)
func (pp *_ppool) setPrivate(v interface{}) bool {
return atomic.CompareAndSwapPointer(&pp.private, nil, unsafe.Pointer(&v))
}
// get fetches an element from the queue
func (pp *_ppool) get() interface{} {
for {
// Attempt to load root elem
root := atomic.LoadPointer(&pp.root)
if root == nil {
return nil
}
// Attempt to consume root elem
if root == inUsePtr ||
!atomic.CompareAndSwapPointer(&pp.root, root, inUsePtr) {
continue
}
// Root becomes next in chain
e := (*elem)(root)
v := e.value
// Place new root back in the chain
atomic.StorePointer(&pp.root, unsafe.Pointer(e.next))
putElem(e)
return v
}
}
// put places an element in the queue
func (pp *_ppool) put(v interface{}) {
// Prepare next elem
e := getElem()
e.value = v
for {
// Attempt to load root elem
root := atomic.LoadPointer(&pp.root)
if root == inUsePtr {
continue
}
// Set the next elem value (might be nil)
e.next = (*elem)(root)
// Attempt to store this new value at root
if atomic.CompareAndSwapPointer(&pp.root, root, unsafe.Pointer(e)) {
break
}
}
}
// hook evicts all entries from pool, calling hook on each
func (pp *_ppool) evict(hook func(interface{})) {
if v := pp.getPrivate(); v != nil {
hook(v)
}
for {
v := pp.get()
if v == nil {
break
}
hook(v)
}
}
// inUsePtr is a ptr used to indicate _ppool is in use
var inUsePtr = unsafe.Pointer(&elem{
next: nil,
value: "in_use",
})
// elem defines an element in the _ppool queue
type elem struct {
next *elem
value interface{}
}
// elemPool is a simple pool of unused elements
var elemPool = struct {
root unsafe.Pointer
}{}
// getElem fetches a new elem from pool, or creates new
func getElem() *elem {
// Attempt to load root elem
root := atomic.LoadPointer(&elemPool.root)
if root == nil {
return &elem{}
}
// Attempt to consume root elem
if root == inUsePtr ||
!atomic.CompareAndSwapPointer(&elemPool.root, root, inUsePtr) {
return &elem{}
}
// Root becomes next in chain
e := (*elem)(root)
atomic.StorePointer(&elemPool.root, unsafe.Pointer(e.next))
e.next = nil
return e
}
// putElem will place element in the pool
func putElem(e *elem) {
e.value = nil
// Attempt to load root elem
root := atomic.LoadPointer(&elemPool.root)
if root == inUsePtr {
return // drop
}
// Set the next elem value (might be nil)
e.next = (*elem)(root)
// Attempt to store this new value at root
atomic.CompareAndSwapPointer(&elemPool.root, root, unsafe.Pointer(e))
}
//go:linkname runtime_procPin sync.runtime_procPin
func runtime_procPin() int
//go:linkname runtime_procUnpin sync.runtime_procUnpin
func runtime_procUnpin()

View File

@ -5,6 +5,13 @@ import (
"time"
)
// closedctx is an always closed context.
var closedctx = func() context.Context {
ctx := make(cancelctx)
close(ctx)
return ctx
}()
// ContextWithCancel returns a new context.Context impl with cancel.
func ContextWithCancel() (context.Context, context.CancelFunc) {
ctx := make(cancelctx)

View File

@ -2,6 +2,7 @@ package runners
import (
"context"
"runtime"
"sync"
)
@ -22,6 +23,12 @@ type WorkerPool struct {
// The number of workers represents how many WorkerFuncs can be executed simultaneously, and the
// queue size represents the max number of WorkerFuncs that can be queued at any one time.
func NewWorkerPool(workers int, queue int) WorkerPool {
if workers < 1 {
workers = runtime.GOMAXPROCS(0)
}
if queue < 1 {
queue = workers * 2
}
return WorkerPool{
queue: make(chan WorkerFunc, queue),
free: make(chan struct{}, workers),
@ -59,22 +66,28 @@ func (pool *WorkerPool) Running() bool {
// execute will take a queued function and pass it to a free worker when available.
func (pool *WorkerPool) execute(ctx context.Context, fn WorkerFunc) {
var acquired bool
// Set as running
pool.wait.Add(1)
select {
// Pool context cancelled
// (we fall through and let
// the function execute).
case <-ctx.Done():
pool.wait.Done()
// Free worker acquired
// Free worker acquired.
case pool.free <- struct{}{}:
acquired = true
}
go func() {
defer func() {
// defer in case panic
if acquired {
<-pool.free
}
pool.wait.Done()
}()
@ -110,8 +123,8 @@ func (pool *WorkerPool) process(ctx context.Context) {
}
// Enqueue will add provided WorkerFunc to the queue to be performed when there is a free worker.
// Note that 'fn' will ALWAYS be executed, and the supplied context will specify whether this 'fn'
// is being executed during normal pool execution, or if the pool has been stopped with <-ctx.Done().
// This will block until the function has been queued. 'fn' will ALWAYS be executed, even on pool
// close, which can be determined via context <-ctx.Done(). WorkerFuncs MUST respect the passed context.
func (pool *WorkerPool) Enqueue(fn WorkerFunc) {
// Check valid fn
if fn == nil {
@ -121,13 +134,14 @@ func (pool *WorkerPool) Enqueue(fn WorkerFunc) {
select {
// Pool context cancelled
case <-pool.svc.Done():
fn(closedctx)
// Placed fn in queue
case pool.queue <- fn:
}
}
// EnqueueNoBlock performs Enqueue but returns false if queue size is at max. Else, true.
// EnqueueNoBlock attempts Enqueue but returns false if not executed.
func (pool *WorkerPool) EnqueueNoBlock(fn WorkerFunc) bool {
// Check valid fn
if fn == nil {

View File

@ -1,7 +1,8 @@
package kv
import (
"codeberg.org/gruf/go-errors"
"errors"
"codeberg.org/gruf/go-mutexes"
"codeberg.org/gruf/go-store/storage"
)

View File

@ -1,9 +1,9 @@
package kv
import (
"errors"
"io"
"codeberg.org/gruf/go-errors"
"codeberg.org/gruf/go-mutexes"
)

View File

@ -1,7 +1,9 @@
package storage
import (
"bytes"
"crypto/sha256"
"fmt"
"io"
"io/fs"
"os"
@ -9,8 +11,9 @@ import (
"sync"
"syscall"
"codeberg.org/gruf/go-bytes"
"codeberg.org/gruf/go-errors"
"codeberg.org/gruf/go-byteutil"
"codeberg.org/gruf/go-errors/v2"
"codeberg.org/gruf/go-fastcopy"
"codeberg.org/gruf/go-hashenc"
"codeberg.org/gruf/go-pools"
"codeberg.org/gruf/go-store/util"
@ -34,6 +37,9 @@ type BlockConfig struct {
// BlockSize is the chunking size to use when splitting and storing blocks of data
BlockSize int
// ReadBufSize is the buffer size to use when reading node files
ReadBufSize int
// WriteBufSize is the buffer size to use when writing file streams (PutStream)
WriteBufSize int
@ -87,6 +93,7 @@ type BlockStorage struct {
config BlockConfig // cfg is the supplied configuration for this store
hashPool sync.Pool // hashPool is this store's hashEncoder pool
bufpool pools.BufferPool // bufpool is this store's bytes.Buffer pool
cppool fastcopy.CopyPool // cppool is the prepared io copier with buffer pool
lock *Lock // lock is the opened lockfile for this storage instance
// NOTE:
@ -154,8 +161,8 @@ func OpenBlock(path string, cfg *BlockConfig) (*BlockStorage, error) {
bufSz = config.WriteBufSize
}
// Return new BlockStorage
return &BlockStorage{
// Prepare BlockStorage
st := &BlockStorage{
path: path,
blockPath: pb.Join(path, blockPathPrefix),
nodePath: pb.Join(path, nodePathPrefix),
@ -167,7 +174,12 @@ func OpenBlock(path string, cfg *BlockConfig) (*BlockStorage, error) {
},
bufpool: pools.NewBufferPool(bufSz),
lock: lock,
}, nil
}
// Set copypool buffer size
st.cppool.Buffer(config.ReadBufSize)
return st, nil
}
// Clean implements storage.Clean()
@ -297,7 +309,7 @@ func (st *BlockStorage) Clean() error {
for key := range nodes {
nodeKeys = append(nodeKeys, key)
}
return errCorruptNodes.Extend("%v", nodeKeys)
return fmt.Errorf("store/storage: corrupted nodes: %v", nodeKeys)
}
return nil
@ -337,7 +349,7 @@ func (st *BlockStorage) ReadStream(key string) (io.ReadCloser, error) {
file, err := open(npath, defaultFileROFlags)
if err != nil {
st.lock.Done()
return nil, err
return nil, errSwapNotFound(err)
}
defer file.Close()
@ -347,13 +359,12 @@ func (st *BlockStorage) ReadStream(key string) (io.ReadCloser, error) {
// Write file contents to node
node := node{}
_, err = io.CopyBuffer(
_, err = st.cppool.Copy(
&nodeWriter{
node: &node,
buf: hbuf,
},
file,
nil,
)
if err != nil {
st.lock.Done()
@ -375,14 +386,14 @@ func (st *BlockStorage) readBlock(key string) ([]byte, error) {
// Attempt to open RO file
file, err := open(bpath, defaultFileROFlags)
if err != nil {
return nil, err
return nil, wrap(errCorruptNode, err)
}
defer file.Close()
// Wrap the file in a compressor
cFile, err := st.config.Compression.Reader(file)
if err != nil {
return nil, err
return nil, wrap(errCorruptNode, err)
}
defer cFile.Close()
@ -470,10 +481,10 @@ loop:
sum := hc.EncodeSum(buf.B)
// Append to the node's hashes
node.hashes = append(node.hashes, sum.String())
node.hashes = append(node.hashes, sum)
// If already on disk, skip
has, err := st.statBlock(sum.StringPtr())
has, err := st.statBlock(sum)
if err != nil {
st.bufpool.Put(buf)
return err
@ -497,7 +508,7 @@ loop:
}()
// Write block to store at hash
err = st.writeBlock(sum.StringPtr(), buf.B[:n])
err = st.writeBlock(sum, buf.B[:n])
if err != nil {
onceErr.Store(err)
return
@ -564,7 +575,7 @@ func (st *BlockStorage) writeBlock(hash string, value []byte) error {
// Attempt to open RW file
file, err := open(bpath, defaultFileRWFlags)
if err != nil {
if err == ErrAlreadyExists {
if err == syscall.EEXIST {
err = nil /* race issue describe in struct NOTE */
}
return err
@ -626,8 +637,12 @@ func (st *BlockStorage) Remove(key string) error {
return ErrClosed
}
// Attempt to remove file
return os.Remove(kpath)
// Remove at path (we know this is file)
if err := unlink(kpath); err != nil {
return errSwapNotFound(err)
}
return nil
}
// Close implements Storage.Close()
@ -762,7 +777,7 @@ func (r *nodeReader) Read(b []byte) (int, error) {
// which is useful when calculated node file is being read from the store
type nodeWriter struct {
node *node
buf *bytes.Buffer
buf *byteutil.Buffer
}
func (w *nodeWriter) Write(b []byte) (int, error) {
@ -874,7 +889,7 @@ func newHashEncoder() *hashEncoder {
}
// EncodeSum encodes the src data and returns resulting bytes, only valid until next call to EncodeSum()
func (henc *hashEncoder) EncodeSum(src []byte) bytes.Bytes {
func (henc *hashEncoder) EncodeSum(src []byte) string {
henc.henc.EncodeSum(henc.ebuf, src)
return bytes.ToBytes(henc.ebuf)
return string(henc.ebuf)
}

View File

@ -318,8 +318,12 @@ func (st *DiskStorage) Remove(key string) error {
return ErrClosed
}
// Attempt to remove file
return os.Remove(kpath)
// Remove at path (we know this is file)
if err := unlink(kpath); err != nil {
return errSwapNotFound(err)
}
return nil
}
// Close implements Storage.Close()

View File

@ -1,52 +1,65 @@
package storage
import (
"fmt"
"errors"
"syscall"
)
// errorString is our own simple error type
type errorString string
// Error implements error
func (e errorString) Error() string {
return string(e)
}
// Extend appends extra information to an errorString
func (e errorString) Extend(s string, a ...interface{}) errorString {
return errorString(string(e) + ": " + fmt.Sprintf(s, a...))
}
var (
// ErrClosed is returned on operations on a closed storage
ErrClosed = errorString("store/storage: closed")
ErrClosed = errors.New("store/storage: closed")
// ErrNotFound is the error returned when a key cannot be found in storage
ErrNotFound = errorString("store/storage: key not found")
ErrNotFound = errors.New("store/storage: key not found")
// ErrAlreadyExist is the error returned when a key already exists in storage
ErrAlreadyExists = errorString("store/storage: key already exists")
ErrAlreadyExists = errors.New("store/storage: key already exists")
// ErrInvalidkey is the error returned when an invalid key is passed to storage
ErrInvalidKey = errorString("store/storage: invalid key")
// errPathIsFile is returned when a path for a disk config is actually a file
errPathIsFile = errorString("store/storage: path is file")
// errNoHashesWritten is returned when no blocks are written for given input value
errNoHashesWritten = errorString("storage/storage: no hashes written")
// errInvalidNode is returned when read on an invalid node in the store is attempted
errInvalidNode = errorString("store/storage: invalid node")
// errCorruptNodes is returned when nodes with missing blocks are found during a BlockStorage clean
errCorruptNodes = errorString("store/storage: corrupted nodes")
ErrInvalidKey = errors.New("store/storage: invalid key")
// ErrAlreadyLocked is returned on fail opening a storage lockfile
ErrAlreadyLocked = errorString("store/storage: storage lock already open")
ErrAlreadyLocked = errors.New("store/storage: storage lock already open")
// errPathIsFile is returned when a path for a disk config is actually a file
errPathIsFile = errors.New("store/storage: path is file")
// errNoHashesWritten is returned when no blocks are written for given input value
errNoHashesWritten = errors.New("storage/storage: no hashes written")
// errInvalidNode is returned when read on an invalid node in the store is attempted
errInvalidNode = errors.New("store/storage: invalid node")
// errCorruptNode is returned when a block fails to be opened / read during read of a node.
errCorruptNode = errors.New("store/storage: corrupted node")
)
// wrappedError allows wrapping together an inner with outer error.
type wrappedError struct {
inner error
outer error
}
// wrap will return a new wrapped error from given inner and outer errors.
func wrap(outer, inner error) *wrappedError {
return &wrappedError{
inner: inner,
outer: outer,
}
}
func (e *wrappedError) Is(target error) bool {
return e.outer == target || e.inner == target
}
func (e *wrappedError) Error() string {
return e.outer.Error() + ": " + e.inner.Error()
}
func (e *wrappedError) Unwrap() error {
return e.inner
}
// errSwapNoop performs no error swaps
func errSwapNoop(err error) error {
return err

View File

@ -9,8 +9,8 @@ import (
const (
// default file permission bits
defaultDirPerms = 0755
defaultFilePerms = 0644
defaultDirPerms = 0o755
defaultFilePerms = 0o644
// default file open flags
defaultFileROFlags = syscall.O_RDONLY
@ -22,7 +22,7 @@ const (
// These functions are for opening storage files,
// not necessarily for e.g. initial setup (OpenFile)
// open should not be called directly
// open should not be called directly.
func open(path string, flags int) (*os.File, error) {
var fd int
err := util.RetryOnEINTR(func() (err error) {
@ -35,7 +35,7 @@ func open(path string, flags int) (*os.File, error) {
return os.NewFile(uintptr(fd), path), nil
}
// stat checks for a file on disk
// stat checks for a file on disk.
func stat(path string) (bool, error) {
var stat syscall.Stat_t
err := util.RetryOnEINTR(func() error {
@ -49,3 +49,17 @@ func stat(path string) (bool, error) {
}
return true, nil
}
// unlink removes a file (not dir!) on disk.
func unlink(path string) error {
return util.RetryOnEINTR(func() error {
return syscall.Unlink(path)
})
}
// rmdir removes a dir (not file!) on disk.
func rmdir(path string) error {
return util.RetryOnEINTR(func() error {
return syscall.Rmdir(path)
})
}

25
vendor/modules.txt vendored
View File

@ -1,34 +1,37 @@
# codeberg.org/gruf/go-bitutil v1.0.0
## explicit; go 1.16
codeberg.org/gruf/go-bitutil
# codeberg.org/gruf/go-bytes v1.0.2
## explicit; go 1.14
codeberg.org/gruf/go-bytes
# codeberg.org/gruf/go-byteutil v1.0.0
## explicit; go 1.16
codeberg.org/gruf/go-byteutil
# codeberg.org/gruf/go-debug v1.1.2
## explicit; go 1.16
codeberg.org/gruf/go-debug
# codeberg.org/gruf/go-errors v1.0.5
## explicit; go 1.15
codeberg.org/gruf/go-errors
# codeberg.org/gruf/go-errors/v2 v2.0.1
## explicit; go 1.16
codeberg.org/gruf/go-errors/v2
# codeberg.org/gruf/go-fastcopy v1.1.1
## explicit; go 1.17
codeberg.org/gruf/go-fastcopy
# codeberg.org/gruf/go-fastpath v1.0.2
# codeberg.org/gruf/go-fastpath v1.0.3
## explicit; go 1.14
codeberg.org/gruf/go-fastpath
# codeberg.org/gruf/go-format v1.0.3
## explicit; go 1.17
codeberg.org/gruf/go-format
# codeberg.org/gruf/go-hashenc v1.0.1
# codeberg.org/gruf/go-hashenc v1.0.2
## explicit; go 1.16
codeberg.org/gruf/go-hashenc
# codeberg.org/gruf/go-mutexes v1.1.2
## explicit; go 1.14
codeberg.org/gruf/go-mutexes
# codeberg.org/gruf/go-pools v1.0.2
# codeberg.org/gruf/go-pools v1.1.0
## explicit; go 1.16
codeberg.org/gruf/go-pools
# codeberg.org/gruf/go-runners v1.2.0
# codeberg.org/gruf/go-runners v1.2.1
## explicit; go 1.14
codeberg.org/gruf/go-runners
# codeberg.org/gruf/go-store v1.3.6
# codeberg.org/gruf/go-store v1.3.7
## explicit; go 1.14
codeberg.org/gruf/go-store/kv
codeberg.org/gruf/go-store/storage