Update
This commit is contained in:
1 parent
3530afb854
commit
6903fcff27
2146 files changed
+112256
-190703
No files matched your search
+97
-19
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package zstdframe
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"tailscale.com/util/must"
|
||||
"tailscale.com/util/testenv"
|
||||
)
|
||||
|
||||
// Option is an option that can be passed to [AppendEncode] or [AppendDecode].
|
||||
@@ -104,6 +105,17 @@ func (lowMemory) isOption() {}
|
||||
// By default, more memory used for better speed.
|
||||
func LowMemory(low bool) Option { return lowMemory(low) }
|
||||
|
||||
// poolCoders reports whether encoders and decoders should be pooled for
|
||||
// reuse across calls.
|
||||
//
|
||||
// Pooling is disabled within testing/synctest bubbles. The zstd Encoder and
|
||||
// Decoder types use channels internally, and the Go runtime kills the
|
||||
// process when a channel created within a bubble is used from outside that
|
||||
// bubble. A process-wide pool shared between bubbled and non-bubbled
|
||||
// goroutines does exactly that, so bubbled goroutines instead construct a
|
||||
// fresh coder per call and let the GC reclaim it.
|
||||
func poolCoders() bool { return !testenv.InSynctestBubble() }
|
||||
|
||||
var encoderPools sync.Map // map[encoderOptions]*sync.Pool -> *zstd.Encoder
|
||||
|
||||
type encoderOptions struct {
|
||||
@@ -113,12 +125,8 @@ type encoderOptions struct {
|
||||
lowMemory bool
|
||||
}
|
||||
|
||||
type encoder struct {
|
||||
pool *sync.Pool
|
||||
*zstd.Encoder
|
||||
}
|
||||
|
||||
func getEncoder(opts ...Option) encoder {
|
||||
// parseEncoderOptions applies opts on top of the default encoder options.
|
||||
func parseEncoderOptions(opts []Option) encoderOptions {
|
||||
eopts := encoderOptions{level: zstd.SpeedDefault, checksum: true}
|
||||
for _, opt := range opts {
|
||||
switch opt := opt.(type) {
|
||||
@@ -132,13 +140,71 @@ func getEncoder(opts ...Option) encoder {
|
||||
eopts.lowMemory = bool(opt)
|
||||
}
|
||||
}
|
||||
return eopts
|
||||
}
|
||||
|
||||
vpool, ok := encoderPools.Load(eopts)
|
||||
if !ok {
|
||||
vpool, _ = encoderPools.LoadOrStore(eopts, new(sync.Pool))
|
||||
type encoder struct {
|
||||
pool *sync.Pool
|
||||
*zstd.Encoder
|
||||
}
|
||||
|
||||
var streamingEncoderPools sync.Map // map[encoderOptions]*sync.Pool -> *zstd.Encoder
|
||||
|
||||
// GetStreamingEncoder returns an encoder from the shared pool, configured
|
||||
// for streaming (stateful) use: it maintains compression context across
|
||||
// Write calls, and Close finishes the frame. The caller must call
|
||||
// enc.Reset with the destination writer before first use, and enc.Close
|
||||
// when done writing. Call the returned put function exactly once
|
||||
// afterwards to return the encoder to the pool; the encoder must not be
|
||||
// used after that, and must not be used concurrently.
|
||||
func GetStreamingEncoder(opts ...Option) (enc *zstd.Encoder, put func()) {
|
||||
eopts := parseEncoderOptions(opts)
|
||||
|
||||
var pool *sync.Pool
|
||||
if poolCoders() {
|
||||
vpool, ok := streamingEncoderPools.Load(eopts)
|
||||
if !ok {
|
||||
vpool, _ = streamingEncoderPools.LoadOrStore(eopts, new(sync.Pool))
|
||||
}
|
||||
pool = vpool.(*sync.Pool)
|
||||
enc, _ = pool.Get().(*zstd.Encoder)
|
||||
}
|
||||
if enc == nil {
|
||||
// Unlike the stateless encoders above, streaming encoders must not
|
||||
// use SingleSegment framing: the total content size is not known up
|
||||
// front, and the window size must be communicated to decoders.
|
||||
zopts := []zstd.EOption{
|
||||
// Set concurrency=1 to ensure synchronous operation.
|
||||
zstd.WithEncoderConcurrency(1),
|
||||
zstd.WithEncoderLevel(eopts.level),
|
||||
zstd.WithEncoderCRC(eopts.checksum),
|
||||
zstd.WithLowerEncoderMem(eopts.lowMemory),
|
||||
}
|
||||
if eopts.maxWindowLog2 > 0 {
|
||||
zopts = append(zopts, zstd.WithWindowSize(1<<eopts.maxWindowLog2))
|
||||
}
|
||||
enc = must.Get(zstd.NewWriter(nil, zopts...))
|
||||
}
|
||||
return enc, func() {
|
||||
if pool != nil {
|
||||
pool.Put(enc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getEncoder(opts ...Option) encoder {
|
||||
eopts := parseEncoderOptions(opts)
|
||||
|
||||
var pool *sync.Pool
|
||||
var enc *zstd.Encoder
|
||||
if poolCoders() {
|
||||
vpool, ok := encoderPools.Load(eopts)
|
||||
if !ok {
|
||||
vpool, _ = encoderPools.LoadOrStore(eopts, new(sync.Pool))
|
||||
}
|
||||
pool = vpool.(*sync.Pool)
|
||||
enc, _ = pool.Get().(*zstd.Encoder)
|
||||
}
|
||||
pool := vpool.(*sync.Pool)
|
||||
enc, _ := pool.Get().(*zstd.Encoder)
|
||||
if enc == nil {
|
||||
var noopts int
|
||||
zopts := [...]zstd.EOption{
|
||||
@@ -167,7 +233,11 @@ func getEncoder(opts ...Option) encoder {
|
||||
return encoder{pool, enc}
|
||||
}
|
||||
|
||||
func putEncoder(e encoder) { e.pool.Put(e.Encoder) }
|
||||
func putEncoder(e encoder) {
|
||||
if e.pool != nil {
|
||||
e.pool.Put(e.Encoder)
|
||||
}
|
||||
}
|
||||
|
||||
var decoderPools sync.Map // map[decoderOptions]*sync.Pool -> *zstd.Decoder
|
||||
|
||||
@@ -205,12 +275,16 @@ func getDecoder(opts ...Option) decoder {
|
||||
}
|
||||
}
|
||||
|
||||
vpool, ok := decoderPools.Load(dopts)
|
||||
if !ok {
|
||||
vpool, _ = decoderPools.LoadOrStore(dopts, new(sync.Pool))
|
||||
var pool *sync.Pool
|
||||
var dec *zstd.Decoder
|
||||
if poolCoders() {
|
||||
vpool, ok := decoderPools.Load(dopts)
|
||||
if !ok {
|
||||
vpool, _ = decoderPools.LoadOrStore(dopts, new(sync.Pool))
|
||||
}
|
||||
pool = vpool.(*sync.Pool)
|
||||
dec, _ = pool.Get().(*zstd.Decoder)
|
||||
}
|
||||
pool := vpool.(*sync.Pool)
|
||||
dec, _ := pool.Get().(*zstd.Decoder)
|
||||
if dec == nil {
|
||||
var noopts int
|
||||
zopts := [...]zstd.DOption{
|
||||
@@ -231,7 +305,11 @@ func getDecoder(opts ...Option) decoder {
|
||||
return decoder{pool, dec, maxSize}
|
||||
}
|
||||
|
||||
func putDecoder(d decoder) { d.pool.Put(d.Decoder) }
|
||||
func putDecoder(d decoder) {
|
||||
if d.pool != nil {
|
||||
d.pool.Put(d.Decoder)
|
||||
}
|
||||
}
|
||||
|
||||
func (d decoder) DecodeAll(src, dst []byte) ([]byte, error) {
|
||||
// We only configure DecodeAll to enforce MaxDecodedSize by powers-of-two.
|
||||
|
||||
+12
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package zstdframe provides functionality for encoding and decoding
|
||||
@@ -72,6 +72,17 @@ func AppendDecode(dst, src []byte, opts ...Option) ([]byte, error) {
|
||||
return dec.DecodeAll(src, dst)
|
||||
}
|
||||
|
||||
// GetDecoder returns a decoder from the shared decoder pool for streaming
|
||||
// (io.Reader-style) use, configured with opts. The caller must call
|
||||
// dec.Reset on it before first use and whenever its underlying reader
|
||||
// changes. When done with the decoder, the caller must call the returned
|
||||
// put function exactly once to return it to the pool. The decoder must not
|
||||
// be used after put is called, and must not be used concurrently.
|
||||
func GetDecoder(opts ...Option) (dec *zstd.Decoder, put func()) {
|
||||
d := getDecoder(opts...)
|
||||
return d.Decoder, func() { putDecoder(d) }
|
||||
}
|
||||
|
||||
// NextSize parses the next frame (regardless of whether it is a
|
||||
// data frame or a metadata frame) and returns the total size of the frame.
|
||||
// The frame can be skipped by slicing n bytes from b (e.g., b[n:]).
|
||||
|
||||
Reference in new issue
Block a user