Skip to content

Commit

Permalink
Adding compressReadCloser to ensure underlying resources are closed
Browse files Browse the repository at this point in the history
  • Loading branch information
MovieStoreGuy committed Sep 20, 2024
1 parent 32086ca commit ecac030
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 1 deletion.
23 changes: 23 additions & 0 deletions config/confighttp/compress_readcloser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package confighttp

import "io"

// compressReadCloser couples the original compressed reader
// and the compression reader to ensure that the original body
// is correctly closed to ensure resources are freed.
type compressReadCloser struct {
io.Reader
orig io.ReadCloser
}

var (
_ io.Reader = (*compressReadCloser)(nil)
_ io.Closer = (*compressReadCloser)(nil)
)

func (crc *compressReadCloser) Close() error {
return crc.orig.Close()
}
77 changes: 77 additions & 0 deletions config/confighttp/compress_readcloser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package confighttp

import (
"bytes"
"errors"
"io"
"testing"
"testing/iotest"

"github.com/stretchr/testify/require"
)

type errorReadCloser struct {
io.Reader
err error
}

func (erc errorReadCloser) Close() error {
return erc.err
}

func TestCompressReadCloser(t *testing.T) {
t.Parallel()

for _, tc := range []struct {
name string
wrapper func(r io.Reader) io.ReadCloser
content []byte
errVal string
}{
{
name: "non mutating wrapper",
wrapper: func(r io.Reader) io.ReadCloser {
return errorReadCloser{
Reader: r,
err: nil,
}
},
content: []byte("hello world"),
errVal: "",
},
{
name: "failed reader",
wrapper: func(r io.Reader) io.ReadCloser {
return errorReadCloser{
Reader: r,
err: errors.New("failed to close reader"),
}
},
errVal: "failed to close reader",
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

orig := bytes.NewBuffer([]byte("hello world"))

crc := &compressReadCloser{
Reader: orig,
orig: tc.wrapper(orig),
}

require.NoError(t, iotest.TestReader(crc, orig.Bytes()), "Must be able to read original content")

err := crc.Close()
if tc.errVal != "" {
require.EqualError(t, err, tc.errVal, "Must match the expected error message")
} else {
require.NoError(t, err, "Must not error when closing reader")
}
})
}
}
5 changes: 4 additions & 1 deletion config/confighttp/compression.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ var availableDecoders = map[string]func(body io.ReadCloser) (io.ReadCloser, erro
//nolint:unparam // Ignoring the linter request to remove error return since it needs to match the method signature
"snappy": func(body io.ReadCloser) (io.ReadCloser, error) {
// Lazy Reading content to improve memory efficiency
return io.NopCloser(snappy.NewReader(body)), nil
return &compressReadCloser{
Reader: snappy.NewReader(body),
orig: body,
}, nil
},
}

Expand Down

0 comments on commit ecac030

Please sign in to comment.