diff options
Diffstat (limited to 'vendor/codeberg.org')
42 files changed, 2427 insertions, 2480 deletions
diff --git a/vendor/codeberg.org/gruf/go-errors/data.go b/vendor/codeberg.org/gruf/go-errors/data.go index 3b242f03c..b5226172c 100644 --- a/vendor/codeberg.org/gruf/go-errors/data.go +++ b/vendor/codeberg.org/gruf/go-errors/data.go @@ -4,17 +4,9 @@ import ( "fmt" "sync" - "codeberg.org/gruf/go-bytes" - "codeberg.org/gruf/go-logger" + "codeberg.org/gruf/go-format" ) -// global logfmt data formatter. -var logfmt = logger.TextFormat{ - Strict: false, - Verbose: true, - MaxDepth: 5, -} - // KV is a structure for setting key-value pairs in ErrorData. type KV struct { Key string @@ -31,7 +23,7 @@ type ErrorData interface { Append(...KV) // Implement byte slice representation formatter. - logger.Formattable + format.Formattable // Implement string representation formatter. fmt.Stringer @@ -89,13 +81,22 @@ func (d *errorData) Append(kvs ...KV) { } func (d *errorData) AppendFormat(b []byte) []byte { - buf := bytes.Buffer{B: b} + buf := format.Buffer{B: b} d.mu.Lock() buf.B = append(buf.B, '{') + + // Append data as kv pairs for i := range d.data { - logfmt.AppendKey(&buf, d.data[i].Key) - logfmt.AppendValue(&buf, d.data[i].Value) + 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 diff --git a/vendor/codeberg.org/gruf/go-logger/LICENSE b/vendor/codeberg.org/gruf/go-format/LICENSE index b7c4417ac..b7c4417ac 100644 --- a/vendor/codeberg.org/gruf/go-logger/LICENSE +++ b/vendor/codeberg.org/gruf/go-format/LICENSE diff --git a/vendor/codeberg.org/gruf/go-format/README.md b/vendor/codeberg.org/gruf/go-format/README.md new file mode 100644 index 000000000..7126e215e --- /dev/null +++ b/vendor/codeberg.org/gruf/go-format/README.md @@ -0,0 +1,16 @@ +# 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 + diff --git a/vendor/codeberg.org/gruf/go-format/buffer.go b/vendor/codeberg.org/gruf/go-format/buffer.go new file mode 100644 index 000000000..393f2fcd3 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-format/buffer.go @@ -0,0 +1,81 @@ +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)) +} diff --git a/vendor/codeberg.org/gruf/go-format/format.go b/vendor/codeberg.org/gruf/go-format/format.go new file mode 100644 index 000000000..856fe890e --- /dev/null +++ b/vendor/codeberg.org/gruf/go-format/format.go @@ -0,0 +1,565 @@ +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 +} diff --git a/vendor/codeberg.org/gruf/go-format/formatter.go b/vendor/codeberg.org/gruf/go-format/formatter.go new file mode 100644 index 000000000..640fa3f04 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-format/formatter.go @@ -0,0 +1,352 @@ +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...) +} diff --git a/vendor/codeberg.org/gruf/go-format/print.go b/vendor/codeberg.org/gruf/go-format/print.go new file mode 100644 index 000000000..288e6af10 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-format/print.go @@ -0,0 +1,88 @@ +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 +} diff --git a/vendor/codeberg.org/gruf/go-format/util.go b/vendor/codeberg.org/gruf/go-format/util.go new file mode 100644 index 000000000..68a9e2de3 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-format/util.go @@ -0,0 +1,13 @@ +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) +} diff --git a/vendor/codeberg.org/gruf/go-logger/README.md b/vendor/codeberg.org/gruf/go-logger/README.md deleted file mode 100644 index 57410ea87..000000000 --- a/vendor/codeberg.org/gruf/go-logger/README.md +++ /dev/null @@ -1,13 +0,0 @@ -Fast levelled logging package with customizable formatting. - -Supports logging in 2 modes: -- no locks, fastest possible logging, no guarantees for io.Writer thread safety -- mutex locks during writes, still far faster than standard library logger - -Running without locks isn't likely to cause you any issues*, but if it does, you can wrap your `io.Writer` using `AddSafety()` when instantiating your new Logger. Even when running the benchmarks, this library has no printing issues without locks, so in most cases you'll be fine, but the safety is there if you need it. - -*most logging libraries advertising high speeds are likely not performing mutex locks, which is why with this library you have the option to opt-in/out of them. - -Note there are 2 uses of the unsafe package: -- safer interface nil value checks, uses similar logic to reflect package to check if the value in the internal fat pointer is nil -- casting a byte slice to string to allow sharing of similar byte and string methods, performs same logic as `strings.Builder{}.String()`
\ No newline at end of file diff --git a/vendor/codeberg.org/gruf/go-logger/clock.go b/vendor/codeberg.org/gruf/go-logger/clock.go deleted file mode 100644 index cc7d7ed0c..000000000 --- a/vendor/codeberg.org/gruf/go-logger/clock.go +++ /dev/null @@ -1,21 +0,0 @@ -package logger - -import ( - "sync" - "time" - - "codeberg.org/gruf/go-nowish" -) - -var ( - clock = nowish.Clock{} - clockOnce = sync.Once{} -) - -// startClock starts the global nowish clock. -func startClock() { - clockOnce.Do(func() { - clock.Start(time.Millisecond * 100) - clock.SetFormat("2006-01-02 15:04:05") - }) -} diff --git a/vendor/codeberg.org/gruf/go-logger/default.go b/vendor/codeberg.org/gruf/go-logger/default.go deleted file mode 100644 index 3fd65c6b1..000000000 --- a/vendor/codeberg.org/gruf/go-logger/default.go +++ /dev/null @@ -1,107 +0,0 @@ -package logger - -import ( - "os" - "sync" -) - -var ( - instance *Logger - instanceOnce = sync.Once{} -) - -// Default returns the default Logger instance. -func Default() *Logger { - instanceOnce.Do(func() { instance = New(os.Stdout) }) - return instance -} - -// Debug prints the provided arguments with the debug prefix to the global Logger instance. -func Debug(a ...interface{}) { - Default().Debug(a...) -} - -// Debugf prints the provided format string and arguments with the debug prefix to the global Logger instance. -func Debugf(s string, a ...interface{}) { - Default().Debugf(s, a...) -} - -// Info prints the provided arguments with the info prefix to the global Logger instance. -func Info(a ...interface{}) { - Default().Info(a...) -} - -// Infof prints the provided format string and arguments with the info prefix to the global Logger instance. -func Infof(s string, a ...interface{}) { - Default().Infof(s, a...) -} - -// Warn prints the provided arguments with the warn prefix to the global Logger instance. -func Warn(a ...interface{}) { - Default().Warn(a...) -} - -// Warnf prints the provided format string and arguments with the warn prefix to the global Logger instance. -func Warnf(s string, a ...interface{}) { - Default().Warnf(s, a...) -} - -// Error prints the provided arguments with the error prefix to the global Logger instance. -func Error(a ...interface{}) { - Default().Error(a...) -} - -// Errorf prints the provided format string and arguments with the error prefix to the global Logger instance. -func Errorf(s string, a ...interface{}) { - Default().Errorf(s, a...) -} - -// Fatal prints the provided arguments with the fatal prefix to the global Logger instance before exiting the program with os.Exit(1). -func Fatal(a ...interface{}) { - Default().Fatal(a...) -} - -// Fatalf prints the provided format string and arguments with the fatal prefix to the global Logger instance before exiting the program with os.Exit(1). -func Fatalf(s string, a ...interface{}) { - Default().Fatalf(s, a...) -} - -// Log prints the provided arguments with the supplied log level to the global Logger instance. -func Log(lvl LEVEL, a ...interface{}) { - Default().Log(lvl, a...) -} - -// Logf prints the provided format string and arguments with the supplied log level to the global Logger instance. -func Logf(lvl LEVEL, s string, a ...interface{}) { - Default().Logf(lvl, s, a...) -} - -// LogFields prints the provided fields formatted as key-value pairs at the supplied log level to the global Logger instance. -func LogFields(lvl LEVEL, fields map[string]interface{}) { - Default().LogFields(lvl, fields) -} - -// LogValues prints the provided values formatted as-so at the supplied log level to the global Logger instance. -func LogValues(lvl LEVEL, a ...interface{}) { - Default().LogValues(lvl, a...) -} - -// Print simply prints provided arguments to the global Logger instance. -func Print(a ...interface{}) { - Default().Print(a...) -} - -// Printf simply prints provided the provided format string and arguments to the global Logger instance. -func Printf(s string, a ...interface{}) { - Default().Printf(s, a...) -} - -// PrintFields prints the provided fields formatted as key-value pairs to the global Logger instance. -func PrintFields(fields map[string]interface{}) { - Default().PrintFields(fields) -} - -// PrintValues prints the provided values formatted as-so to the global Logger instance. -func PrintValues(a ...interface{}) { - Default().PrintValues(a...) -} diff --git a/vendor/codeberg.org/gruf/go-logger/entry.go b/vendor/codeberg.org/gruf/go-logger/entry.go deleted file mode 100644 index 11e383086..000000000 --- a/vendor/codeberg.org/gruf/go-logger/entry.go +++ /dev/null @@ -1,385 +0,0 @@ -package logger - -import ( - "context" - "fmt" - "time" - - "codeberg.org/gruf/go-bytes" -) - -// Entry defines an entry in the log, it is NOT safe for concurrent use -type Entry struct { - ctx context.Context - lvl LEVEL - buf *bytes.Buffer - log *Logger -} - -// Context returns the current set Entry context.Context -func (e *Entry) Context() context.Context { - return e.ctx -} - -// WithContext updates Entry context value to the supplied -func (e *Entry) WithContext(ctx context.Context) *Entry { - e.ctx = ctx - return e -} - -// Level appends the supplied level to the log entry, and sets the entry level. -// Please note this CAN be called and append log levels multiple times -func (e *Entry) Level(lvl LEVEL) *Entry { - e.log.Format.AppendLevel(e.buf, lvl) - e.buf.WriteByte(' ') - e.lvl = lvl - return e -} - -// Timestamp appends the current timestamp to the log entry. Please note this -// CAN be called and append the timestamp multiple times -func (e *Entry) Timestamp() *Entry { - e.log.Format.AppendTimestamp(e.buf, clock.NowFormat()) - e.buf.WriteByte(' ') - return e -} - -// TimestampIf performs Entry.Timestamp() only IF timestamping is enabled for the Logger. -// Please note this CAN be called multiple times -func (e *Entry) TimestampIf() *Entry { - if e.log.Timestamp { - e.Timestamp() - } - return e -} - -// Hooks applies currently set Hooks to the Entry. Please note this CAN be -// called and perform the Hooks multiple times -func (e *Entry) Hooks() *Entry { - for _, hook := range e.log.Hooks { - hook.Do(e) - } - return e -} - -// Byte appends a byte value to the log entry -func (e *Entry) Byte(value byte) *Entry { - e.log.Format.AppendByte(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// ByteField appends a byte value as key-value pair to the log entry -func (e *Entry) ByteField(key string, value byte) *Entry { - e.log.Format.AppendKey(e.buf, key) - e.log.Format.AppendByte(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// Bytes appends a byte slice value as to the log entry -func (e *Entry) Bytes(value []byte) *Entry { - e.log.Format.AppendBytes(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// BytesField appends a byte slice value as key-value pair to the log entry -func (e *Entry) BytesField(key string, value []byte) *Entry { - e.log.Format.AppendKey(e.buf, key) - e.log.Format.AppendBytes(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// Str appends a string value to the log entry -func (e *Entry) Str(value string) *Entry { - e.log.Format.AppendString(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// StrField appends a string value as key-value pair to the log entry -func (e *Entry) StrField(key string, value string) *Entry { - e.log.Format.AppendKey(e.buf, key) - e.log.Format.AppendString(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// Strs appends a string slice value to the log entry -func (e *Entry) Strs(value []string) *Entry { - e.log.Format.AppendStrings(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// StrsField appends a string slice value as key-value pair to the log entry -func (e *Entry) StrsField(key string, value []string) *Entry { - e.log.Format.AppendKey(e.buf, key) - e.log.Format.AppendStrings(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// Int appends an int value to the log entry -func (e *Entry) Int(value int) *Entry { - e.log.Format.AppendInt(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// IntField appends an int value as key-value pair to the log entry -func (e *Entry) IntField(key string, value int) *Entry { - e.log.Format.AppendKey(e.buf, key) - e.log.Format.AppendInt(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// Ints appends an int slice value to the log entry -func (e *Entry) Ints(value []int) *Entry { - e.log.Format.AppendInts(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// IntsField appends an int slice value as key-value pair to the log entry -func (e *Entry) IntsField(key string, value []int) *Entry { - e.log.Format.AppendKey(e.buf, key) - e.log.Format.AppendInts(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// Uint appends a uint value to the log entry -func (e *Entry) Uint(value uint) *Entry { - e.log.Format.AppendUint(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// UintField appends a uint value as key-value pair to the log entry -func (e *Entry) UintField(key string, value uint) *Entry { - e.log.Format.AppendKey(e.buf, key) - e.log.Format.AppendUint(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// Uints appends a uint slice value to the log entry -func (e *Entry) Uints(value []uint) *Entry { - e.log.Format.AppendUints(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// UintsField appends a uint slice value as key-value pair to the log entry -func (e *Entry) UintsField(key string, value []uint) *Entry { - e.log.Format.AppendKey(e.buf, key) - e.log.Format.AppendUints(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// Float appends a float value to the log entry -func (e *Entry) Float(value float64) *Entry { - e.log.Format.AppendFloat(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// FloatField appends a float value as key-value pair to the log entry -func (e *Entry) FloatField(key string, value float64) *Entry { - e.log.Format.AppendKey(e.buf, key) - e.log.Format.AppendFloat(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// Floats appends a float slice value to the log entry -func (e *Entry) Floats(value []float64) *Entry { - e.log.Format.AppendFloats(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// FloatsField appends a float slice value as key-value pair to the log entry -func (e *Entry) FloatsField(key string, value []float64) *Entry { - e.log.Format.AppendKey(e.buf, key) - e.log.Format.AppendFloats(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// Bool appends a bool value to the log entry -func (e *Entry) Bool(value bool) *Entry { - e.log.Format.AppendBool(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// BoolField appends a bool value as key-value pair to the log entry -func (e *Entry) BoolField(key string, value bool) *Entry { - e.log.Format.AppendKey(e.buf, key) - e.log.Format.AppendBool(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// Bools appends a bool slice value to the log entry -func (e *Entry) Bools(value []bool) *Entry { - e.log.Format.AppendBools(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// BoolsField appends a bool slice value as key-value pair to the log entry -func (e *Entry) BoolsField(key string, value []bool) *Entry { - e.log.Format.AppendKey(e.buf, key) - e.log.Format.AppendBools(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// Time appends a time.Time value to the log entry -func (e *Entry) Time(value time.Time) *Entry { - e.log.Format.AppendTime(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// TimeField appends a time.Time value as key-value pair to the log entry -func (e *Entry) TimeField(key string, value time.Time) *Entry { - e.log.Format.AppendKey(e.buf, key) - e.log.Format.AppendTime(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// Times appends a time.Time slice value to the log entry -func (e *Entry) Times(value []time.Time) *Entry { - e.log.Format.AppendTimes(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// TimesField appends a time.Time slice value as key-value pair to the log entry -func (e *Entry) TimesField(key string, value []time.Time) *Entry { - e.log.Format.AppendKey(e.buf, key) - e.log.Format.AppendTimes(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// DurationField appends a time.Duration value to the log entry -func (e *Entry) Duration(value time.Duration) *Entry { - e.log.Format.AppendDuration(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// DurationField appends a time.Duration value as key-value pair to the log entry -func (e *Entry) DurationField(key string, value time.Duration) *Entry { - e.log.Format.AppendKey(e.buf, key) - e.log.Format.AppendDuration(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// Durations appends a time.Duration slice value to the log entry -func (e *Entry) Durations(value []time.Duration) *Entry { - e.log.Format.AppendDurations(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// DurationsField appends a time.Duration slice value as key-value pair to the log entry -func (e *Entry) DurationsField(key string, value []time.Duration) *Entry { - e.log.Format.AppendKey(e.buf, key) - e.log.Format.AppendDurations(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// Field appends an interface value as key-value pair to the log entry -func (e *Entry) Field(key string, value interface{}) *Entry { - e.log.Format.AppendKey(e.buf, key) - e.log.Format.AppendValue(e.buf, value) - e.buf.WriteByte(' ') - return e -} - -// Fields appends a map of key-value pairs to the log entry -func (e *Entry) Fields(fields map[string]interface{}) *Entry { - for key, value := range fields { - e.Field(key, value) - } - return e -} - -// Values appends the given values to the log entry formatted as values, without a key. -func (e *Entry) Values(values ...interface{}) *Entry { - for _, value := range values { - e.log.Format.AppendValue(e.buf, value) - e.buf.WriteByte(' ') - } - return e -} - -// Append will append the given args formatted using fmt.Sprint(a...) to the Entry. -func (e *Entry) Append(a ...interface{}) *Entry { - fmt.Fprint(e.buf, a...) - e.buf.WriteByte(' ') - return e -} - -// Appendf will append the given format string and args using fmt.Sprintf(s, a...) to the Entry. -func (e *Entry) Appendf(s string, a ...interface{}) *Entry { - fmt.Fprintf(e.buf, s, a...) - e.buf.WriteByte(' ') - return e -} - -// Msg appends the fmt.Sprint() formatted final message to the log and calls .Send() -func (e *Entry) Msg(a ...interface{}) { - e.log.Format.AppendMsg(e.buf, a...) - e.Send() -} - -// Msgf appends the fmt.Sprintf() formatted final message to the log and calls .Send() -func (e *Entry) Msgf(s string, a ...interface{}) { - e.log.Format.AppendMsgf(e.buf, s, a...) - e.Send() -} - -// Send triggers write of the log entry, skipping if the entry's log LEVEL -// is below the currently set Logger level, and releases the Entry back to -// the Logger's Entry pool. So it is NOT safe to continue using this Entry -// object after calling .Send(), .Msg() or .Msgf() -func (e *Entry) Send() { - // If nothing to do, return - if e.lvl < e.log.Level || e.buf.Len() < 1 { - e.reset() - return - } - - // Ensure a final new line - if e.buf.B[e.buf.Len()-1] != '\n' { - e.buf.WriteByte('\n') - } - - // Write, reset and release - e.log.Output.Write(e.buf.B) - e.reset() -} - -func (e *Entry) reset() { - // Reset all - e.ctx = nil - e.buf.Reset() - e.lvl = unset - - // Release to pool - e.log.pool.Put(e) -} diff --git a/vendor/codeberg.org/gruf/go-logger/format.go b/vendor/codeberg.org/gruf/go-logger/format.go deleted file mode 100644 index 3901ea37f..000000000 --- a/vendor/codeberg.org/gruf/go-logger/format.go +++ /dev/null @@ -1,87 +0,0 @@ -package logger - -import ( - "time" - - "codeberg.org/gruf/go-bytes" -) - -// Check our types impl LogFormat -var _ LogFormat = &TextFormat{} - -// Formattable defines a type capable of writing a string formatted form -// of itself to a supplied byte buffer, and returning the resulting byte -// buffer. Implementing this will greatly speed up formatting of custom -// types passed to LogFormat (assuming they implement checking for this). -type Formattable interface { - AppendFormat([]byte) []byte -} - -// LogFormat defines a method of formatting log entries -type LogFormat interface { - // AppendKey appends given key to the log buffer - AppendKey(buf *bytes.Buffer, key string) - - // AppendLevel appends given log level as key-value pair to the log buffer - AppendLevel(buf *bytes.Buffer, lvl LEVEL) - - // AppendTimestamp appends given timestamp string as key-value pair to the log buffer - AppendTimestamp(buf *bytes.Buffer, fmtNow string) - - // AppendValue appends given interface formatted as value to the log buffer - AppendValue(buf *bytes.Buffer, value interface{}) - - // AppendByte appends given byte value to the log buffer - AppendByte(buf *bytes.Buffer, value byte) - - // AppendBytes appends given byte slice value to the log buffer - AppendBytes(buf *bytes.Buffer, value []byte) - - // AppendString appends given string value to the log buffer - AppendString(buf *bytes.Buffer, value string) - - // AppendStrings appends given string slice value to the log buffer - AppendStrings(buf *bytes.Buffer, value []string) - - // AppendBool appends given bool value to the log buffer - AppendBool(buf *bytes.Buffer, value bool) - - // AppendBools appends given bool slice value to the log buffer - AppendBools(buf *bytes.Buffer, value []bool) - - // AppendInt appends given int value to the log buffer - AppendInt(buf *bytes.Buffer, value int) - - // AppendInts appends given int slice value to the log buffer - AppendInts(buf *bytes.Buffer, value []int) - - // AppendUint appends given uint value to the log buffer - AppendUint(buf *bytes.Buffer, value uint) - - // AppendUints appends given uint slice value to the log buffer - AppendUints(buf *bytes.Buffer, value []uint) - - // AppendFloat appends given float value to the log buffer - AppendFloat(buf *bytes.Buffer, value float64) - - // AppendFloats appends given float slice value to the log buffer - AppendFloats(buf *bytes.Buffer, value []float64) - - // AppendTime appends given time value to the log buffer - AppendTime(buf *bytes.Buffer, value time.Time) - - // AppendTimes appends given time slice value to the log buffer - AppendTimes(buf *bytes.Buffer, value []time.Time) - - // AppendDuration appends given duration value to the log buffer - AppendDuration(buf *bytes.Buffer, value time.Duration) - - // AppendDurations appends given duration slice value to the log buffer - AppendDurations(buf *bytes.Buffer, value []time.Duration) - - // AppendMsg appends given msg as key-value pair to the log buffer using fmt.Sprint(...) formatting - AppendMsg(buf *bytes.Buffer, a ...interface{}) - - // AppendMsgf appends given msg format string as key-value pair to the log buffer using fmt.Sprintf(...) formatting - AppendMsgf(buf *bytes.Buffer, s string, a ...interface{}) -} diff --git a/vendor/codeberg.org/gruf/go-logger/format_text.go b/vendor/codeberg.org/gruf/go-logger/format_text.go deleted file mode 100644 index f9c90f887..000000000 --- a/vendor/codeberg.org/gruf/go-logger/format_text.go +++ /dev/null @@ -1,914 +0,0 @@ -package logger - -import ( - stdfmt "fmt" - "reflect" - "strconv" - "time" - "unsafe" - - "codeberg.org/gruf/go-bytes" -) - -// DefaultTextFormat is the default TextFormat instance -var DefaultTextFormat = TextFormat{ - Strict: false, - Verbose: false, - MaxDepth: 10, - Levels: DefaultLevels(), -} - -// TextFormat is the default LogFormat implementation, with very similar formatting to the -// standard "fmt" package's '%#v' operator. The main difference being that pointers are -// dereferenced as far as possible in order to reach a printable value. It is also *mildly* faster. -type TextFormat struct { - // Strict defines whether to use strict key-value pair formatting, i.e. should the level - // timestamp and msg be formatted as key-value pairs (with forced quoting for msg) - Strict bool - - // Verbose defines whether to increase output verbosity, i.e. include types with nil values - // and force values implementing .String() / .AppendFormat() to be printed as a struct etc. - Verbose bool - - // MaxDepth specifies the max depth of fields the formatter will iterate - MaxDepth uint8 - - // Levels defines the map of log LEVELs to level strings - Levels Levels -} - -// fmt returns a new format instance based on receiver TextFormat and given buffer -func (f TextFormat) fmt(buf *bytes.Buffer) format { - var flags uint8 - if f.Verbose { - flags |= vboseBit - } - return format{ - flags: flags, - curd: 0, - maxd: f.MaxDepth, - buf: buf, - } -} - -func (f TextFormat) AppendKey(buf *bytes.Buffer, key string) { - if len(key) > 0 { - // only append if key is non-zero length - appendString(f.fmt(buf).SetIsKey(true), key) - buf.WriteByte('=') - } -} - -func (f TextFormat) AppendLevel(buf *bytes.Buffer, lvl LEVEL) { - if f.Strict { - // Strict format, append level key - buf.WriteString(`level=`) - buf.WriteString(f.Levels.Get(lvl)) - return - } - - // Write level string - buf.WriteByte('[') - buf.WriteString(f.Levels.Get(lvl)) - buf.WriteByte(']') -} - -func (f TextFormat) AppendTimestamp(buf *bytes.Buffer, now string) { - if f.Strict { - // Strict format, use key and quote - buf.WriteString(`time=`) - appendString(f.fmt(buf), now) - return - } - - // Write time as-is - buf.WriteString(now) -} - -func (f TextFormat) AppendValue(buf *bytes.Buffer, value interface{}) { - appendIfaceOrRValue(f.fmt(buf).SetIsKey(false), value) -} - -func (f TextFormat) AppendByte(buf *bytes.Buffer, value byte) { - appendByte(f.fmt(buf), value) -} - -func (f TextFormat) AppendBytes(buf *bytes.Buffer, value []byte) { - appendBytes(f.fmt(buf), value) -} - -func (f TextFormat) AppendString(buf *bytes.Buffer, value string) { - appendString(f.fmt(buf), value) -} - -func (f TextFormat) AppendStrings(buf *bytes.Buffer, value []string) { - appendStringSlice(f.fmt(buf), value) -} - -func (f TextFormat) AppendBool(buf *bytes.Buffer, value bool) { - appendBool(f.fmt(buf), value) -} - -func (f TextFormat) AppendBools(buf *bytes.Buffer, value []bool) { - appendBoolSlice(f.fmt(buf), value) -} - -func (f TextFormat) AppendInt(buf *bytes.Buffer, value int) { - appendInt(f.fmt(buf), int64(value)) -} - -func (f TextFormat) AppendInts(buf *bytes.Buffer, value []int) { - appendIntSlice(f.fmt(buf), value) -} - -func (f TextFormat) AppendUint(buf *bytes.Buffer, value uint) { - appendUint(f.fmt(buf), uint64(value)) -} - -func (f TextFormat) AppendUints(buf *bytes.Buffer, value []uint) { - appendUintSlice(f.fmt(buf), value) -} - -func (f TextFormat) AppendFloat(buf *bytes.Buffer, value float64) { - appendFloat(f.fmt(buf), value) -} - -func (f TextFormat) AppendFloats(buf *bytes.Buffer, value []float64) { - appendFloatSlice(f.fmt(buf), value) -} - -func (f TextFormat) AppendTime(buf *bytes.Buffer, value time.Time) { - appendTime(f.fmt(buf), value) -} - -func (f TextFormat) AppendTimes(buf *bytes.Buffer, value []time.Time) { - appendTimeSlice(f.fmt(buf), value) -} - -func (f TextFormat) AppendDuration(buf *bytes.Buffer, value time.Duration) { - appendDuration(f.fmt(buf), value) -} - -func (f TextFormat) AppendDurations(buf *bytes.Buffer, value []time.Duration) { - appendDurationSlice(f.fmt(buf), value) -} - -func (f TextFormat) AppendMsg(buf *bytes.Buffer, a ...interface{}) { - if f.Strict { - // Strict format, use key and quote - buf.WriteString(`msg=`) - buf.B = strconv.AppendQuote(buf.B, stdfmt.Sprint(a...)) - return - } - - // Write message as-is - stdfmt.Fprint(buf, a...) -} - -func (f TextFormat) AppendMsgf(buf *bytes.Buffer, s string, a ...interface{}) { - if f.Strict { - // Strict format, use key and quote - buf.WriteString(`msg=`) - buf.B = strconv.AppendQuote(buf.B, stdfmt.Sprintf(s, a...)) - return - } - - // Write message as-is - stdfmt.Fprintf(buf, s, a...) -} - -// 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 *bytes.Buffer // out buffer -} - -const ( - // flag bit constants - isKeyBit = uint8(1) << 0 - vboseBit = uint8(1) << 1 -) - -// 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 -} - -// Verbose returns whether the verbose flag is set. -func (f format) Verbose() bool { - return (f.flags & vboseBit) != 0 -} - -// SetIsKey returns format instance with the isKey bit set to value. -func (f format) SetIsKey(is bool) format { - flags := f.flags - if is { - flags |= isKeyBit - } else { - flags &= ^isKeyBit - } - return format{ - flags: flags, - drefs: f.drefs, - 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, - drefs: f.drefs, - 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.WriteByte('*') - } - fmt.buf.WriteString(t) -} - -// appendNilType writes nil to buf, type included if verbose. -func appendNilType(fmt format, t string) { - if fmt.Verbose() { - fmt.buf.WriteByte('(') - appendType(fmt, t) - fmt.buf.WriteString(`)(nil)`) - } else { - fmt.buf.WriteString(`nil`) - } -} - -// appendNilFace writes nil to buf, type included if verbose. -func appendNilIface(fmt format, i interface{}) { - if fmt.Verbose() { - fmt.buf.WriteByte('(') - appendType(fmt, reflect.TypeOf(i).String()) - fmt.buf.WriteString(`)(nil)`) - } else { - fmt.buf.WriteString(`nil`) - } -} - -// appendNilRValue writes nil to buf, type included if verbose. -func appendNilRValue(fmt format, v reflect.Value) { - if fmt.Verbose() { - fmt.buf.WriteByte('(') - appendType(fmt, v.Type().String()) - fmt.buf.WriteString(`)(nil)`) - } else { - fmt.buf.WriteString(`nil`) - } -} - -// appendByte writes a single byte to buf -func appendByte(fmt format, b byte) { - fmt.buf.WriteByte(b) -} - -// appendBytes writes a quoted byte slice to buf -func appendBytes(fmt format, b []byte) { - if !fmt.IsKey() && b == nil { - // Values CAN be nil formatted - appendNilType(fmt, `[]byte`) - } else { - // unsafe cast as string to prevent reallocation - appendString(fmt, *(*string)(unsafe.Pointer(&b))) - } -} - -// appendString writes an escaped, double-quoted string to buf -func appendString(fmt format, s string) { - if !fmt.IsKey() || !strconv.CanBackquote(s) { - // All non-keys and multiline keys get quoted + escaped - fmt.buf.B = strconv.AppendQuote(fmt.buf.B, s) - return - } else if containsSpaceOrTab(s) { - // Key containing spaces/tabs, quote this - fmt.buf.WriteByte('"') - fmt.buf.WriteString(s) - fmt.buf.WriteByte('"') - return - } - - // Safe to leave unquoted - fmt.buf.WriteString(s) -} - -// appendStringSlice writes a slice of strings to buf -func appendStringSlice(fmt format, s []string) { - // Check for nil slice - if s == nil { - appendNilType(fmt, `[]string`) - return - } - - fmt.buf.WriteByte('[') - - // Write elements - for _, s := range s { - appendString(fmt.SetIsKey(false), s) - fmt.buf.WriteByte(',') - } - - // Drop last comma - if len(s) > 0 { - fmt.buf.Truncate(1) - } - - fmt.buf.WriteByte(']') -} - -// appendBool writes a formatted bool to buf -func appendBool(fmt format, b bool) { - fmt.buf.B = strconv.AppendBool(fmt.buf.B, b) -} - -// appendBool writes a slice of formatted bools to buf -func appendBoolSlice(fmt format, b []bool) { - // Check for nil slice - if b == nil { - appendNilType(fmt, `[]bool`) - return - } - - fmt.buf.WriteByte('[') - - // Write elements - for _, b := range b { - appendBool(fmt, b) - fmt.buf.WriteByte(',') - } - - // Drop last comma - if len(b) > 0 { - fmt.buf.Truncate(1) - } - - fmt.buf.WriteByte(']') -} - -// appendInt writes a formatted int to buf -func appendInt(fmt format, i int64) { - fmt.buf.B = strconv.AppendInt(fmt.buf.B, i, 10) -} - -// appendIntSlice writes a slice of formatted int to buf -func appendIntSlice(fmt format, i []int) { - // Check for nil slice - if i == nil { - appendNilType(fmt, `[]int`) - return - } - - fmt.buf.WriteByte('[') - - // Write elements - for _, i := range i { - appendInt(fmt, int64(i)) - fmt.buf.WriteByte(',') - } - - // Drop last comma - if len(i) > 0 { - fmt.buf.Truncate(1) - } - - fmt.buf.WriteByte(']') -} - -// appendUint writes a formatted uint to buf -func appendUint(fmt format, u uint64) { - fmt.buf.B = strconv.AppendUint(fmt.buf.B, u, 10) -} - -// appendUintSlice writes a slice of formatted uint to buf -func appendUintSlice(fmt format, u []uint) { - // Check for nil slice - if u == nil { - appendNilType(fmt, `[]uint`) - return - } - - fmt.buf.WriteByte('[') - - // Write elements - for _, u := range u { - appendUint(fmt, uint64(u)) - fmt.buf.WriteByte(',') - } - - // Drop last comma - if len(u) > 0 { - fmt.buf.Truncate(1) - } - - fmt.buf.WriteByte(']') -} - -// appendFloat writes a formatted float to buf -func appendFloat(fmt format, f float64) { - fmt.buf.B = strconv.AppendFloat(fmt.buf.B, f, 'G', -1, 64) -} - -// appendFloatSlice writes a slice formatted floats to buf -func appendFloatSlice(fmt format, f []float64) { - // Check for nil slice - if f == nil { - appendNilType(fmt, `[]float64`) - return - } - - fmt.buf.WriteByte('[') - - // Write elements - for _, f := range f { - appendFloat(fmt, f) - fmt.buf.WriteByte(',') - } - - // Drop last comma - if len(f) > 0 { - fmt.buf.Truncate(1) - } - - fmt.buf.WriteByte(']') -} - -// appendTime writes a formatted, quoted time string to buf -func appendTime(fmt format, t time.Time) { - appendString(fmt.SetIsKey(true), t.Format(time.RFC1123)) -} - -// appendTimeSlice writes a slice of formatted time strings to buf -func appendTimeSlice(fmt format, t []time.Time) { - // Check for nil slice - if t == nil { - appendNilType(fmt, `[]time.Time`) - return - } - - fmt.buf.WriteByte('[') - - // Write elements - for _, t := range t { - appendString(fmt.SetIsKey(true), t.Format(time.RFC1123)) - fmt.buf.WriteByte(',') - } - - // Drop last comma - if len(t) > 0 { - fmt.buf.Truncate(1) - } - - fmt.buf.WriteByte(']') -} - -// appendDuration writes a formatted, quoted duration string to buf -func appendDuration(fmt format, d time.Duration) { - appendString(fmt.SetIsKey(true), d.String()) -} - -// appendDurationSlice writes a slice of formatted, quoted duration strings to buf -func appendDurationSlice(fmt format, d []time.Duration) { - // Check for nil slice - if d == nil { - appendNilType(fmt, `[]time.Duration`) - return - } - - fmt.buf.WriteByte('[') - - // Write elements - for _, d := range d { - appendString(fmt.SetIsKey(true), d.String()) - fmt.buf.WriteByte(',') - } - - // Drop last comma - if len(d) > 0 { - fmt.buf.Truncate(1) - } - - fmt.buf.WriteByte(']') -} - -// appendComplex writes a formatted complex128 to buf -func appendComplex(fmt format, c complex128) { - appendFloat(fmt, real(c)) - fmt.buf.WriteByte('+') - appendFloat(fmt, imag(c)) - fmt.buf.WriteByte('i') -} - -// appendComplexSlice writes a slice of formatted complex128s to buf -func appendComplexSlice(fmt format, c []complex128) { - // Check for nil slice - if c == nil { - appendNilType(fmt, `[]complex128`) - return - } - - fmt.buf.WriteByte('[') - - // Write elements - for _, c := range c { - appendComplex(fmt, c) - fmt.buf.WriteByte(',') - } - - // Drop last comma - if len(c) > 0 { - fmt.buf.Truncate(1) - } - - fmt.buf.WriteByte(']') -} - -// notNil will safely check if 'v' is nil without dealing with weird Go interface nil bullshit. -func notNil(i interface{}) bool { - // cast to get fat pointer - e := *(*struct { - typeOf unsafe.Pointer // ignored - valueOf unsafe.Pointer - })(unsafe.Pointer(&i)) - - // check if value part is nil - return (e.valueOf != nil) -} - -// appendIfaceOrRValueNext performs appendIfaceOrRValue checking + incr depth -func appendIfaceOrRValueNext(fmt format, i interface{}) { - // Check we haven't hit max - if fmt.AtMaxDepth() { - fmt.buf.WriteString("...") - return - } - - // Incr the depth - fmt = fmt.IncrDepth() - - // Make actual call - appendIfaceOrRValue(fmt, i) -} - -// 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)) - } -} - -// appendValueOrIfaceNext performs appendRValueOrIface checking + incr depth -func appendRValueOrIfaceNext(fmt format, v reflect.Value) { - // Check we haven't hit max - if fmt.AtMaxDepth() { - fmt.buf.WriteString("...") - return - } - - // Incr the depth - fmt = fmt.IncrDepth() - - // Make actual call - appendRValueOrIface(fmt, v) -} - -// appendRValueOrIface will attempt to interface the reflect.Value, falling back to using this directly -func appendRValueOrIface(fmt format, v reflect.Value) { - if !v.CanInterface() || !appendIface(fmt, v.Interface()) { - appendRValue(fmt, v) - } -} - -// appendIface parses and writes a formatted interface value to buf -func appendIface(fmt format, i interface{}) bool { - switch i := i.(type) { - case nil: - fmt.buf.WriteString(`nil`) - case byte: - appendByte(fmt, i) - case []byte: - appendBytes(fmt, i) - case string: - appendString(fmt, i) - case []string: - appendStringSlice(fmt, i) - 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) - case []int: - appendIntSlice(fmt, i) - case uint: - appendUint(fmt, uint64(i)) - case uint16: - appendUint(fmt, uint64(i)) - case uint32: - appendUint(fmt, uint64(i)) - case uint64: - appendUint(fmt, i) - case []uint: - appendUintSlice(fmt, i) - case float32: - appendFloat(fmt, float64(i)) - case float64: - appendFloat(fmt, i) - case []float64: - appendFloatSlice(fmt, i) - case bool: - appendBool(fmt, i) - case []bool: - appendBoolSlice(fmt, i) - case time.Time: - appendTime(fmt, i) - case []time.Time: - appendTimeSlice(fmt, i) - case time.Duration: - appendDuration(fmt, i) - case []time.Duration: - appendDurationSlice(fmt, i) - case complex64: - appendComplex(fmt, complex128(i)) - case complex128: - appendComplex(fmt, i) - case []complex128: - appendComplexSlice(fmt, i) - case map[string]interface{}: - appendIfaceMap(fmt, i) - case error: - if notNil(i) /* use safer nil check */ { - appendString(fmt, i.Error()) - } else { - appendNilIface(fmt, i) - } - case Formattable: - switch { - // catch nil case first - case !notNil(i): - appendNilIface(fmt, i) - - // not permitted - case fmt.Verbose(): - return false - - // use func - default: - fmt.buf.B = i.AppendFormat(fmt.buf.B) - } - case stdfmt.Stringer: - switch { - // catch nil case first - case !notNil(i): - appendNilIface(fmt, i) - - // not permitted - case fmt.Verbose(): - return false - - // use func - default: - appendString(fmt, i.String()) - } - default: - return false // could not handle - } - - return true -} - -// appendReflectValue will safely append a reflected value -func appendRValue(fmt format, v reflect.Value) { - switch v.Kind() { - case reflect.Float32, reflect.Float64: - appendFloat(fmt, v.Float()) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - appendInt(fmt, v.Int()) - case reflect.Uint8: - appendByte(fmt, uint8(v.Uint())) - case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: - appendUint(fmt, v.Uint()) - case reflect.Bool: - appendBool(fmt, v.Bool()) - case reflect.Array: - appendArrayType(fmt, v) - case reflect.Slice: - appendSliceType(fmt, v) - case reflect.Map: - appendMapType(fmt, v) - case reflect.Struct: - appendStructType(fmt, v) - case reflect.Ptr: - if v.IsNil() { - appendNilRValue(fmt, v) - } else { - appendRValue(fmt.IncrDerefs(), v.Elem()) - } - case reflect.UnsafePointer: - fmt.buf.WriteString("(unsafe.Pointer)") - fmt.buf.WriteByte('(') - if u := v.Pointer(); u != 0 { - fmt.buf.WriteString("0x") - fmt.buf.B = strconv.AppendUint(fmt.buf.B, uint64(u), 16) - } else { - fmt.buf.WriteString(`nil`) - } - fmt.buf.WriteByte(')') - case reflect.Uintptr: - fmt.buf.WriteString("(uintptr)") - fmt.buf.WriteByte('(') - if u := v.Uint(); u != 0 { - fmt.buf.WriteString("0x") - fmt.buf.B = strconv.AppendUint(fmt.buf.B, u, 16) - } else { - fmt.buf.WriteString(`nil`) - } - fmt.buf.WriteByte(')') - case reflect.String: - appendString(fmt, v.String()) - case reflect.Complex64, reflect.Complex128: - appendComplex(fmt, v.Complex()) - case reflect.Func, reflect.Chan, reflect.Interface: - if v.IsNil() { - appendNilRValue(fmt, v) - } else { - fmt.buf.WriteString(v.String()) - } - default: - fmt.buf.WriteString(v.String()) - } -} - -// appendIfaceMap writes a map of key-value pairs (as a set of fields) to buf -func appendIfaceMap(fmt format, v map[string]interface{}) { - // Catch nil map - if v == nil { - appendNilType(fmt, `map[string]interface{}`) - return - } - - fmt.buf.WriteByte('{') - - // Write map pairs! - for key, value := range v { - appendString(fmt.SetIsKey(true), key) - fmt.buf.WriteByte('=') - appendIfaceOrRValueNext(fmt.SetIsKey(false), value) - fmt.buf.WriteByte(' ') - } - - // Drop last space - if len(v) > 0 { - fmt.buf.Truncate(1) - } - - fmt.buf.WriteByte('}') -} - -// appendArrayType writes 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.WriteByte('[') - - // Write values - for i := 0; i < n; i++ { - appendRValueOrIfaceNext(fmt.SetIsKey(false), v.Index(i)) - fmt.buf.WriteByte(',') - } - - // Drop last comma - if n > 0 { - fmt.buf.Truncate(1) - } - - fmt.buf.WriteByte(']') -} - -// appendSliceType writes a slice of unknown type (parsed by reflection) to buf -func appendSliceType(fmt format, v reflect.Value) { - if v.IsNil() { - appendNilRValue(fmt, v) - } else { - appendArrayType(fmt, v) - } -} - -// appendMapType writes a map of unknown types (parsed by reflection) to buf -func appendMapType(fmt format, v reflect.Value) { - // Catch nil map - if v.IsNil() { - appendNilRValue(fmt, v) - return - } - - // Get a map iterator - r := v.MapRange() - n := v.Len() - - fmt.buf.WriteByte('{') - - // Iterate pairs - for r.Next() { - appendRValueOrIfaceNext(fmt.SetIsKey(true), r.Key()) - fmt.buf.WriteByte('=') - appendRValueOrIfaceNext(fmt.SetIsKey(false), r.Value()) - fmt.buf.WriteByte(' ') - } - - // Drop last space - if n > 0 { - fmt.buf.Truncate(1) - } - - fmt.buf.WriteByte('}') -} - -// appendStructType writes 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() - w := 0 - - // If verbose, append the type - - fmt.buf.WriteByte('{') - - // Iterate fields - for i := 0; i < n; i++ { - vfield := v.Field(i) - name := t.Field(i).Name - - // Append field name - appendString(fmt.SetIsKey(true), name) - fmt.buf.WriteByte('=') - - if !vfield.CanInterface() { - // This is an unexported field - appendRValue(fmt.SetIsKey(false), vfield) - } else { - // This is an exported field! - appendRValueOrIfaceNext(fmt.SetIsKey(false), vfield) - } - - // Iter written count - fmt.buf.WriteByte(' ') - w++ - } - - // Drop last space - if w > 0 { - fmt.buf.Truncate(1) - } - - fmt.buf.WriteByte('}') -} - -// 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 -} diff --git a/vendor/codeberg.org/gruf/go-logger/hook.go b/vendor/codeberg.org/gruf/go-logger/hook.go deleted file mode 100644 index 2345ca93b..000000000 --- a/vendor/codeberg.org/gruf/go-logger/hook.go +++ /dev/null @@ -1,13 +0,0 @@ -package logger - -// Hook defines a log Entry modifier -type Hook interface { - Do(*Entry) -} - -// HookFunc is a simple adapter to allow functions to satisfy the Hook interface -type HookFunc func(*Entry) - -func (hook HookFunc) Do(entry *Entry) { - hook(entry) -} diff --git a/vendor/codeberg.org/gruf/go-logger/level.go b/vendor/codeberg.org/gruf/go-logger/level.go deleted file mode 100644 index 0a076c246..000000000 --- a/vendor/codeberg.org/gruf/go-logger/level.go +++ /dev/null @@ -1,38 +0,0 @@ -package logger - -// LEVEL defines a level of logging -type LEVEL uint8 - -// Available levels of logging. -const ( - unset LEVEL = ^LEVEL(0) - DEBUG LEVEL = 5 - INFO LEVEL = 10 - WARN LEVEL = 15 - ERROR LEVEL = 20 - FATAL LEVEL = 25 -) - -var unknownLevel = "unknown" - -// Levels defines a mapping of log LEVELs to formatted level strings -type Levels [^LEVEL(0)]string - -// DefaultLevels returns the default set of log levels -func DefaultLevels() Levels { - return Levels{ - DEBUG: "DEBUG", - INFO: "INFO", - WARN: "WARN", - ERROR: "ERROR", - FATAL: "FATAL", - } -} - -// Get fetches the level string for the provided value, or "unknown" -func (l Levels) Get(lvl LEVEL) string { - if str := l[int(lvl)]; str != "" { - return str - } - return unknownLevel -} diff --git a/vendor/codeberg.org/gruf/go-logger/logger.go b/vendor/codeberg.org/gruf/go-logger/logger.go deleted file mode 100644 index 94d3ab8ca..000000000 --- a/vendor/codeberg.org/gruf/go-logger/logger.go +++ /dev/null @@ -1,187 +0,0 @@ -package logger - -import ( - "context" - "fmt" - "io" - "os" - "sync" - "sync/atomic" - - "codeberg.org/gruf/go-bytes" -) - -type Logger struct { - // Hooks defines a list of hooks which are called before an entry - // is written. This should NOT be modified while the Logger is in use - Hooks []Hook - - // Level is the current log LEVEL, entries at level below the - // currently set level will not be output. This should NOT - // be modified while the Logger is in use - Level LEVEL - - // Timestamp defines whether to automatically append timestamps - // to entries written via Logger convience methods and specifically - // Entry.TimestampIf(). This should NOT be modified while Logger in use - Timestamp bool - - // Format is the log entry LogFormat to use. This should NOT - // be modified while the Logger is in use - Format LogFormat - - // BufferSize is the Entry buffer size to use when allocating - // new Entry objects. This should be modified atomically - BufSize int64 - - // Output is the log's output writer. This should NOT be - // modified while the Logger is in use - Output io.Writer - - // entry pool - pool sync.Pool -} - -// New returns a new Logger instance with defaults -func New(out io.Writer) *Logger { - return NewWith(0 /* all */, true, DefaultTextFormat, 512, out) -} - -// NewWith returns a new Logger instance with supplied configuration -func NewWith(lvl LEVEL, timestamp bool, fmt LogFormat, bufsize int64, out io.Writer) *Logger { - // Create new logger object - log := &Logger{ - Level: lvl, - Timestamp: timestamp, - Format: fmt, - BufSize: bufsize, - Output: out, - } - - // Ensure clock running - startClock() - - // Set-up logger Entry pool - log.pool.New = func() interface{} { - return &Entry{ - lvl: unset, - buf: &bytes.Buffer{B: make([]byte, 0, atomic.LoadInt64(&log.BufSize))}, - log: log, - } - } - - return log -} - -// Entry returns a new Entry from the Logger's pool with background context -func (l *Logger) Entry() *Entry { - entry, _ := l.pool.Get().(*Entry) - entry.ctx = context.Background() - return entry -} - -// Debug prints the provided arguments with the debug prefix -func (l *Logger) Debug(a ...interface{}) { - l.Log(DEBUG, a...) -} - -// Debugf prints the provided format string and arguments with the debug prefix -func (l *Logger) Debugf(s string, a ...interface{}) { - l.Logf(DEBUG, s, a...) -} - -// Info prints the provided arguments with the info prefix -func (l *Logger) Info(a ...interface{}) { - l.Log(INFO, a...) -} - -// Infof prints the provided format string and arguments with the info prefix -func (l *Logger) Infof(s string, a ...interface{}) { - l.Logf(INFO, s, a...) -} - -// Warn prints the provided arguments with the warn prefix -func (l *Logger) Warn(a ...interface{}) { - l.Log(WARN, a...) -} - -// Warnf prints the provided format string and arguments with the warn prefix -func (l *Logger) Warnf(s string, a ...interface{}) { - l.Logf(WARN, s, a...) -} - -// Error prints the provided arguments with the error prefix -func (l *Logger) Error(a ...interface{}) { - l.Log(ERROR, a...) -} - -// Errorf prints the provided format string and arguments with the error prefix -func (l *Logger) Errorf(s string, a ...interface{}) { - l.Logf(ERROR, s, a...) -} - -// Fatal prints provided arguments with the fatal prefix before exiting the program -// with os.Exit(1) -func (l *Logger) Fatal(a ...interface{}) { - defer os.Exit(1) - l.Log(FATAL, a...) -} - -// Fatalf prints provided the provided format string and arguments with the fatal prefix -// before exiting the program with os.Exit(1) -func (l *Logger) Fatalf(s string, a ...interface{}) { - defer os.Exit(1) - l.Logf(FATAL, s, a...) -} - -// Log prints the provided arguments at the supplied log level -func (l *Logger) Log(lvl LEVEL, a ...interface{}) { - if lvl >= l.Level { - l.Entry().TimestampIf().Level(lvl).Hooks().Msg(a...) - } -} - -// Logf prints the provided format string and arguments at the supplied log level -func (l *Logger) Logf(lvl LEVEL, s string, a ...interface{}) { - if lvl >= l.Level { - l.Entry().TimestampIf().Level(lvl).Hooks().Msgf(s, a...) - } -} - -// LogFields prints the provided fields formatted as key-value pairs at the supplied log level -func (l *Logger) LogFields(lvl LEVEL, fields map[string]interface{}) { - if lvl >= l.Level { - l.Entry().TimestampIf().Level(lvl).Fields(fields).Hooks().Send() - } -} - -// LogValues prints the provided values formatted as-so at the supplied log level -func (l *Logger) LogValues(lvl LEVEL, a ...interface{}) { - if lvl >= l.Level { - l.Entry().TimestampIf().Level(lvl).Values(a...).Hooks().Send() - } -} - -// Print simply prints provided arguments -func (l *Logger) Print(a ...interface{}) { - e := l.Entry().TimestampIf() - fmt.Fprint(e.buf, a...) - e.Send() -} - -// Printf simply prints provided the provided format string and arguments -func (l *Logger) Printf(s string, a ...interface{}) { - e := l.Entry().TimestampIf() - fmt.Fprintf(e.buf, s, a...) - e.Send() -} - -// PrintFields prints the provided fields formatted as key-value pairs -func (l *Logger) PrintFields(fields map[string]interface{}) { - l.Entry().TimestampIf().Fields(fields).Send() -} - -// PrintValues prints the provided values formatted as-so -func (l *Logger) PrintValues(a ...interface{}) { - l.Entry().TimestampIf().Values(a...).Send() -} diff --git a/vendor/codeberg.org/gruf/go-logger/writer.go b/vendor/codeberg.org/gruf/go-logger/writer.go deleted file mode 100644 index 72321f518..000000000 --- a/vendor/codeberg.org/gruf/go-logger/writer.go +++ /dev/null @@ -1,29 +0,0 @@ -package logger - -import ( - "io" - "io/ioutil" - "sync" -) - -// AddSafety wraps an io.Writer to provide mutex locking protection -func AddSafety(w io.Writer) io.Writer { - if w == nil { - w = ioutil.Discard - } else if sw, ok := w.(*safeWriter); ok { - return sw - } - return &safeWriter{wr: w} -} - -// safeWriter wraps an io.Writer to provide mutex locking on write -type safeWriter struct { - wr io.Writer - mu sync.Mutex -} - -func (w *safeWriter) Write(b []byte) (int, error) { - w.mu.Lock() - defer w.mu.Unlock() - return w.wr.Write(b) -} diff --git a/vendor/codeberg.org/gruf/go-mutexes/map.go b/vendor/codeberg.org/gruf/go-mutexes/map.go index ea917ee5e..cb31a9543 100644 --- a/vendor/codeberg.org/gruf/go-mutexes/map.go +++ b/vendor/codeberg.org/gruf/go-mutexes/map.go @@ -1,105 +1,322 @@ package mutexes import ( + "runtime" "sync" + "sync/atomic" ) +// locktype defines maskable mutexmap lock types. +type locktype uint8 + +const ( + // possible lock types. + lockTypeRead = locktype(1) << 0 + lockTypeWrite = locktype(1) << 1 + lockTypeMap = locktype(1) << 2 + + // possible mutexmap states. + stateUnlockd = uint8(0) + stateRLocked = uint8(1) + stateLocked = uint8(2) + stateInUse = uint8(3) +) + +// permitLockType returns if provided locktype is permitted to go ahead in current state. +func permitLockType(state uint8, lt locktype) bool { + switch state { + // Unlocked state + // (all allowed) + case stateUnlockd: + return true + + // Keys locked, no state lock. + // (don't allow map locks) + case stateInUse: + return lt&lockTypeMap == 0 + + // Read locked + // (only allow read locks) + case stateRLocked: + return lt&lockTypeRead != 0 + + // Write locked + // (none allowed) + case stateLocked: + return false + + // shouldn't reach here + default: + panic("unexpected state") + } +} + // MutexMap is a structure that allows having a map of self-evicting mutexes // by key. You do not need to worry about managing the contents of the map, // only requesting RLock/Lock for keys, and ensuring to call the returned // unlock functions. type MutexMap struct { - // NOTE: - // Individual keyed mutexes should ONLY ever - // be locked within the protection of the outer - // mapMu lock. If you lock these outside the - // protection of this, there is a chance for - // deadlocks - mus map[string]RWMutex mapMu sync.Mutex pool sync.Pool + queue []func() + evict []func() + count int32 + maxmu int32 + state uint8 } -// NewMap returns a new MutexMap instance based on supplied -// RWMutex allocator function, nil implies use default -func NewMap(newFn func() RWMutex) MutexMap { - if newFn == nil { - newFn = NewRW +// NewMap returns a new MutexMap instance with provided max no. open mutexes. +func NewMap(max int32) MutexMap { + if max < 1 { + // Default = 128 * GOMAXPROCS + procs := runtime.GOMAXPROCS(0) + max = int32(procs * 128) } return MutexMap{ - mus: make(map[string]RWMutex), - mapMu: sync.Mutex{}, + mus: make(map[string]RWMutex), pool: sync.Pool{ New: func() interface{} { - return newFn() + return NewRW() }, }, + maxmu: max, + } +} + +// acquire will either acquire a mutex from pool or alloc. +func (mm *MutexMap) acquire() RWMutex { + return mm.pool.Get().(RWMutex) +} + +// release will release provided mutex to pool. +func (mm *MutexMap) release(mu RWMutex) { + mm.pool.Put(mu) +} + +// spinLock will wait (using a mutex to sleep thread) until 'cond()' returns true, +// returning with map lock. Note that 'cond' is performed within a map lock. +func (mm *MutexMap) spinLock(cond func() bool) { + mu := mm.acquire() + defer mm.release(mu) + + for { + // Get map lock + mm.mapMu.Lock() + + // Check if return + if cond() { + return + } + + // Queue ourselves + unlock := mu.Lock() + mm.queue = append(mm.queue, unlock) + mm.mapMu.Unlock() + + // Wait on notify + mu.Lock()() + } +} + +// lockMutex will acquire a lock on the mutex at provided key, handling earlier allocated mutex if provided. Unlocks map on return. +func (mm *MutexMap) lockMutex(key string, lt locktype) func() { + var unlock func() + + // Incr counter + mm.count++ + + // Check for existing mutex at key + mu, ok := mm.mus[key] + if !ok { + // Alloc from pool + mu = mm.acquire() + mm.mus[key] = mu + + // Queue mutex for eviction + mm.evict = append(mm.evict, func() { + delete(mm.mus, key) + mm.pool.Put(mu) + }) + } + + // If no state, set in use. + // State will already have been + // set if this is from LockState{} + if mm.state == stateUnlockd { + mm.state = stateInUse + } + + switch { + // Read lock + case lt&lockTypeRead != 0: + unlock = mu.RLock() + + // Write lock + case lt&lockTypeWrite != 0: + unlock = mu.Lock() + + // shouldn't reach here + default: + panic("unexpected lock type") + } + + // Unlock map + return + mm.mapMu.Unlock() + return func() { + mm.mapMu.Lock() + unlock() + go mm.onUnlock() } } -func (mm *MutexMap) evict(key string, mu RWMutex) { - // Acquire map lock - mm.mapMu.Lock() +// onUnlock is performed as the final (async) stage of releasing an acquired key / map mutex. +func (mm *MutexMap) onUnlock() { + // Decr counter + mm.count-- + + if mm.count < 1 { + // Perform all queued evictions + for i := 0; i < len(mm.evict); i++ { + mm.evict[i]() + } - // Toggle mutex lock to - // ensure it is unused - unlock := mu.Lock() - unlock() + // Notify all waiting goroutines + for i := 0; i < len(mm.queue); i++ { + mm.queue[i]() + } - // Delete mutex key - delete(mm.mus, key) + // Reset the map state + mm.evict = nil + mm.queue = nil + mm.state = stateUnlockd + } + + // Finally, unlock mm.mapMu.Unlock() +} - // Release to pool - mm.pool.Put(mu) +// RLockMap acquires a read lock over the entire map, returning a lock state for acquiring key read locks. +// Please note that the 'unlock()' function will block until all keys locked from this state are unlocked. +func (mm *MutexMap) RLockMap() *LockState { + return mm.getMapLock(lockTypeRead) } -// RLock acquires a mutex read lock for supplied key, returning an RUnlock function -func (mm *MutexMap) RLock(key string) func() { - return mm.getLock(key, func(mu RWMutex) func() { - return mu.RLock() +// LockMap acquires a write lock over the entire map, returning a lock state for acquiring key read/write locks. +// Please note that the 'unlock()' function will block until all keys locked from this state are unlocked. +func (mm *MutexMap) LockMap() *LockState { + return mm.getMapLock(lockTypeWrite) +} + +// RLock acquires a mutex read lock for supplied key, returning an RUnlock function. +func (mm *MutexMap) RLock(key string) (runlock func()) { + return mm.getLock(key, lockTypeRead) +} + +// Lock acquires a mutex write lock for supplied key, returning an Unlock function. +func (mm *MutexMap) Lock(key string) (unlock func()) { + return mm.getLock(key, lockTypeWrite) +} + +// getLock will fetch lock of provided type, for given key, returning unlock function. +func (mm *MutexMap) getLock(key string, lt locktype) func() { + // Spin until achieve lock + mm.spinLock(func() bool { + return permitLockType(mm.state, lt) && + mm.count < mm.maxmu // not overloaded }) + + // Perform actual mutex lock + return mm.lockMutex(key, lt) } -// Lock acquires a mutex lock for supplied key, returning an Unlock function -func (mm *MutexMap) Lock(key string) func() { - return mm.getLock(key, func(mu RWMutex) func() { - return mu.Lock() +// getMapLock will acquire a map lock of provided type, returning a LockState session. +func (mm *MutexMap) getMapLock(lt locktype) *LockState { + // Spin until achieve lock + mm.spinLock(func() bool { + return permitLockType(mm.state, lt|lockTypeMap) && + mm.count < mm.maxmu // not overloaded }) + + // Incr counter + mm.count++ + + switch { + // Set read lock state + case lt&lockTypeRead != 0: + mm.state = stateRLocked + + // Set write lock state + case lt&lockTypeWrite != 0: + mm.state = stateLocked + + default: + panic("unexpected lock type") + } + + // Unlock + return + mm.mapMu.Unlock() + return &LockState{ + mmap: mm, + ltyp: lt, + } } -func (mm *MutexMap) getLock(key string, doLock func(RWMutex) func()) func() { - // Get map lock - mm.mapMu.Lock() +// LockState represents a window to a locked MutexMap. +type LockState struct { + wait sync.WaitGroup + mmap *MutexMap + done uint32 + ltyp locktype +} - // Look for mutex - mu, ok := mm.mus[key] - if ok { - // Lock and return - // its unlocker func - unlock := doLock(mu) - mm.mapMu.Unlock() - return unlock +// Lock: see MutexMap.Lock() definition. Will panic if map only read locked. +func (st *LockState) Lock(key string) (unlock func()) { + return st.getLock(key, lockTypeWrite) +} + +// RLock: see MutexMap.RLock() definition. +func (st *LockState) RLock(key string) (runlock func()) { + return st.getLock(key, lockTypeRead) +} + +// UnlockMap will close this state and release the currently locked map. +func (st *LockState) UnlockMap() { + // Set state to finished (or panic if already done) + if !atomic.CompareAndSwapUint32(&st.done, 0, 1) { + panic("called UnlockMap() on expired state") } - // Note: even though the mutex data structure is - // small, benchmarking does actually show that pooled - // alloc of mutexes here is faster + // Wait until done + st.wait.Wait() - // Acquire mu + add - mu = mm.pool.Get().(RWMutex) - mm.mus[key] = mu + // Async reset map + st.mmap.mapMu.Lock() + go st.mmap.onUnlock() +} - // Lock mutex + unlock map - unlockFn := doLock(mu) - mm.mapMu.Unlock() +// getLock: see MutexMap.getLock() definition. +func (st *LockState) getLock(key string, lt locktype) func() { + st.wait.Add(1) // track lock - return func() { - // Unlock mutex - unlockFn() + // Check if closed, or if write lock is allowed + if atomic.LoadUint32(&st.done) == 1 { + panic("map lock closed") + } else if lt&lockTypeWrite != 0 && + st.ltyp&lockTypeWrite == 0 { + panic("called .Lock() on rlocked map") + } + + // Spin until achieve map lock + st.mmap.spinLock(func() bool { + return st.mmap.count < st.mmap.maxmu + }) // i.e. not overloaded - // Release function - go mm.evict(key, mu) + // Perform actual mutex lock + unlock := st.mmap.lockMutex(key, lt) + + return func() { + unlock() + st.wait.Done() } } diff --git a/vendor/codeberg.org/gruf/go-mutexes/mutex_timeout.go b/vendor/codeberg.org/gruf/go-mutexes/mutex_timeout.go index 5da69ef25..2e7b8f802 100644 --- a/vendor/codeberg.org/gruf/go-mutexes/mutex_timeout.go +++ b/vendor/codeberg.org/gruf/go-mutexes/mutex_timeout.go @@ -3,8 +3,6 @@ package mutexes import ( "sync" "time" - - "codeberg.org/gruf/go-nowish" ) // TimeoutMutex defines a Mutex with timeouts on locks @@ -73,14 +71,6 @@ func (mu *timeoutRWMutex) RLockFunc(fn func()) func() { return mutexTimeout(mu.rd, mu.mu.RLock(), fn) } -// timeoutPool provides nowish.Timeout objects for timeout mutexes -var timeoutPool = sync.Pool{ - New: func() interface{} { - t := nowish.NewTimeout() - return &t - }, -} - // mutexTimeout performs a timed unlock, calling supplied fn if timeout is reached func mutexTimeout(d time.Duration, unlock func(), fn func()) func() { if d < 1 { @@ -88,18 +78,65 @@ func mutexTimeout(d time.Duration, unlock func(), fn func()) func() { return unlock } - // Acquire timeout obj - t := timeoutPool.Get().(*nowish.Timeout) + // Acquire timer from pool + t := timerPool.Get().(*timer) - // Start the timeout with hook - t.Start(d, fn) + // Start the timer + go t.Start(d, fn) // Return func cancelling timeout, // replacing Timeout in pool and // finally unlocking mutex return func() { + defer timerPool.Put(t) t.Cancel() - timeoutPool.Put(t) unlock() } } + +// timerPool is the global &timer{} pool. +var timerPool = sync.Pool{ + New: func() interface{} { + return newtimer() + }, +} + +// timer represents a reusable cancellable timer. +type timer struct { + t *time.Timer + c chan struct{} +} + +// newtimer returns a new timer instance. +func newtimer() *timer { + t := time.NewTimer(time.Minute) + t.Stop() + return &timer{t: t, c: make(chan struct{})} +} + +// Start will start the timer with duration 'd', performing 'fn' on timeout. +func (t *timer) Start(d time.Duration, fn func()) { + t.t.Reset(d) + select { + // Timed out + case <-t.t.C: + fn() + + // Cancelled + case <-t.c: + } +} + +// Cancel will attempt to cancel the running timer. +func (t *timer) Cancel() { + select { + // cancel successful + case t.c <- struct{}{}: + if !t.t.Stop() { + <-t.t.C + } // stop timer + + // already stopped + default: + } +} diff --git a/vendor/codeberg.org/gruf/go-nowish/README.md b/vendor/codeberg.org/gruf/go-nowish/README.md deleted file mode 100644 index 4070e5013..000000000 --- a/vendor/codeberg.org/gruf/go-nowish/README.md +++ /dev/null @@ -1,3 +0,0 @@ -a simple Go library with useful time utiities: -- Clock: a high performance clock giving a good "ish" representation of "now" (hence the name!) -- Timeout: a reusable structure for enforcing timeouts with a cancel diff --git a/vendor/codeberg.org/gruf/go-nowish/clock.go b/vendor/codeberg.org/gruf/go-nowish/clock.go deleted file mode 100644 index 781e59f18..000000000 --- a/vendor/codeberg.org/gruf/go-nowish/clock.go +++ /dev/null @@ -1,132 +0,0 @@ -package nowish - -import ( - "sync" - "sync/atomic" - "time" - "unsafe" -) - -// Start returns a new Clock instance initialized and -// started with the provided precision, along with the -// stop function for it's underlying timer -func Start(precision time.Duration) (*Clock, func()) { - c := Clock{} - return &c, c.Start(precision) -} - -type Clock struct { - // format stores the time formatting style string - format string - - // valid indicates whether the current value stored in .Format is valid - valid uint32 - - // mutex protects writes to .Format, not because it would be unsafe, but - // because we want to minimize unnnecessary allocations - mutex sync.Mutex - - // nowfmt is an unsafe pointer to the last-updated time format string - nowfmt unsafe.Pointer - - // now is an unsafe pointer to the last-updated time.Time object - now unsafe.Pointer -} - -// Start starts the clock with the provided precision, the returned -// function is the stop function for the underlying timer. For >= 2ms, -// actual precision is usually within AT LEAST 10% of requested precision, -// less than this and the actual precision very quickly deteriorates. -func (c *Clock) Start(precision time.Duration) func() { - // Create ticker from duration - tick := time.NewTicker(precision / 10) - - // Set initial time - t := time.Now() - atomic.StorePointer(&c.now, unsafe.Pointer(&t)) - - // Set initial format - s := "" - atomic.StorePointer(&c.nowfmt, unsafe.Pointer(&s)) - - // If formatting string unset, set default - c.mutex.Lock() - if c.format == "" { - c.format = time.RFC822 - } - c.mutex.Unlock() - - // Start main routine - go c.run(tick) - - // Return stop fn - return tick.Stop -} - -// run is the internal clock ticking loop. -func (c *Clock) run(tick *time.Ticker) { - for { - // Wait on tick - _, ok := <-tick.C - - // Channel closed - if !ok { - break - } - - // Update time - t := time.Now() - atomic.StorePointer(&c.now, unsafe.Pointer(&t)) - - // Invalidate format string - atomic.StoreUint32(&c.valid, 0) - } -} - -// Now returns a good (ish) estimate of the current 'now' time. -func (c *Clock) Now() time.Time { - return *(*time.Time)(atomic.LoadPointer(&c.now)) -} - -// NowFormat returns the formatted "now" time, cached until next tick and "now" updates. -func (c *Clock) NowFormat() string { - // If format still valid, return this - if atomic.LoadUint32(&c.valid) == 1 { - return *(*string)(atomic.LoadPointer(&c.nowfmt)) - } - - // Get mutex lock - c.mutex.Lock() - - // Double check still invalid - if atomic.LoadUint32(&c.valid) == 1 { - c.mutex.Unlock() - return *(*string)(atomic.LoadPointer(&c.nowfmt)) - } - - // Calculate time format - nowfmt := c.Now().Format(c.format) - - // Update the stored value and set valid! - atomic.StorePointer(&c.nowfmt, unsafe.Pointer(&nowfmt)) - atomic.StoreUint32(&c.valid, 1) - - // Unlock and return - c.mutex.Unlock() - return nowfmt -} - -// SetFormat sets the time format string used by .NowFormat(). -func (c *Clock) SetFormat(format string) { - // Get mutex lock - c.mutex.Lock() - - // Update time format - c.format = format - - // Invalidate current format string - atomic.StoreUint32(&c.valid, 0) - - // Unlock - c.mutex.Unlock() -} diff --git a/vendor/codeberg.org/gruf/go-nowish/timeout.go b/vendor/codeberg.org/gruf/go-nowish/timeout.go deleted file mode 100644 index 7fe3e1d1d..000000000 --- a/vendor/codeberg.org/gruf/go-nowish/timeout.go +++ /dev/null @@ -1,233 +0,0 @@ -package nowish - -import ( - "sync" - "sync/atomic" - "time" -) - -// Timeout provides a reusable structure for enforcing timeouts with a cancel. -type Timeout struct { - timer *time.Timer // timer is the underlying timeout-timer - cncl syncer // cncl is the cancel synchronization channel - next int64 // next is the next timeout duration to run on - state uint32 // state stores the current timeout state - mu sync.Mutex // mu protects state, and helps synchronize return of .Start() -} - -// NewTimeout returns a new Timeout instance. -func NewTimeout() Timeout { - timer := time.NewTimer(time.Minute) - timer.Stop() // don't keep it running - return Timeout{ - timer: timer, - cncl: make(syncer), - } -} - -// startTimeout is the main timeout routine, handling starting the -// timeout runner at first and upon any time extensions, and handling -// any received cancels by stopping the running timer. -func (t *Timeout) startTimeout(hook func()) { - var cancelled bool - - // Receive first timeout duration - d := atomic.SwapInt64(&t.next, 0) - - // Indicate finished starting, this - // was left locked by t.start(). - t.mu.Unlock() - - for { - // Run supplied timeout - cancelled = t.runTimeout(d) - if cancelled { - break - } - - // Check for extension or set timed out - d = atomic.SwapInt64(&t.next, 0) - if d < 1 { - if t.timedOut() { - // timeout reached - hook() - break - } else { - // already cancelled - t.cncl.wait() - cancelled = true - break - } - } - - if !t.extend() { - // already cancelled - t.cncl.wait() - cancelled = true - break - } - } - - if cancelled { - // Release the .Cancel() - defer t.cncl.notify() - } - - // Mark as done - t.reset() -} - -// runTimeout will until supplied timeout or cancel called. -func (t *Timeout) runTimeout(d int64) (cancelled bool) { - // Start the timer for 'd' - t.timer.Reset(time.Duration(d)) - - select { - // Timeout reached - case <-t.timer.C: - if !t.timingOut() { - // a sneaky cancel! - t.cncl.wait() - cancelled = true - } - - // Cancel called - case <-t.cncl.wait(): - cancelled = true - if !t.timer.Stop() { - <-t.timer.C - } - } - - return cancelled -} - -// Start starts the timer with supplied timeout. If timeout is reached before -// cancel then supplied timeout hook will be called. Panic will be called if -// Timeout is already running when calling this function. -func (t *Timeout) Start(d time.Duration, hook func()) { - if !t.start() { - t.mu.Unlock() // need to unlock - panic("timeout already started") - } - - // Start the timeout - atomic.StoreInt64(&t.next, int64(d)) - go t.startTimeout(hook) - - // Wait until start - t.mu.Lock() - t.mu.Unlock() -} - -// Extend will attempt to extend the timeout runner's time, returns false if not running. -func (t *Timeout) Extend(d time.Duration) bool { - var ok bool - if ok = t.running(); ok { - atomic.AddInt64(&t.next, int64(d)) - } - return ok -} - -// Cancel cancels the currently running timer. If a cancel is achieved, then -// this function will return after the timeout goroutine is finished. -func (t *Timeout) Cancel() { - if !t.cancel() { - return - } - t.cncl.notify() - <-t.cncl.wait() -} - -// possible timeout states. -const ( - stopped = 0 - started = 1 - timingOut = 2 - cancelled = 3 - timedOut = 4 -) - -// cas will perform a compare and swap where the compare is a provided function. -func (t *Timeout) cas(check func(uint32) bool, swap uint32) bool { - var cas bool - - t.mu.Lock() - if cas = check(t.state); cas { - t.state = swap - } - t.mu.Unlock() - - return cas -} - -// start attempts to mark the timeout state as 'started', note DOES NOT unlock Timeout.mu. -func (t *Timeout) start() bool { - var ok bool - - t.mu.Lock() - if ok = (t.state == stopped); ok { - t.state = started - } - - // don't unlock - return ok -} - -// timingOut attempts to mark the timeout state as 'timing out'. -func (t *Timeout) timingOut() bool { - return t.cas(func(u uint32) bool { - return (u == started) - }, timingOut) -} - -// timedOut attempts mark the 'timing out' state as 'timed out'. -func (t *Timeout) timedOut() bool { - return t.cas(func(u uint32) bool { - return (u == timingOut) - }, timedOut) -} - -// extend attempts to extend a 'timing out' state by moving it back to 'started'. -func (t *Timeout) extend() bool { - return t.cas(func(u uint32) bool { - return (u == started) || - (u == timingOut) - }, started) -} - -// running returns whether the state is anything other than 'stopped'. -func (t *Timeout) running() bool { - t.mu.Lock() - running := (t.state != stopped) - t.mu.Unlock() - return running -} - -// cancel attempts to mark the timeout state as 'cancelled'. -func (t *Timeout) cancel() bool { - return t.cas(func(u uint32) bool { - return (u == started) || - (u == timingOut) - }, cancelled) -} - -// reset marks the timeout state as 'stopped'. -func (t *Timeout) reset() { - t.mu.Lock() - t.state = stopped - t.mu.Unlock() -} - -// syncer provides helpful receiver methods for a synchronization channel. -type syncer (chan struct{}) - -// notify blocks on sending an empty value down channel. -func (s syncer) notify() { - s <- struct{}{} -} - -// wait returns the underlying channel for blocking until '.notify()'. -func (s syncer) wait() <-chan struct{} { - return s -} diff --git a/vendor/codeberg.org/gruf/go-nowish/util.go b/vendor/codeberg.org/gruf/go-nowish/util.go deleted file mode 100644 index 31fe9050e..000000000 --- a/vendor/codeberg.org/gruf/go-nowish/util.go +++ /dev/null @@ -1,10 +0,0 @@ -package nowish - -//nolint -type noCopy struct{} - -//nolint -func (*noCopy) Lock() {} - -//nolint -func (*noCopy) Unlock() {} diff --git a/vendor/codeberg.org/gruf/go-nowish/LICENSE b/vendor/codeberg.org/gruf/go-runners/LICENSE index b7c4417ac..b7c4417ac 100644 --- a/vendor/codeberg.org/gruf/go-nowish/LICENSE +++ b/vendor/codeberg.org/gruf/go-runners/LICENSE diff --git a/vendor/codeberg.org/gruf/go-runners/README.md b/vendor/codeberg.org/gruf/go-runners/README.md new file mode 100644 index 000000000..91cc1528d --- /dev/null +++ b/vendor/codeberg.org/gruf/go-runners/README.md @@ -0,0 +1,3 @@ +# go-runners + +Provides a means a simple means of managing long-running functions and services
\ No newline at end of file diff --git a/vendor/codeberg.org/gruf/go-runners/context.go b/vendor/codeberg.org/gruf/go-runners/context.go new file mode 100644 index 000000000..edb695060 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-runners/context.go @@ -0,0 +1,36 @@ +package runners + +import ( + "context" + "time" +) + +// ContextWithCancel returns a new context.Context impl with cancel. +func ContextWithCancel() (context.Context, context.CancelFunc) { + ctx := make(cancelctx) + return ctx, func() { close(ctx) } +} + +// cancelctx is the simplest possible cancellable context. +type cancelctx (chan struct{}) + +func (cancelctx) Deadline() (time.Time, bool) { + return time.Time{}, false +} + +func (ctx cancelctx) Done() <-chan struct{} { + return ctx +} + +func (ctx cancelctx) Err() error { + select { + case <-ctx: + return context.Canceled + default: + return nil + } +} + +func (cancelctx) Value(key interface{}) interface{} { + return nil +} diff --git a/vendor/codeberg.org/gruf/go-runners/pool.go b/vendor/codeberg.org/gruf/go-runners/pool.go new file mode 100644 index 000000000..49fc22038 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-runners/pool.go @@ -0,0 +1,160 @@ +package runners + +import ( + "context" + "sync" +) + +// WorkerFunc represents a function processable by a worker in WorkerPool. Note +// that implementations absolutely MUST check whether passed context is Done() +// otherwise stopping the pool may block for large periods of time. +type WorkerFunc func(context.Context) + +// WorkerPool provides a means of enqueuing asynchronous work. +type WorkerPool struct { + queue chan WorkerFunc + free chan struct{} + wait sync.WaitGroup + svc Service +} + +// NewWorkerPool returns a new WorkerPool with provided worker count and WorkerFunc queue size. +// 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 { + return WorkerPool{ + queue: make(chan WorkerFunc, queue), + free: make(chan struct{}, workers), + } +} + +// Start will attempt to start the worker pool, asynchronously. Return is success state. +func (pool *WorkerPool) Start() bool { + ok := true + + done := make(chan struct{}) + go func() { + ok = pool.svc.Run(func(ctx context.Context) { + close(done) + pool.process(ctx) + }) + if !ok { + close(done) + } + }() + <-done + + return ok +} + +// Stop will attempt to stop the worker pool, this will block until stopped. Return is success state. +func (pool *WorkerPool) Stop() bool { + return pool.svc.Stop() +} + +// Running returns whether the worker pool is running. +func (pool *WorkerPool) Running() bool { + return pool.svc.Running() +} + +// execute will take a queued function and pass it to a free worker when available. +func (pool *WorkerPool) execute(ctx context.Context, fn WorkerFunc) { + // Set as running + pool.wait.Add(1) + + select { + // Pool context cancelled + case <-ctx.Done(): + pool.wait.Done() + + // Free worker acquired + case pool.free <- struct{}{}: + } + + go func() { + defer func() { + // defer in case panic + <-pool.free + pool.wait.Done() + }() + + // Run queued + fn(ctx) + }() +} + +// process is the background processing routine that passes queued functions to workers. +func (pool *WorkerPool) process(ctx context.Context) { + for { + select { + // Pool context cancelled + case <-ctx.Done(): + for { + select { + // Pop and execute queued + case fn := <-pool.queue: + fn(ctx) // ctx is closed + + // Empty, wait for workers + default: + pool.wait.Wait() + return + } + } + + // Queued func received + case fn := <-pool.queue: + pool.execute(ctx, fn) + } + } +} + +// 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(). +func (pool *WorkerPool) Enqueue(fn WorkerFunc) { + // Check valid fn + if fn == nil { + return + } + + select { + // Pool context cancelled + case <-pool.svc.Done(): + + // Placed fn in queue + case pool.queue <- fn: + } +} + +// EnqueueNoBlock performs Enqueue but returns false if queue size is at max. Else, true. +func (pool *WorkerPool) EnqueueNoBlock(fn WorkerFunc) bool { + // Check valid fn + if fn == nil { + return false + } + + select { + // Pool context cancelled + case <-pool.svc.Done(): + return false + + // Placed fn in queue + case pool.queue <- fn: + return true + + // Queue is full + default: + return false + } +} + +// Queue returns the number of currently queued WorkerFuncs. +func (pool *WorkerPool) Queue() int { + return len(pool.queue) +} + +// Workers returns the number of currently active workers. +func (pool *WorkerPool) Workers() int { + return len(pool.free) +} diff --git a/vendor/codeberg.org/gruf/go-runners/run.go b/vendor/codeberg.org/gruf/go-runners/run.go new file mode 100644 index 000000000..27f7fb9b8 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-runners/run.go @@ -0,0 +1,130 @@ +package runners + +import ( + "context" + "errors" + "fmt" + "sync" + "time" +) + +// FuncRunner provides a means of managing long-running functions e.g. main logic loops. +type FuncRunner struct { + // HandOff is the time after which a blocking function will be considered handed off + HandOff time.Duration + + // ErrorHandler is the function that errors are passed to when encountered by the + // provided function. This can be used both for logging, and for error filtering + ErrorHandler func(err error) error + + svc Service // underlying service to manage start/stop + err error // last-set error + mu sync.Mutex // protects err +} + +// Go will attempt to run 'fn' asynchronously. The provided context is used to propagate requested +// cancel if FuncRunner.Stop() is called. Any returned error will be passed to FuncRunner.ErrorHandler +// for filtering/logging/etc. Any blocking functions will be waited on for FuncRunner.HandOff amount of +// time before considering the function as handed off. Returned bool is success state, i.e. returns true +// if function is successfully handed off or returns within hand off time with nil error. +func (r *FuncRunner) Go(fn func(ctx context.Context) error) bool { + done := make(chan struct{}) + + go func() { + var cancelled bool + + has := r.svc.Run(func(ctx context.Context) { + // reset error + r.mu.Lock() + r.err = nil + r.mu.Unlock() + + // Run supplied func and set errror if returned + if err := Run(func() error { return fn(ctx) }); err != nil { + r.mu.Lock() + r.err = err + r.mu.Unlock() + } + + // signal done + close(done) + + // Check if cancelled + select { + case <-ctx.Done(): + cancelled = true + default: + cancelled = false + } + }) + + switch has { + // returned after starting + case true: + r.mu.Lock() + + // filter out errors due FuncRunner.Stop() being called + if cancelled && errors.Is(r.err, context.Canceled) { + // filter out errors from FuncRunner.Stop() being called + r.err = nil + } else if r.err != nil && r.ErrorHandler != nil { + // pass any non-nil error to set handler + r.err = r.ErrorHandler(r.err) + } + + r.mu.Unlock() + + // already running + case false: + close(done) + } + }() + + // get valid handoff to use + handoff := r.HandOff + if handoff < 1 { + handoff = time.Second * 5 + } + + select { + // handed off (long-run successful) + case <-time.After(handoff): + return true + + // 'fn' returned, check error + case <-done: + return (r.Err() == nil) + } +} + +// Stop will cancel the context supplied to the running function. +func (r *FuncRunner) Stop() bool { + return r.svc.Stop() +} + +// Err returns the last-set error value. +func (r *FuncRunner) Err() error { + r.mu.Lock() + err := r.err + r.mu.Unlock() + return err +} + +// Run will execute the supplied 'fn' catching any panics. Returns either function-returned error or formatted panic. +func Run(fn func() error) (err error) { + defer func() { + if r := recover(); r != nil { + if e, ok := r.(error); ok { + // wrap and preserve existing error + err = fmt.Errorf("caught panic: %w", e) + } else { + // simply create new error fromt iface + err = fmt.Errorf("caught panic: %v", r) + } + } + }() + + // run supplied func + err = fn() + return +} diff --git a/vendor/codeberg.org/gruf/go-runners/service.go b/vendor/codeberg.org/gruf/go-runners/service.go new file mode 100644 index 000000000..c0f878c45 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-runners/service.go @@ -0,0 +1,159 @@ +package runners + +import ( + "context" + "sync" +) + +// Service provides a means of tracking a single long-running service, provided protected state +// changes and preventing multiple instances running. Also providing service state information. +type Service struct { + state uint32 // 0=stopped, 1=running, 2=stopping + wait sync.Mutex // wait is the mutex used as a single-entity wait-group, i.e. just a "wait" :p + cncl context.CancelFunc // cncl is the cancel function set for the current context + ctx context.Context // ctx is the current context for running function (or nil if not running) + mu sync.Mutex // mu protects state changes +} + +// Run will run the supplied function until completion, use given context to propagate cancel. +// Immediately returns false if the Service is already running, and true after completed run. +func (svc *Service) Run(fn func(context.Context)) bool { + // Attempt to start the svc + ctx, ok := svc.doStart() + if !ok { + return false + } + + defer func() { + // unlock single wait + svc.wait.Unlock() + + // ensure stopped + svc.Stop() + }() + + // Run user func + if fn != nil { + fn(ctx) + } + return true +} + +// Stop will attempt to stop the service, cancelling the running function's context. Immediately +// returns false if not running, and true only after Service is fully stopped. +func (svc *Service) Stop() bool { + // Attempt to stop the svc + cncl, ok := svc.doStop() + if !ok { + return false + } + + defer func() { + // Get svc lock + svc.mu.Lock() + + // Wait until stopped + svc.wait.Lock() + svc.wait.Unlock() + + // Reset the svc + svc.ctx = nil + svc.cncl = nil + svc.state = 0 + svc.mu.Unlock() + }() + + cncl() // cancel ctx + return true +} + +// doStart will safely set Service state to started, returning a ptr to this context insance. +func (svc *Service) doStart() (context.Context, bool) { + // Protect startup + svc.mu.Lock() + + if svc.state != 0 /* not stopped */ { + svc.mu.Unlock() + return nil, false + } + + // state started + svc.state = 1 + + // Take our own ptr + var ctx context.Context + + if svc.ctx == nil { + // Context required allocating + svc.ctx, svc.cncl = ContextWithCancel() + } + + // Start the waiter + svc.wait.Lock() + + // Set our ptr + unlock + ctx = svc.ctx + svc.mu.Unlock() + + return ctx, true +} + +// doStop will safely set Service state to stopping, returning a ptr to this cancelfunc instance. +func (svc *Service) doStop() (context.CancelFunc, bool) { + // Protect stop + svc.mu.Lock() + + if svc.state != 1 /* not started */ { + svc.mu.Unlock() + return nil, false + } + + // state stopping + svc.state = 2 + + // Take our own ptr + // and unlock state + cncl := svc.cncl + svc.mu.Unlock() + + return cncl, true +} + +// Running returns if Service is running (i.e. state NOT stopped / stopping). +func (svc *Service) Running() bool { + svc.mu.Lock() + state := svc.state + svc.mu.Unlock() + return (state == 1) +} + +// Done returns a channel that's closed when Service.Stop() is called. It is +// the same channel provided to the currently running service function. +func (svc *Service) Done() <-chan struct{} { + var done <-chan struct{} + + svc.mu.Lock() + switch svc.state { + // stopped + // (here we create a new context so that the + // returned 'done' channel here will still + // be valid for when Service is next started) + case 0: + if svc.ctx == nil { + // need to allocate new context + svc.ctx, svc.cncl = ContextWithCancel() + } + done = svc.ctx.Done() + + // started + case 1: + done = svc.ctx.Done() + + // stopping + case 2: + done = svc.ctx.Done() + } + svc.mu.Unlock() + + return done +} diff --git a/vendor/codeberg.org/gruf/go-store/kv/iterator.go b/vendor/codeberg.org/gruf/go-store/kv/iterator.go index d3999273f..da743ead1 100644 --- a/vendor/codeberg.org/gruf/go-store/kv/iterator.go +++ b/vendor/codeberg.org/gruf/go-store/kv/iterator.go @@ -2,6 +2,7 @@ package kv import ( "codeberg.org/gruf/go-errors" + "codeberg.org/gruf/go-mutexes" "codeberg.org/gruf/go-store/storage" ) @@ -17,10 +18,10 @@ var ErrIteratorClosed = errors.New("store/kv: iterator closed") // have multiple iterators running concurrently type KVIterator struct { store *KVStore // store is the linked KVStore + state *mutexes.LockState entries []storage.StorageEntry index int key string - onClose func() } // Next attempts to set the next key-value pair, the @@ -43,13 +44,10 @@ func (i *KVIterator) Key() string { // Release releases the KVIterator and KVStore's read lock func (i *KVIterator) Release() { - // Reset key, path, entries + i.state.UnlockMap() i.store = nil i.key = "" i.entries = nil - - // Perform requested callback - i.onClose() } // Value returns the next value from the KVStore @@ -60,5 +58,5 @@ func (i *KVIterator) Value() ([]byte, error) { } // Attempt to fetch from store - return i.store.get(i.key) + return i.store.get(i.state.RLock, i.key) } diff --git a/vendor/codeberg.org/gruf/go-store/kv/state.go b/vendor/codeberg.org/gruf/go-store/kv/state.go index 20a3e951d..0b226e107 100644 --- a/vendor/codeberg.org/gruf/go-store/kv/state.go +++ b/vendor/codeberg.org/gruf/go-store/kv/state.go @@ -2,9 +2,9 @@ package kv import ( "io" - "sync" "codeberg.org/gruf/go-errors" + "codeberg.org/gruf/go-mutexes" ) var ErrStateClosed = errors.New("store/kv: state closed") @@ -16,61 +16,42 @@ var ErrStateClosed = errors.New("store/kv: state closed") // then the state has zero guarantees type StateRO struct { store *KVStore - mutex sync.RWMutex + state *mutexes.LockState } func (st *StateRO) Get(key string) ([]byte, error) { - // Get state read lock - st.mutex.RLock() - defer st.mutex.RUnlock() - // Check not closed if st.store == nil { return nil, ErrStateClosed } // Pass request to store - return st.store.get(key) + return st.store.get(st.state.RLock, key) } func (st *StateRO) GetStream(key string) (io.ReadCloser, error) { - // Get state read lock - st.mutex.RLock() - defer st.mutex.RUnlock() - // Check not closed if st.store == nil { return nil, ErrStateClosed } // Pass request to store - return st.store.getStream(key) + return st.store.getStream(st.state.RLock, key) } func (st *StateRO) Has(key string) (bool, error) { - // Get state read lock - st.mutex.RLock() - defer st.mutex.RUnlock() - // Check not closed if st.store == nil { return false, ErrStateClosed } // Pass request to store - return st.store.has(key) + return st.store.has(st.state.RLock, key) } func (st *StateRO) Release() { - // Get state write lock - st.mutex.Lock() - defer st.mutex.Unlock() - - // Release the store - if st.store != nil { - st.store.mutex.RUnlock() - st.store = nil - } + st.state.UnlockMap() + st.store = nil } // StateRW provides a read-write window to the store. While this @@ -80,101 +61,70 @@ func (st *StateRO) Release() { // then the state has zero guarantees type StateRW struct { store *KVStore - mutex sync.RWMutex + state *mutexes.LockState } func (st *StateRW) Get(key string) ([]byte, error) { - // Get state read lock - st.mutex.RLock() - defer st.mutex.RUnlock() - // Check not closed if st.store == nil { return nil, ErrStateClosed } // Pass request to store - return st.store.get(key) + return st.store.get(st.state.RLock, key) } func (st *StateRW) GetStream(key string) (io.ReadCloser, error) { - // Get state read lock - st.mutex.RLock() - defer st.mutex.RUnlock() - // Check not closed if st.store == nil { return nil, ErrStateClosed } // Pass request to store - return st.store.getStream(key) + return st.store.getStream(st.state.RLock, key) } func (st *StateRW) Put(key string, value []byte) error { - // Get state read lock - st.mutex.RLock() - defer st.mutex.RUnlock() - // Check not closed if st.store == nil { return ErrStateClosed } // Pass request to store - return st.store.put(key, value) + return st.store.put(st.state.Lock, key, value) } func (st *StateRW) PutStream(key string, r io.Reader) error { - // Get state read lock - st.mutex.RLock() - defer st.mutex.RUnlock() - // Check not closed if st.store == nil { return ErrStateClosed } // Pass request to store - return st.store.putStream(key, r) + return st.store.putStream(st.state.Lock, key, r) } func (st *StateRW) Has(key string) (bool, error) { - // Get state read lock - st.mutex.RLock() - defer st.mutex.RUnlock() - // Check not closed if st.store == nil { return false, ErrStateClosed } // Pass request to store - return st.store.has(key) + return st.store.has(st.state.RLock, key) } func (st *StateRW) Delete(key string) error { - // Get state read lock - st.mutex.RLock() - defer st.mutex.RUnlock() - // Check not closed if st.store == nil { return ErrStateClosed } // Pass request to store - return st.store.delete(key) + return st.store.delete(st.state.Lock, key) } func (st *StateRW) Release() { - // Get state write lock - st.mutex.Lock() - defer st.mutex.Unlock() - - // Release the store - if st.store != nil { - st.store.mutex.Unlock() - st.store = nil - } + st.state.UnlockMap() + st.store = nil } diff --git a/vendor/codeberg.org/gruf/go-store/kv/store.go b/vendor/codeberg.org/gruf/go-store/kv/store.go index 34fe91987..a8741afe0 100644 --- a/vendor/codeberg.org/gruf/go-store/kv/store.go +++ b/vendor/codeberg.org/gruf/go-store/kv/store.go @@ -2,7 +2,6 @@ package kv import ( "io" - "sync" "codeberg.org/gruf/go-mutexes" "codeberg.org/gruf/go-store/storage" @@ -11,9 +10,8 @@ import ( // KVStore is a very simple, yet performant key-value store type KVStore struct { - mutexMap mutexes.MutexMap // mutexMap is a map of keys to mutexes to protect file access - mutex sync.RWMutex // mutex is the total store mutex - storage storage.Storage // storage is the underlying storage + mutex mutexes.MutexMap // mutex is a map of keys to mutexes to protect file access + storage storage.Storage // storage is the underlying storage } func OpenFile(path string, cfg *storage.DiskConfig) (*KVStore, error) { @@ -47,25 +45,29 @@ func OpenStorage(storage storage.Storage) (*KVStore, error) { // Return new KVStore return &KVStore{ - mutexMap: mutexes.NewMap(mutexes.NewRW), - mutex: sync.RWMutex{}, - storage: storage, + mutex: mutexes.NewMap(-1), + storage: storage, }, nil } +// RLock acquires a read-lock on supplied key, returning unlock function. +func (st *KVStore) RLock(key string) (runlock func()) { + return st.mutex.RLock(key) +} + +// Lock acquires a write-lock on supplied key, returning unlock function. +func (st *KVStore) Lock(key string) (unlock func()) { + return st.mutex.Lock(key) +} + // Get fetches the bytes for supplied key in the store func (st *KVStore) Get(key string) ([]byte, error) { - // Acquire store read lock - st.mutex.RLock() - defer st.mutex.RUnlock() - - // Pass to unprotected fn - return st.get(key) + return st.get(st.RLock, key) } -func (st *KVStore) get(key string) ([]byte, error) { +func (st *KVStore) get(rlock func(string) func(), key string) ([]byte, error) { // Acquire read lock for key - runlock := st.mutexMap.RLock(key) + runlock := rlock(key) defer runlock() // Read file bytes @@ -74,17 +76,12 @@ func (st *KVStore) get(key string) ([]byte, error) { // GetStream fetches a ReadCloser for the bytes at the supplied key location in the store func (st *KVStore) GetStream(key string) (io.ReadCloser, error) { - // Acquire store read lock - st.mutex.RLock() - defer st.mutex.RUnlock() - - // Pass to unprotected fn - return st.getStream(key) + return st.getStream(st.RLock, key) } -func (st *KVStore) getStream(key string) (io.ReadCloser, error) { +func (st *KVStore) getStream(rlock func(string) func(), key string) (io.ReadCloser, error) { // Acquire read lock for key - runlock := st.mutexMap.RLock(key) + runlock := rlock(key) // Attempt to open stream for read rd, err := st.storage.ReadStream(key) @@ -99,17 +96,12 @@ func (st *KVStore) getStream(key string) (io.ReadCloser, error) { // Put places the bytes at the supplied key location in the store func (st *KVStore) Put(key string, value []byte) error { - // Acquire store write lock - st.mutex.Lock() - defer st.mutex.Unlock() - - // Pass to unprotected fn - return st.put(key, value) + return st.put(st.Lock, key, value) } -func (st *KVStore) put(key string, value []byte) error { +func (st *KVStore) put(lock func(string) func(), key string, value []byte) error { // Acquire write lock for key - unlock := st.mutexMap.Lock(key) + unlock := lock(key) defer unlock() // Write file bytes @@ -118,17 +110,12 @@ func (st *KVStore) put(key string, value []byte) error { // PutStream writes the bytes from the supplied Reader at the supplied key location in the store func (st *KVStore) PutStream(key string, r io.Reader) error { - // Acquire store write lock - st.mutex.Lock() - defer st.mutex.Unlock() - - // Pass to unprotected fn - return st.putStream(key, r) + return st.putStream(st.Lock, key, r) } -func (st *KVStore) putStream(key string, r io.Reader) error { +func (st *KVStore) putStream(lock func(string) func(), key string, r io.Reader) error { // Acquire write lock for key - unlock := st.mutexMap.Lock(key) + unlock := lock(key) defer unlock() // Write file stream @@ -137,17 +124,12 @@ func (st *KVStore) putStream(key string, r io.Reader) error { // Has checks whether the supplied key exists in the store func (st *KVStore) Has(key string) (bool, error) { - // Acquire store read lock - st.mutex.RLock() - defer st.mutex.RUnlock() - - // Pass to unprotected fn - return st.has(key) + return st.has(st.RLock, key) } -func (st *KVStore) has(key string) (bool, error) { +func (st *KVStore) has(rlock func(string) func(), key string) (bool, error) { // Acquire read lock for key - runlock := st.mutexMap.RLock(key) + runlock := rlock(key) defer runlock() // Stat file on disk @@ -156,17 +138,12 @@ func (st *KVStore) has(key string) (bool, error) { // Delete removes the supplied key-value pair from the store func (st *KVStore) Delete(key string) error { - // Acquire store write lock - st.mutex.Lock() - defer st.mutex.Unlock() - - // Pass to unprotected fn - return st.delete(key) + return st.delete(st.Lock, key) } -func (st *KVStore) delete(key string) error { +func (st *KVStore) delete(lock func(string) func(), key string) error { // Acquire write lock for key - unlock := st.mutexMap.Lock(key) + unlock := lock(key) defer unlock() // Remove file from disk @@ -181,7 +158,7 @@ func (st *KVStore) Iterator(matchFn func(string) bool) (*KVIterator, error) { } // Get store read lock - st.mutex.RLock() + state := st.mutex.RLockMap() // Setup the walk keys function entries := []storage.StorageEntry{} @@ -198,24 +175,24 @@ func (st *KVStore) Iterator(matchFn func(string) bool) (*KVIterator, error) { // Walk keys in the storage err := st.storage.WalkKeys(storage.WalkKeysOptions{WalkFn: walkFn}) if err != nil { - st.mutex.RUnlock() + state.UnlockMap() return nil, err } // Return new iterator return &KVIterator{ store: st, + state: state, entries: entries, index: -1, key: "", - onClose: st.mutex.RUnlock, }, nil } // Read provides a read-only window to the store, holding it in a read-locked state until release func (st *KVStore) Read() *StateRO { - st.mutex.RLock() - return &StateRO{store: st} + state := st.mutex.RLockMap() + return &StateRO{store: st, state: state} } // ReadFn provides a read-only window to the store, holding it in a read-locked state until fn return. @@ -230,8 +207,8 @@ func (st *KVStore) ReadFn(fn func(*StateRO)) { // Update provides a read-write window to the store, holding it in a write-locked state until release func (st *KVStore) Update() *StateRW { - st.mutex.Lock() - return &StateRW{store: st} + state := st.mutex.LockMap() + return &StateRW{store: st, state: state} } // UpdateFn provides a read-write window to the store, holding it in a write-locked state until fn return. @@ -243,3 +220,8 @@ func (st *KVStore) UpdateFn(fn func(*StateRW)) { // Pass to fn fn(state) } + +// Close will close the underlying storage, the mutex map locking (e.g. RLock(), Lock() will still work). +func (st *KVStore) Close() error { + return st.storage.Close() +} diff --git a/vendor/codeberg.org/gruf/go-store/storage/block.go b/vendor/codeberg.org/gruf/go-store/storage/block.go index 9a8c4dc7d..c50faa10b 100644 --- a/vendor/codeberg.org/gruf/go-store/storage/block.go +++ b/vendor/codeberg.org/gruf/go-store/storage/block.go @@ -87,6 +87,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 + lock *Lock // lock is the opened lockfile for this storage instance // NOTE: // BlockStorage does not need to lock each of the underlying block files @@ -138,6 +139,12 @@ func OpenBlock(path string, cfg *BlockConfig) (*BlockStorage, error) { return nil, errPathIsFile } + // Open and acquire storage lock for path + lock, err := OpenLock(pb.Join(path, LockFile)) + if err != nil { + return nil, err + } + // Figure out the largest size for bufpool slices bufSz := encodedHashLen if bufSz < config.BlockSize { @@ -159,19 +166,29 @@ func OpenBlock(path string, cfg *BlockConfig) (*BlockStorage, error) { }, }, bufpool: pools.NewBufferPool(bufSz), + lock: lock, }, nil } // Clean implements storage.Clean() func (st *BlockStorage) Clean() error { - nodes := map[string]*node{} + // Track open + st.lock.Add() + defer st.lock.Done() + + // Check if open + if st.lock.Closed() { + return ErrClosed + } // Acquire path builder pb := util.GetPathBuilder() defer util.PutPathBuilder(pb) - // Walk nodes dir for entries + nodes := map[string]*node{} onceErr := errors.OnceError{} + + // Walk nodes dir for entries err := util.WalkDir(pb, st.nodePath, func(npath string, fsentry fs.DirEntry) { // Only deal with regular files if !fsentry.Type().IsRegular() { @@ -293,6 +310,7 @@ func (st *BlockStorage) ReadBytes(key string) ([]byte, error) { if err != nil { return nil, err } + defer rc.Close() // Read all bytes and return return io.ReadAll(rc) @@ -306,9 +324,19 @@ func (st *BlockStorage) ReadStream(key string) (io.ReadCloser, error) { return nil, err } + // Track open + st.lock.Add() + + // Check if open + if st.lock.Closed() { + st.lock.Done() + return nil, ErrClosed + } + // Attempt to open RO file file, err := open(npath, defaultFileROFlags) if err != nil { + st.lock.Done() return nil, err } defer file.Close() @@ -328,14 +356,16 @@ func (st *BlockStorage) ReadStream(key string) (io.ReadCloser, error) { nil, ) if err != nil { + st.lock.Done() return nil, err } - // Return new block reader - return util.NopReadCloser(&blockReader{ + // Prepare block reader and return + rc := util.NopReadCloser(&blockReader{ storage: st, node: &node, - }), nil + }) // we wrap the blockreader to decr lockfile waitgroup + return util.ReadCloserWithCallback(rc, st.lock.Done), nil } func (st *BlockStorage) readBlock(key string) ([]byte, error) { @@ -373,6 +403,15 @@ func (st *BlockStorage) WriteStream(key string, r io.Reader) error { return err } + // Track open + st.lock.Add() + defer st.lock.Done() + + // Check if open + if st.lock.Closed() { + return ErrClosed + } + // Check if this exists ok, err := stat(key) if err != nil { @@ -443,11 +482,16 @@ loop: continue loop } - // Write in separate goroutine + // Check if reached EOF + atEOF := (n < buf.Len()) + wg.Add(1) go func() { - // Defer buffer release + signal done + // Perform writes in goroutine + defer func() { + // Defer release + + // signal we're done st.bufpool.Put(buf) wg.Done() }() @@ -460,8 +504,8 @@ loop: } }() - // We reached EOF - if n < buf.Len() { + // Break at end + if atEOF { break loop } } @@ -552,6 +596,15 @@ func (st *BlockStorage) Stat(key string) (bool, error) { return false, err } + // Track open + st.lock.Add() + defer st.lock.Done() + + // Check if open + if st.lock.Closed() { + return false, ErrClosed + } + // Check for file on disk return stat(kpath) } @@ -564,12 +617,35 @@ func (st *BlockStorage) Remove(key string) error { return err } + // Track open + st.lock.Add() + defer st.lock.Done() + + // Check if open + if st.lock.Closed() { + return ErrClosed + } + // Attempt to remove file return os.Remove(kpath) } +// Close implements Storage.Close() +func (st *BlockStorage) Close() error { + return st.lock.Close() +} + // WalkKeys implements Storage.WalkKeys() func (st *BlockStorage) WalkKeys(opts WalkKeysOptions) error { + // Track open + st.lock.Add() + defer st.lock.Done() + + // Check if open + if st.lock.Closed() { + return ErrClosed + } + // Acquire path builder pb := util.GetPathBuilder() defer util.PutPathBuilder(pb) @@ -610,7 +686,7 @@ func (st *BlockStorage) blockPathForKey(hash string) string { } // hashSeparator is the separating byte between block hashes -const hashSeparator = byte(':') +const hashSeparator = byte('\n') // node represents the contents of a node file in storage type node struct { @@ -773,24 +849,27 @@ func (r *blockReader) Read(b []byte) (int, error) { } } +var ( + // base64Encoding is our base64 encoding object. + base64Encoding = hashenc.Base64() + + // encodedHashLen is the once-calculated encoded hash-sum length + encodedHashLen = base64Encoding.EncodedLen( + sha256.New().Size(), + ) +) + // hashEncoder is a HashEncoder with built-in encode buffer type hashEncoder struct { henc hashenc.HashEncoder ebuf []byte } -// encodedHashLen is the once-calculated encoded hash-sum length -var encodedHashLen = hashenc.Base64().EncodedLen( - sha256.New().Size(), -) - // newHashEncoder returns a new hashEncoder instance func newHashEncoder() *hashEncoder { - hash := sha256.New() - enc := hashenc.Base64() return &hashEncoder{ - henc: hashenc.New(hash, enc), - ebuf: make([]byte, enc.EncodedLen(hash.Size())), + henc: hashenc.New(sha256.New(), base64Encoding), + ebuf: make([]byte, encodedHashLen), } } diff --git a/vendor/codeberg.org/gruf/go-store/storage/disk.go b/vendor/codeberg.org/gruf/go-store/storage/disk.go index 060d56688..287042886 100644 --- a/vendor/codeberg.org/gruf/go-store/storage/disk.go +++ b/vendor/codeberg.org/gruf/go-store/storage/disk.go @@ -5,6 +5,8 @@ import ( "io/fs" "os" "path" + _path "path" + "strings" "syscall" "codeberg.org/gruf/go-bytes" @@ -31,6 +33,11 @@ type DiskConfig struct { // Overwrite allows overwriting values of stored keys in the storage Overwrite bool + // LockFile allows specifying the filesystem path to use for the lockfile, + // providing only a filename it will store the lockfile within provided store + // path and nest the store under `path/store` to prevent access to lockfile + LockFile string + // Compression is the Compressor to use when reading / writing files, default is no compression Compression Compressor } @@ -57,11 +64,17 @@ func getDiskConfig(cfg *DiskConfig) DiskConfig { cfg.WriteBufSize = DefaultDiskConfig.WriteBufSize } + // Assume empty lockfile path == use default + if len(cfg.LockFile) < 1 { + cfg.LockFile = LockFile + } + // Return owned config copy return DiskConfig{ Transform: cfg.Transform, WriteBufSize: cfg.WriteBufSize, Overwrite: cfg.Overwrite, + LockFile: cfg.LockFile, Compression: cfg.Compression, } } @@ -71,23 +84,35 @@ type DiskStorage struct { path string // path is the root path of this store bufp pools.BufferPool // bufp is the buffer pool for this DiskStorage config DiskConfig // cfg is the supplied configuration for this store + lock *Lock // lock is the opened lockfile for this storage instance } // OpenFile opens a DiskStorage instance for given folder path and configuration func OpenFile(path string, cfg *DiskConfig) (*DiskStorage, error) { + // Get checked config + config := getDiskConfig(cfg) + // Acquire path builder pb := util.GetPathBuilder() defer util.PutPathBuilder(pb) - // Clean provided path, ensure ends in '/' (should - // be dir, this helps with file path trimming later) - path = pb.Clean(path) + "/" + // Clean provided store path, ensure + // ends in '/' to help later path trimming + storePath := pb.Clean(path) + "/" - // Get checked config - config := getDiskConfig(cfg) + // Clean provided lockfile path + lockfile := pb.Clean(config.LockFile) + + // Check if lockfile is an *actual* path or just filename + if lockDir, _ := _path.Split(lockfile); len(lockDir) < 1 { + // Lockfile is a filename, store must be nested under + // $storePath/store to prevent access to the lockfile + storePath += "store/" + lockfile = pb.Join(path, lockfile) + } // Attempt to open dir path - file, err := os.OpenFile(path, defaultFileROFlags, defaultDirPerms) + file, err := os.OpenFile(storePath, defaultFileROFlags, defaultDirPerms) if err != nil { // If not a not-exist error, return if !os.IsNotExist(err) { @@ -95,13 +120,13 @@ func OpenFile(path string, cfg *DiskConfig) (*DiskStorage, error) { } // Attempt to make store path dirs - err = os.MkdirAll(path, defaultDirPerms) + err = os.MkdirAll(storePath, defaultDirPerms) if err != nil { return nil, err } // Reopen dir now it's been created - file, err = os.OpenFile(path, defaultFileROFlags, defaultDirPerms) + file, err = os.OpenFile(storePath, defaultFileROFlags, defaultDirPerms) if err != nil { return nil, err } @@ -116,16 +141,28 @@ func OpenFile(path string, cfg *DiskConfig) (*DiskStorage, error) { return nil, errPathIsFile } + // Open and acquire storage lock for path + lock, err := OpenLock(lockfile) + if err != nil { + return nil, err + } + // Return new DiskStorage return &DiskStorage{ - path: path, + path: storePath, bufp: pools.NewBufferPool(config.WriteBufSize), config: config, + lock: lock, }, nil } // Clean implements Storage.Clean() func (st *DiskStorage) Clean() error { + st.lock.Add() + defer st.lock.Done() + if st.lock.Closed() { + return ErrClosed + } return util.CleanDirs(st.path) } @@ -150,9 +187,18 @@ func (st *DiskStorage) ReadStream(key string) (io.ReadCloser, error) { return nil, err } + // Track open + st.lock.Add() + + // Check if open + if st.lock.Closed() { + return nil, ErrClosed + } + // Attempt to open file (replace ENOENT with our own) file, err := open(kpath, defaultFileROFlags) if err != nil { + st.lock.Done() return nil, errSwapNotFound(err) } @@ -160,12 +206,14 @@ func (st *DiskStorage) ReadStream(key string) (io.ReadCloser, error) { cFile, err := st.config.Compression.Reader(file) if err != nil { file.Close() // close this here, ignore error + st.lock.Done() return nil, err } // Wrap compressor to ensure file close return util.ReadCloserWithCallback(cFile, func() { file.Close() + st.lock.Done() }), nil } @@ -182,6 +230,15 @@ func (st *DiskStorage) WriteStream(key string, r io.Reader) error { return err } + // Track open + st.lock.Add() + defer st.lock.Done() + + // Check if open + if st.lock.Closed() { + return ErrClosed + } + // Ensure dirs leading up to file exist err = os.MkdirAll(path.Dir(kpath), defaultDirPerms) if err != nil { @@ -232,6 +289,15 @@ func (st *DiskStorage) Stat(key string) (bool, error) { return false, err } + // Track open + st.lock.Add() + defer st.lock.Done() + + // Check if open + if st.lock.Closed() { + return false, ErrClosed + } + // Check for file on disk return stat(kpath) } @@ -244,20 +310,44 @@ func (st *DiskStorage) Remove(key string) error { return err } + // Track open + st.lock.Add() + defer st.lock.Done() + + // Check if open + if st.lock.Closed() { + return ErrClosed + } + // Attempt to remove file return os.Remove(kpath) } +// Close implements Storage.Close() +func (st *DiskStorage) Close() error { + return st.lock.Close() +} + // WalkKeys implements Storage.WalkKeys() func (st *DiskStorage) WalkKeys(opts WalkKeysOptions) error { + // Track open + st.lock.Add() + defer st.lock.Done() + + // Check if open + if st.lock.Closed() { + return ErrClosed + } + // Acquire path builder pb := util.GetPathBuilder() defer util.PutPathBuilder(pb) // Walk dir for entries return util.WalkDir(pb, st.path, func(kpath string, fsentry fs.DirEntry) { - // Only deal with regular files if fsentry.Type().IsRegular() { + // Only deal with regular files + // Get full item path (without root) kpath = pb.Join(kpath, fsentry.Name())[len(st.path):] @@ -269,21 +359,39 @@ func (st *DiskStorage) WalkKeys(opts WalkKeysOptions) error { // filepath checks and returns a formatted filepath for given key func (st *DiskStorage) filepath(key string) (string, error) { + // Calculate transformed key path + key = st.config.Transform.KeyToPath(key) + // Acquire path builder pb := util.GetPathBuilder() defer util.PutPathBuilder(pb) - // Calculate transformed key path - key = st.config.Transform.KeyToPath(key) - // Generated joined root path pb.AppendString(st.path) pb.AppendString(key) // Check for dir traversal outside of root - if util.IsDirTraversal(st.path, pb.StringPtr()) { + if isDirTraversal(st.path, pb.StringPtr()) { return "", ErrInvalidKey } return pb.String(), nil } + +// isDirTraversal will check if rootPlusPath is a dir traversal outside of root, +// assuming that both are cleaned and that rootPlusPath is path.Join(root, somePath) +func isDirTraversal(root, rootPlusPath string) bool { + switch { + // Root is $PWD, check for traversal out of + case root == ".": + return strings.HasPrefix(rootPlusPath, "../") + + // The path MUST be prefixed by root + case !strings.HasPrefix(rootPlusPath, root): + return true + + // In all other cases, check not equal + default: + return len(root) == len(rootPlusPath) + } +} diff --git a/vendor/codeberg.org/gruf/go-store/storage/errors.go b/vendor/codeberg.org/gruf/go-store/storage/errors.go index 016593596..ad2b742e6 100644 --- a/vendor/codeberg.org/gruf/go-store/storage/errors.go +++ b/vendor/codeberg.org/gruf/go-store/storage/errors.go @@ -19,6 +19,9 @@ func (e errorString) Extend(s string, a ...interface{}) errorString { } var ( + // ErrClosed is returned on operations on a closed storage + ErrClosed = errorString("store/storage: closed") + // ErrNotFound is the error returned when a key cannot be found in storage ErrNotFound = errorString("store/storage: key not found") @@ -39,6 +42,9 @@ var ( // errCorruptNodes is returned when nodes with missing blocks are found during a BlockStorage clean errCorruptNodes = errorString("store/storage: corrupted nodes") + + // ErrAlreadyLocked is returned on fail opening a storage lockfile + ErrAlreadyLocked = errorString("store/storage: storage lock already open") ) // errSwapNoop performs no error swaps @@ -61,3 +67,11 @@ func errSwapExist(err error) error { } return err } + +// errSwapUnavailable swaps syscall.EAGAIN for ErrAlreadyLocked +func errSwapUnavailable(err error) error { + if err == syscall.EAGAIN { + return ErrAlreadyLocked + } + return err +} diff --git a/vendor/codeberg.org/gruf/go-store/storage/fs.go b/vendor/codeberg.org/gruf/go-store/storage/fs.go index 444cee4b0..b1c3560d2 100644 --- a/vendor/codeberg.org/gruf/go-store/storage/fs.go +++ b/vendor/codeberg.org/gruf/go-store/storage/fs.go @@ -8,11 +8,14 @@ import ( ) const ( - defaultDirPerms = 0755 - defaultFilePerms = 0644 + // default file permission bits + defaultDirPerms = 0755 + defaultFilePerms = 0644 + + // default file open flags defaultFileROFlags = syscall.O_RDONLY defaultFileRWFlags = syscall.O_CREAT | syscall.O_RDWR - defaultFileLockFlags = syscall.O_RDONLY | syscall.O_EXCL | syscall.O_CREAT + defaultFileLockFlags = syscall.O_RDONLY | syscall.O_CREAT ) // NOTE: @@ -39,7 +42,7 @@ func stat(path string) (bool, error) { return syscall.Stat(path, &stat) }) if err != nil { - if err == syscall.ENOENT { + if err == syscall.ENOENT { //nolint err = nil } return false, err diff --git a/vendor/codeberg.org/gruf/go-store/storage/lock.go b/vendor/codeberg.org/gruf/go-store/storage/lock.go index 3d794cda9..8a6c4c5e8 100644 --- a/vendor/codeberg.org/gruf/go-store/storage/lock.go +++ b/vendor/codeberg.org/gruf/go-store/storage/lock.go @@ -1,34 +1,76 @@ package storage import ( - "os" + "sync" + "sync/atomic" "syscall" "codeberg.org/gruf/go-store/util" ) -type lockableFile struct { - *os.File +// LockFile is our standard lockfile name. +const LockFile = "store.lock" + +// Lock represents a filesystem lock to ensure only one storage instance open per path. +type Lock struct { + fd int + wg sync.WaitGroup + st uint32 } -func openLock(path string) (*lockableFile, error) { - file, err := open(path, defaultFileLockFlags) +// OpenLock opens a lockfile at path. +func OpenLock(path string) (*Lock, error) { + var fd int + + // Open the file descriptor at path + err := util.RetryOnEINTR(func() (err error) { + fd, err = syscall.Open(path, defaultFileLockFlags, defaultFilePerms) + return + }) if err != nil { return nil, err } - return &lockableFile{file}, nil + + // Get a flock on the file descriptor + err = util.RetryOnEINTR(func() error { + return syscall.Flock(fd, syscall.LOCK_EX|syscall.LOCK_NB) + }) + if err != nil { + return nil, errSwapUnavailable(err) + } + + return &Lock{fd: fd}, nil } -func (f *lockableFile) lock() error { - return f.flock(syscall.LOCK_EX | syscall.LOCK_NB) +// Add will add '1' to the underlying sync.WaitGroup. +func (f *Lock) Add() { + f.wg.Add(1) } -func (f *lockableFile) unlock() error { - return f.flock(syscall.LOCK_UN | syscall.LOCK_NB) +// Done will decrememnt '1' from the underlying sync.WaitGroup. +func (f *Lock) Done() { + f.wg.Done() } -func (f *lockableFile) flock(how int) error { - return util.RetryOnEINTR(func() error { - return syscall.Flock(int(f.Fd()), how) - }) +// Close will attempt to close the lockfile and file descriptor. +func (f *Lock) Close() error { + var err error + if atomic.CompareAndSwapUint32(&f.st, 0, 1) { + // Wait until done + f.wg.Wait() + + // Ensure gets closed + defer syscall.Close(f.fd) + + // Call funlock on the file descriptor + err = util.RetryOnEINTR(func() error { + return syscall.Flock(f.fd, syscall.LOCK_UN|syscall.LOCK_NB) + }) + } + return err +} + +// Closed will return whether this lockfile has been closed (and unlocked). +func (f *Lock) Closed() bool { + return (atomic.LoadUint32(&f.st) == 1) } diff --git a/vendor/codeberg.org/gruf/go-store/storage/memory.go b/vendor/codeberg.org/gruf/go-store/storage/memory.go index be60fa464..2dab562d6 100644 --- a/vendor/codeberg.org/gruf/go-store/storage/memory.go +++ b/vendor/codeberg.org/gruf/go-store/storage/memory.go @@ -2,6 +2,7 @@ package storage import ( "io" + "sync" "codeberg.org/gruf/go-bytes" "codeberg.org/gruf/go-store/util" @@ -10,36 +11,76 @@ import ( // MemoryStorage is a storage implementation that simply stores key-value // pairs in a Go map in-memory. The map is protected by a mutex. type MemoryStorage struct { + ow bool // overwrites fs map[string][]byte + mu sync.Mutex + st uint32 } // OpenMemory opens a new MemoryStorage instance with internal map of 'size'. -func OpenMemory(size int) *MemoryStorage { +func OpenMemory(size int, overwrites bool) *MemoryStorage { return &MemoryStorage{ fs: make(map[string][]byte, size), + mu: sync.Mutex{}, + ow: overwrites, } } // Clean implements Storage.Clean(). func (st *MemoryStorage) Clean() error { + st.mu.Lock() + defer st.mu.Unlock() + if st.st == 1 { + return ErrClosed + } return nil } // ReadBytes implements Storage.ReadBytes(). func (st *MemoryStorage) ReadBytes(key string) ([]byte, error) { + // Lock storage + st.mu.Lock() + + // Check store open + if st.st == 1 { + st.mu.Unlock() + return nil, ErrClosed + } + + // Check for key b, ok := st.fs[key] + st.mu.Unlock() + + // Return early if not exist if !ok { return nil, ErrNotFound } + + // Create return copy return bytes.Copy(b), nil } // ReadStream implements Storage.ReadStream(). func (st *MemoryStorage) ReadStream(key string) (io.ReadCloser, error) { + // Lock storage + st.mu.Lock() + + // Check store open + if st.st == 1 { + st.mu.Unlock() + return nil, ErrClosed + } + + // Check for key b, ok := st.fs[key] + st.mu.Unlock() + + // Return early if not exist if !ok { return nil, ErrNotFound } + + // Create io.ReadCloser from 'b' copy b = bytes.Copy(b) r := bytes.NewReader(b) return util.NopReadCloser(r), nil @@ -47,43 +88,101 @@ func (st *MemoryStorage) ReadStream(key string) (io.ReadCloser, error) { // WriteBytes implements Storage.WriteBytes(). func (st *MemoryStorage) WriteBytes(key string, b []byte) error { + // Lock storage + st.mu.Lock() + defer st.mu.Unlock() + + // Check store open + if st.st == 1 { + return ErrClosed + } + _, ok := st.fs[key] - if ok { + + // Check for already exist + if ok && !st.ow { return ErrAlreadyExists } + + // Write + unlock st.fs[key] = bytes.Copy(b) return nil } // WriteStream implements Storage.WriteStream(). func (st *MemoryStorage) WriteStream(key string, r io.Reader) error { + // Read all from reader b, err := io.ReadAll(r) if err != nil { return err } + + // Write to storage return st.WriteBytes(key, b) } // Stat implements Storage.Stat(). func (st *MemoryStorage) Stat(key string) (bool, error) { + // Lock storage + st.mu.Lock() + defer st.mu.Unlock() + + // Check store open + if st.st == 1 { + return false, ErrClosed + } + + // Check for key _, ok := st.fs[key] return ok, nil } // Remove implements Storage.Remove(). func (st *MemoryStorage) Remove(key string) error { + // Lock storage + st.mu.Lock() + defer st.mu.Unlock() + + // Check store open + if st.st == 1 { + return ErrClosed + } + + // Check for key _, ok := st.fs[key] if !ok { return ErrNotFound } + + // Remove from store delete(st.fs, key) + + return nil +} + +// Close implements Storage.Close(). +func (st *MemoryStorage) Close() error { + st.mu.Lock() + st.st = 1 + st.mu.Unlock() return nil } // WalkKeys implements Storage.WalkKeys(). func (st *MemoryStorage) WalkKeys(opts WalkKeysOptions) error { + // Lock storage + st.mu.Lock() + defer st.mu.Unlock() + + // Check store open + if st.st == 1 { + return ErrClosed + } + + // Walk store keys for key := range st.fs { opts.WalkFn(entry(key)) } + return nil } diff --git a/vendor/codeberg.org/gruf/go-store/storage/storage.go b/vendor/codeberg.org/gruf/go-store/storage/storage.go index b160267a4..346aff097 100644 --- a/vendor/codeberg.org/gruf/go-store/storage/storage.go +++ b/vendor/codeberg.org/gruf/go-store/storage/storage.go @@ -19,9 +19,6 @@ func (e entry) Key() string { // Storage defines a means of storing and accessing key value pairs type Storage interface { - // Clean removes unused values and unclutters the storage (e.g. removing empty folders) - Clean() error - // ReadBytes returns the byte value for key in storage ReadBytes(key string) ([]byte, error) @@ -40,6 +37,12 @@ type Storage interface { // Remove attempts to remove the supplied key-value pair from storage Remove(key string) error + // Close will close the storage, releasing any file locks + Close() error + + // Clean removes unused values and unclutters the storage (e.g. removing empty folders) + Clean() error + // WalkKeys walks the keys in the storage WalkKeys(opts WalkKeysOptions) error } diff --git a/vendor/codeberg.org/gruf/go-store/util/fs.go b/vendor/codeberg.org/gruf/go-store/util/fs.go index 93b37a261..53fef7750 100644 --- a/vendor/codeberg.org/gruf/go-store/util/fs.go +++ b/vendor/codeberg.org/gruf/go-store/util/fs.go @@ -3,30 +3,10 @@ package util import ( "io/fs" "os" - "strings" - "syscall" "codeberg.org/gruf/go-fastpath" ) -// IsDirTraversal will check if rootPlusPath is a dir traversal outside of root, -// assuming that both are cleaned and that rootPlusPath is path.Join(root, somePath) -func IsDirTraversal(root string, rootPlusPath string) bool { - switch { - // Root is $PWD, check for traversal out of - case root == ".": - return strings.HasPrefix(rootPlusPath, "../") - - // The path MUST be prefixed by root - case !strings.HasPrefix(rootPlusPath, root): - return true - - // In all other cases, check not equal - default: - return len(root) == len(rootPlusPath) - } -} - // WalkDir traverses the dir tree of the supplied path, performing the supplied walkFn on each entry func WalkDir(pb *fastpath.Builder, path string, walkFn func(string, fs.DirEntry)) error { // Read supplied dir path @@ -100,14 +80,3 @@ func cleanDirs(pb *fastpath.Builder, path string) error { } return nil } - -// RetryOnEINTR is a low-level filesystem function for retrying syscalls on O_EINTR received -func RetryOnEINTR(do func() error) error { - for { - err := do() - if err == syscall.EINTR { - continue - } - return err - } -} diff --git a/vendor/codeberg.org/gruf/go-store/util/sys.go b/vendor/codeberg.org/gruf/go-store/util/sys.go new file mode 100644 index 000000000..6661029e5 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-store/util/sys.go @@ -0,0 +1,14 @@ +package util + +import "syscall" + +// RetryOnEINTR is a low-level filesystem function for retrying syscalls on O_EINTR received +func RetryOnEINTR(do func() error) error { + for { + err := do() + if err == syscall.EINTR { + continue + } + return err + } +} |