Skip to content

Commit

Permalink
Merge pull request #19 from 4kills/autoclose
Browse files Browse the repository at this point in the history
auto close functionality for de-/compressors
  • Loading branch information
4kills authored Jan 19, 2024
2 parents 61e109e + 0d4c877 commit aa9c24d
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 7 deletions.
16 changes: 16 additions & 0 deletions v2/autoclose.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package libdeflate

import "runtime"

// panicFreeCloser is a type that can be closed without panicking if it is already closed. This is used for AutoClose functionality.
type panicFreeCloser interface {
PanicFreeClose()
}

// attachAutoClose attaches a finalizer to the given panicFreeCloser that calls PanicFreeClose() when the object is garbage collected.
// Used for AutoClose functionality. Do not call this function directly.
func attachAutoClose(c panicFreeCloser) {
runtime.SetFinalizer(c, func(finalized panicFreeCloser) {
finalized.PanicFreeClose()
})
}
28 changes: 26 additions & 2 deletions v2/compressor.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,43 @@
package libdeflate

import "github.com/4kills/go-libdeflate/v2/native"
import (
"github.com/4kills/go-libdeflate/v2/native"
)

// Compressor compresses data at the specified compression level.
//
// A single compressor must not not be used across multiple threads concurrently.
// If you want to compress concurrently, create a compressor for each thread.
//
// Always Close() the decompressor to free c memory.
// Always Close() the decompressor to free c memory or use the AutoClose constructors, which will automatically close the Compressor at garabage collection time of the underlying natvie equivalent.
// One Compressor allocates at least 32 KiB.
type Compressor struct {
c *native.Compressor
lvl int
}

// NewCompressorAutoClose returns a Compressor used to compress data with compression level DefaultCompressionLevel and AutoClose functionality.
// This will close the Compressor automatically, when it is no longer used.
// Errors if out of memory. Allocates 32KiB.
// See NewCompressorLevelPointer for custom compression level
func NewCompressorAutoClose() (Compressor, error) {
compressor, err := NewCompressorLevelAutoClose(DefaultCompressionLevel)
return compressor, err
}

// NewCompressorLevelAutoClose returns a new pointer to a Compressor used to compress data and AutoClose functionality.
// This will close the Compressor automatically, when it is no longer used.
// Errors if out of memory or if an invalid compression level was passed.
// Allocates 32KiB.
//
// The compression level is legal if and only if:
// MinCompressionLevel <= level <= MaxCompressionLevel
func NewCompressorLevelAutoClose(level int) (Compressor, error) {
compressor, err := NewCompressorLevel(level)
attachAutoClose(compressor.c)
return compressor, err
}

// NewCompressor returns a new Compressor used to compress data with compression level DefaultCompressionLevel.
// Errors if out of memory. Allocates 32KiB.
// See NewCompressorLevel for custom compression level
Expand Down
18 changes: 17 additions & 1 deletion v2/compressor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,22 @@ func TestCompressZlib(t *testing.T) {
slicesEqual([]byte(shortString), decomp, t)
}

func TestCompressZlib_AutoClose(t *testing.T) {
c, _ := NewCompressorAutoClose()
defer c.Close()
_, comp, err := c.CompressZlib(shortString, nil)
if err != nil {
t.Error(err)
}

r, _ := zlib.NewReader(bytes.NewBuffer(comp))
defer r.Close()
decomp := make([]byte, len(shortString))
r.Read(decomp)

slicesEqual([]byte(shortString), decomp, t)
}

