Skip to content

Commit

Permalink
Add nonclobbering packages that only register if no other compressor …
Browse files Browse the repository at this point in the history
…has been registered with the same name

These new "nonclobbering" packages are suitable for other reusable
libraries to import, if they do not want to accidentally clobber
their user's previously registered compressors.
  • Loading branch information
mostynb committed Jun 16, 2023
1 parent 9905190 commit 673fd2f
Show file tree
Hide file tree
Showing 21 changed files with 881 additions and 403 deletions.
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,25 @@
This respository contains go gRPC encoding wrappers for some useful compression
algorithms that are not available in google.golang.org/grpc.

* snappy - using https://github.com/golang/snappy
* zstd - using https://github.com/klauspost/compress/tree/master/zstd
* lz4 - using https://github.com/pierrec/lz4
* github.com/mostynb/go-grpc-compression/lz4 - using https://github.com/pierrec/lz4
* github.com/mostynb/go-grpc-compression/snappy - using https://github.com/golang/snappy
* github.com/mostynb/go-grpc-compression/zstd - using https://github.com/klauspost/compress/tree/master/zstd

The following algorithms also have experimental implementations, which have
not been tested as much as those above. These may be changed significantly, or
even removed from this library at a future point.

* experimental/s2 - using https://github.com/klauspost/compress/tree/master/s2
* experimental/klauspost_snappy - using https://github.com/klauspost/compress/tree/master/s2
* github.com/mostynb/go-grpc-compression/experimental/klauspost_snappy - using https://github.com/klauspost/compress/tree/master/s2
in snappy compatibility mode
* github.com/mostynb/go-grpc-compression/experimental/s2 - using https://github.com/klauspost/compress/tree/master/s2

Importing any of the packages above will override any previously registered
encoders with the same name. If you would prefer imports to only register
the encoder if there is no previously registered encoder with the same name,
then you should instead import one of the following packages:

* github.com/mostynb/go-grpc-compression/nonclobbering/lz4
* github.com/mostynb/go-grpc-compression/nonclobbering/snappy
* github.com/mostynb/go-grpc-compression/nonclobbering/zstd
* github.com/mostynb/go-grpc-compression/nonclobbering/experimental/klauspost_snappy
* github.com/mostynb/go-grpc-compression/nonclobbering/experimental/s2
123 changes: 34 additions & 89 deletions experimental/klauspost_snappy/klauspost_snappy.go
Original file line number Diff line number Diff line change
@@ -1,98 +1,43 @@
/*
*
* Copyright 2017 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

// Package experimental/klauspost_snappy is a wrapper for using
// github.com/klauspost/compress/s2 in snappy compatibility mode with gRPC.
// It might be more efficient than the top-level snappy package which makes
// use of github.com/golang/snappy.
// Copyright 2023 Mostyn Bramley-Moore.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package github.com/mostynb/go-grpc-compression/experimental/klauspost_snappy
// is a wrapper for using github.com/klauspost/compress/s2 in snappy
// compatibility mode with gRPC. It might be more efficient than
// github.com/mostynb/go-grpc-compression/snappy and
// github.com/mostynb/go-grpc-compression/nonclobbering/snappy
// packages which make use of github.com/golang/snappy.
//
// Note that this is registered under the name "snappy" with gRPC, so only
// one of these packages should be used at a time.
//
// If you import this package, it will register itself as the encoder for
// the "snappy" compressor, overriding any previously registered compressors
// with this name.
//
// Note that this is registered under the same "snappy" name with gRPC, so
// only one of the two packages should be used at a time.
// If you don't want to override previously registered "snappy" compressors,
// then you should instead import
// github.com/mostynb/go-grpc-compression/nonclobbering/experimental/klauspost_snappy
package klauspost_snappy

// This code is based upon the gzip wrapper in github.com/grpc/grpc-go:
// https://github.com/grpc/grpc-go/blob/master/encoding/gzip/gzip.go

import (
"io"
"io/ioutil"
"sync"

snappylib "github.com/klauspost/compress/s2"
"google.golang.org/grpc/encoding"
internalsnappy "github.com/mostynb/go-grpc-compression/internal/klauspost_snappy"
)

const Name = "snappy"

type compressor struct {
poolCompressor sync.Pool
poolDecompressor sync.Pool
}

type writer struct {
*snappylib.Writer
pool *sync.Pool
}

type reader struct {
*snappylib.Reader
pool *sync.Pool
}
const Name = internalsnappy.Name

func init() {
c := &compressor{}
c.poolCompressor.New = func() interface{} {
w := snappylib.NewWriter(ioutil.Discard, snappylib.WriterSnappyCompat())
return &writer{Writer: w, pool: &c.poolCompressor}
}
encoding.RegisterCompressor(c)
}

func (c *compressor) Compress(w io.Writer) (io.WriteCloser, error) {
z := c.poolCompressor.Get().(*writer)
z.Writer.Reset(w)
return z, nil
}

func (c *compressor) Decompress(r io.Reader) (io.Reader, error) {
z, inPool := c.poolDecompressor.Get().(*reader)
if !inPool {
newR := snappylib.NewReader(r, snappylib.ReaderAllocBlock(64 << 10))
return &reader{Reader: newR, pool: &c.poolDecompressor}, nil
}
z.Reset(r)
return z, nil
}

func (c *compressor) Name() string {
return Name
}

func (z *writer) Close() error {
err := z.Writer.Close()
z.pool.Put(z)
return err
}

func (z *reader) Read(p []byte) (n int, err error) {
n, err = z.Reader.Read(p)
if err == io.EOF {
z.pool.Put(z)
}
return n, err
clobbering := true
internalsnappy.PretendInit(clobbering)
}
80 changes: 17 additions & 63 deletions experimental/s2/s2.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2022 Mostyn Bramley-Moore.
// Copyright 2023 Mostyn Bramley-Moore.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -14,73 +14,27 @@

// Package s2 is an experimental wrapper for using
// github.com/klauspost/compress/s2 stream compression with gRPC.

// Package github.com/mostynb/go-grpc-compression/s2 is an experimental
// wrapper for using github.com/klauspost/compress/s2 stream compression
// with gRPC.
//
// If you import this package, it will register itself as the encoder for
// the "s2" compressor, overriding any previously registered compressors
// with this name.
//
// If you don't want to override previously registered "s2" compressors,
// then you should instead import
// github.com/mostynb/go-grpc-compression/nonclobbering/s2
package s2

import (
"io"
"io/ioutil"
"sync"

"github.com/klauspost/compress/s2"
"google.golang.org/grpc/encoding"
internals2 "github.com/mostynb/go-grpc-compression/internal/s2"
)

const Name = "s2"

type compressor struct {
poolCompressor sync.Pool
poolDecompressor sync.Pool
}

type writer struct {
*s2.Writer
pool *sync.Pool
}

type reader struct {
*s2.Reader
pool *sync.Pool
}
const Name = internals2.Name

func init() {
c := &compressor{}
c.poolCompressor.New = func() interface{} {
w := s2.NewWriter(ioutil.Discard, s2.WriterConcurrency(1))
return &writer{Writer: w, pool: &c.poolCompressor}
}
encoding.RegisterCompressor(c)
}

func (c *compressor) Compress(w io.Writer) (io.WriteCloser, error) {
s := c.poolCompressor.Get().(*writer)
s.Writer.Reset(w)
return s, nil
}

func (c *compressor) Decompress(r io.Reader) (io.Reader, error) {
s, inPool := c.poolDecompressor.Get().(*reader)
if !inPool {
newR := s2.NewReader(r)
return &reader{Reader: newR, pool: &c.poolDecompressor}, nil
}
s.Reset(r)
return s, nil
}

func (c *compressor) Name() string {
return Name
}

func (s *writer) Close() error {
err := s.Writer.Close()
s.pool.Put(s)
return err
}

func (s *reader) Read(p []byte) (n int, err error) {
n, err = s.Reader.Read(p)
if err == io.EOF {
s.pool.Put(s)
}
return n, err
clobbering := true
internals2.PretendInit(clobbering)
}
102 changes: 102 additions & 0 deletions internal/klauspost_snappy/klauspost_snappy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
*
* Copyright 2017 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

// Package experimental/klauspost_snappy is a wrapper for using
// github.com/klauspost/compress/s2 in snappy compatibility mode with gRPC.
// It might be more efficient than the top-level snappy package which makes
// use of github.com/golang/snappy.
//
// Note that this is registered under the same "snappy" name with gRPC, so
// only one of the two packages should be used at a time.
package klauspost_snappy

// This code is based upon the gzip wrapper in github.com/grpc/grpc-go:
// https://github.com/grpc/grpc-go/blob/master/encoding/gzip/gzip.go

import (
"io"
"io/ioutil"
"sync"

snappylib "github.com/klauspost/compress/s2"
"google.golang.org/grpc/encoding"
)

const Name = "snappy"

type compressor struct {
poolCompressor sync.Pool
poolDecompressor sync.Pool
}

type writer struct {
*snappylib.Writer
pool *sync.Pool
}

type reader struct {
*snappylib.Reader
pool *sync.Pool
}

func PretendInit(clobbering bool) {
if !clobbering && encoding.GetCompressor(Name) != nil {
return
}

c := &compressor{}
c.poolCompressor.New = func() interface{} {
w := snappylib.NewWriter(ioutil.Discard, snappylib.WriterSnappyCompat())
return &writer{Writer: w, pool: &c.poolCompressor}
}
encoding.RegisterCompressor(c)
}

func (c *compressor) Compress(w io.Writer) (io.WriteCloser, error) {
z := c.poolCompressor.Get().(*writer)
z.Writer.Reset(w)
return z, nil
}

func (c *compressor) Decompress(r io.Reader) (io.Reader, error) {
z, inPool := c.poolDecompressor.Get().(*reader)
if !inPool {
newR := snappylib.NewReader(r, snappylib.ReaderAllocBlock(64<<10))
return &reader{Reader: newR, pool: &c.poolDecompressor}, nil
}
z.Reset(r)
return z, nil
}

func (c *compressor) Name() string {
return Name
}

func (z *writer) Close() error {
err := z.Writer.Close()
z.pool.Put(z)
return err
}

func (z *reader) Read(p []byte) (n int, err error) {
n, err = z.Reader.Read(p)
if err == io.EOF {
z.pool.Put(z)
}
return n, err
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ const (
)

func TestRegisteredCompression(t *testing.T) {
clobbering := true
PretendInit(clobbering)

comp := encoding.GetCompressor(Name)
require.NotNil(t, comp)
assert.Equal(t, Name, comp.Name())
Expand All @@ -62,6 +65,9 @@ func TestRegisteredCompression(t *testing.T) {
}

func TestRoundTrip(t *testing.T) {
clobbering := true
PretendInit(clobbering)

lis := bufconn.Listen(bufSize)
t.Cleanup(func() {
assert.NoError(t, lis.Close())
Expand Down
Loading

0 comments on commit 673fd2f

Please sign in to comment.