// this test doesn't really say as much as TestCompress
func TestCompressMeta(t *testing.T) {
c, _ := NewCompressor()
Expand Down Expand Up @@ -141,7 +157,7 @@ func TestCompressDecompress(t *testing.T) {
out := make([]byte, len(shortString))
dc, _ := NewDecompressor()
defer dc.Close()
if c, _, err := dc.DecompressZlib(comp, out); err != nil || c != len(comp){
if c, _, err := dc.DecompressZlib(comp, out); err != nil || c != len(comp) {
t.Error(err)
}
slicesEqual(shortString, out, t)
Expand Down
22 changes: 21 additions & 1 deletion v2/decompressor.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,32 @@ import "github.com/4kills/go-libdeflate/v2/native"
// A single decompressor must not not be used across multiple threads concurrently.
// If you want to decompress concurrently, create a decompressor for each thread.
//
// Always Close() the decompressor to free c memory.
// Always Close() the decompressor to free c memory or use the AutoClose constructors, which will automatically close the Compressor at garabage collection time of the underlying natvie equivalent.
// One Decompressor allocates at least 32KiB.
type Decompressor struct {
dc *native.Decompressor
}

// NewDecompressor returns a new Decompressor used to decompress data at any compression level and with any Mode. It has AutoClose functionality.
// This will close the Decompressor automatically, when it is no longer used.
// Errors if out of memory. Allocates 32KiB.
func NewDecompressorAutoClose() (Decompressor, error) {
decompressor, err := NewDecompressor()
attachAutoClose(decompressor.dc)
return decompressor, err
}

// NewDecompressorWithExtendedDecompression returns a new Decompressor used to decompress data at any compression level and with any Mode.
// It has AutoClose functionality, which will close the Decompressor automatically, when it is no longer used.
// maxDecompressionFactor customizes how much larger your output than your input may be. This is set to a sensible default,
// however, it might need some tweaking if you have a huge compression factor. Usually, NewDecompressor should suffice.
// Errors if out of memory. Allocates 32KiB.
func NewDecompressorAutoCloseWithExtendedDecompression(maxDecompressionFactor int) (Decompressor, error) {
decompressor, err := NewDecompressorWithExtendedDecompression(maxDecompressionFactor)
attachAutoClose(decompressor.dc)
return decompressor, err
}

// NewDecompressor returns a new Decompressor used to decompress data at any compression level and with any Mode.
// Errors if out of memory. Allocates 32KiB.
func NewDecompressor() (Decompressor, error) {
Expand Down
25 changes: 25 additions & 0 deletions v2/decompressor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,31 @@ func TestDecompressZlib(t *testing.T) {
slicesEqual(shortString, out, t)
}

func TestDecompressZlib_AutoClose(t *testing.T) {
// compress with go standard lib
buf := &bytes.Buffer{}
w := zlib.NewWriter(buf)
w.Write(shortString)
w.Close()
in := buf.Bytes()

// decompress with this lib

out := make([]byte, len(shortString))
dc, _ := NewDecompressorAutoClose()
defer dc.Close()
if c, _, err := dc.DecompressZlib(in, out); err != nil || c != len(in) {
t.Error(err)
}
slicesEqual(shortString, out, t)

c, out, err := dc.DecompressZlib(in, nil)
if err != nil || c != len(in) {
t.Error(err)
}
slicesEqual(shortString, out, t)
}

var (
deadlyCode = "789c7d90316f83301085f7fc0ac45cac3b1b63cc0685542c55a4264b1784825bd1024686284851fe7b8104c454c" +
"b8bdff7de9def6e3b6b3cf6db298dedc0b26f02384014858efb2af78ecb1573c224e24e92301e4919d158c0dd7e7" +
Expand Down
8 changes: 8 additions & 0 deletions v2/native/compressor.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,11 @@ func (c *Compressor) Close() {
C.libdeflate_free_compressor(c.c)
c.isClosed = true
}

// PanicFreeClose is like Close but doesn't panic if the compressor is already closed. This is useful for the higher-level autoclose functionality.
func (c *Compressor) PanicFreeClose() {
if c.isClosed {
return
}
c.Close()
}
14 changes: 11 additions & 3 deletions v2/native/decompressor.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import "unsafe"

// Decompressor decompresses any DEFLATE, zlib or gzip compressed data at any level
type Decompressor struct {
dc *C.decomp
isClosed bool
dc *C.decomp
isClosed bool
maxDecompressionFactor int
}

Expand Down Expand Up @@ -80,7 +80,7 @@ func (dc *Decompressor) decompress(in, out []byte, fit bool, f decompress) (int,

var (
cons int
n int
n int
)

consPtr := uintptr(unsafe.Pointer(&cons))
Expand All @@ -106,3 +106,11 @@ func (dc *Decompressor) Close() {
C.libdeflate_free_decompressor(dc.dc)
dc.isClosed = true
}

// PanicFreeClose is like Close but doesn't panic if the decompressor is already closed. This is useful for the higher-level autoclose functionality.
func (c *Decompressor) PanicFreeClose() {
if c.isClosed {
return
}
c.Close()
}

0 comments on commit aa9c24d

Please sign in to comment.