Documentation
¶
Index ¶
- Constants
- Variables
- func BufPipe(blockSize int, storageSize int64, storage Storage) (*BufPipeReader, *BufPipeWriter)
- func CmpReadersData[L, R io.Reader](left L, right R) error
- func CmpReadersDataWithBuffer[L, R io.Reader](left L, right R, buffer []byte) error
- func ReadFill[T io.Reader](r T, buf []byte) (n int, err error)
- type BlockStorageBuffer
- type BufPipeReader
- type BufPipeWriter
- type ErrOrEofReader
- type LruReaderAt
- type LruReaderAtMetrics
- type ReadCounter
- type ReadDataAtomicCounter
- type ReadDataCounter
- type ReadersDataCmpError
- type Storage
- type StorageBuffer
Examples ¶
Constants ¶
const DefaultBlockSize = 1 * 1024 * 1024
DefaultBlockSize is the default block size used when block size is not specified.
Variables ¶
var ( ErrCmpReadError = errors.New("xio: compare: read error") ErrCmpByteCountMismatch = errors.New("xio: compare: byte count mismatch") ErrCmpDataMismatch = errors.New("xio: compare: data mismatch") )
var ErrNoSpaceLeft = errors.New("xio: no space left")
ErrNoSpaceLeft is returned when there is no space left in the underlying storage.
Functions ¶
func BufPipe ¶
func BufPipe(blockSize int, storageSize int64, storage Storage) (*BufPipeReader, *BufPipeWriter)
BufPipe creates a buffered pipe with a given block size and storage size. It was inspired by the io.Pipe function from the Go standard library.
The blockSize should be at least 512 bytes and storageSize should be at least blockSize. If blockSize is <= 0, a default value is used, DefaultBlockSize 1MiB.
If storageSize is <= 0, it is set to blockSize. If storageSize is less than blockSize then blockSize is capped to storageSize for simplicity but in terms of performance and correctness, you should provide appropriate values.
Example (Example1) ¶
package main
import (
"bytes"
"fmt"
"io"
"sync"
"github.com/ozanh/xio"
)
func main() {
const blockSize = 512
const storageSize = 1024 * 1024
storage := xio.NewBlockStorageBuffer(blockSize, storageSize)
pr, pw := xio.BufPipe(blockSize, storageSize, storage)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
_, err := pw.Write([]byte("hello"))
if err != nil {
panic(err)
}
_ = pw.Close()
}()
buf := bytes.NewBuffer(nil)
_, err := io.Copy(buf, pr)
_ = pr.CloseWithError(err)
wg.Wait()
fmt.Printf("%s", buf.Bytes())
}
Output: hello
Example (Example2) ¶
package main
import (
"crypto/rand"
"fmt"
"io"
"github.com/ozanh/xio"
)
func main() {
const blockSize = 1024 * 1024
const storageSize = 10 * 1024 * 1024
storage := xio.NewBlockStorageBuffer(blockSize, storageSize)
pr, pw := xio.BufPipe(blockSize, storageSize, storage)
go func() {
src := io.LimitReader(rand.Reader, storageSize)
_, err := io.Copy(pw, src)
_ = pw.CloseWithError(err)
}()
n, err := io.Copy(io.Discard, pr)
_ = pr.CloseWithError(err)
fmt.Printf("%d", n)
}
Output: 10485760
Example (Example3) ¶
package main
import (
"context"
"crypto/rand"
"fmt"
"io"
"sync"
"time"
"github.com/ozanh/xio"
)
func main() {
const blockSize = 1024 * 1024
const storageSize = 10 * 1024 * 1024
storage := xio.NewBlockStorageBuffer(blockSize, storageSize)
pr, pw := xio.BufPipe(blockSize, storageSize, storage)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
src := io.LimitReader(rand.Reader, storageSize)
_, err := io.Copy(pw, src)
_ = pw.CloseWithError(err)
}()
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
done := make(chan struct{})
go func() {
select {
case <-ctx.Done():
_ = pw.CloseWithError(ctx.Err())
case <-done:
}
}()
n, err := io.Copy(io.Discard, pr)
_ = pr.CloseWithError(err)
close(done)
wg.Wait()
fmt.Printf("%d", n)
}
Output: 10485760
Example (Example4) ¶
package main
import (
"crypto/rand"
"fmt"
"io"
"os"
"github.com/ozanh/xio"
)
func main() {
const blockSize = 1024 * 1024
const storageSize = 10 * 1024 * 1024
storage, err := os.CreateTemp("", "storage")
if err != nil {
panic(err)
}
defer os.Remove(storage.Name())
defer storage.Close()
err = storage.Truncate(storageSize)
if err != nil {
panic(err)
}
pr, pw := xio.BufPipe(blockSize, storageSize, storage)
go func() {
src := io.LimitReader(rand.Reader, 40_000_000)
_, err := io.Copy(pw, src)
_ = pw.CloseWithError(err)
}()
n, err := io.Copy(io.Discard, pr)
if err != nil {
panic(err)
}
fmt.Printf("%d", n)
}
Output: 40000000
func CmpReadersData ¶ added in v0.3.0
CmpReadersData compares the data read from two readers. It returns an error if the data read from the readers is not equal. CmpReadersData reads data from the readers in chunks and compares the data in the chunks. If the data read from the readers is not equal, it returns an error with the data read from the readers. The error is of type *ReadersDataCmpError.
Example ¶
package main
import (
"bufio"
"bytes"
"crypto/rand"
"fmt"
"github.com/ozanh/xio"
)
func main() {
b := make([]byte, 1000*1000)
_, err := xio.ReadFill(rand.Reader, b)
if err != nil {
panic(err)
}
r1 := bytes.NewReader(b)
r2 := bufio.NewReader(bytes.NewReader(b))
err = xio.CmpReadersData(r1, r2)
if err != nil {
panic(err)
}
fmt.Println("Readers have equal data")
}
Output: Readers have equal data
func CmpReadersDataWithBuffer ¶ added in v0.3.0
CmpReadersDataWithBuffer is like CmpReadersData but accepts a buffer to use for reading data from the readers. If the buffer is not provided, a new buffer of size 64KB is used. Given buffer is split into two halves and used for reading data from the readers.
func ReadFill ¶
ReadFill reads from r into buf until it is full or an error occurs. It returns the number of bytes read into buf and the error, if any. ReadFill returns io.ErrShortBuffer if len(buf) < 1. ReadFill returns io.ErrNoProgress if the reader returns 0 bytes for maxConsecutiveEmptyReads times. ReadFill panics if the reader returns a negative count from Read.
Types ¶
type BlockStorageBuffer ¶
type BlockStorageBuffer struct {
// contains filtered or unexported fields
}
BlockStorageBuffer implements the Storage interface using a slice of byte slices. It is suitable for large data storage instead of using a single byte slice. BlockStorageBuffer methods are goroutine-safe.
func NewBlockStorageBuffer ¶
func NewBlockStorageBuffer(blockSize, storageSize int) *BlockStorageBuffer
NewBlockStorageBuffer creates a new BlockStorageBuffer with the given block size and storage size.
func (*BlockStorageBuffer) BlockSize ¶
func (bs *BlockStorageBuffer) BlockSize() int
BlockSize returns the block size.
func (*BlockStorageBuffer) ReadAt ¶
func (bs *BlockStorageBuffer) ReadAt(p []byte, off int64) (n int, err error)
ReadAt reads len(p) bytes from the storage starting at byte offset off. It implements io.ReaderAt interface.
func (*BlockStorageBuffer) StorageSize ¶
func (bs *BlockStorageBuffer) StorageSize() int
StorageSize returns the storage size.
type BufPipeReader ¶
type BufPipeReader struct {
// contains filtered or unexported fields
}
BufPipeReader is the read side of a buffered pipe. Close() or CloseWithError() should be called when the reader is no longer needed.
func (*BufPipeReader) Close ¶
func (br *BufPipeReader) Close() error
Close closes the reader; subsequent writes to the write side of the buffered pipe will return the error io.ErrClosedPipe.
Normally, receiving io.ErrClosedPipe error in the write side means that read side is closed without an error, maybe not interested in reading anymore.
Close() always returns nil error. See CloseWithError() for closing with an error, and an example.
func (*BufPipeReader) CloseWithError ¶
func (br *BufPipeReader) CloseWithError(err error) error
CloseWithError closes the reader; subsequent writes to the write side of the buffered pipe will receive the provided err.
Calling CloseWithError(nil) is the same as calling Close(). CloseWithError() always returns nil error.
Example:
pr, pw := BufPipe(/*...*/) // ... n, err := io.Copy(dest, pr) pipeReader.CloseWithError(err)
CloseWithError(err) is more ergonomic over Close() with io.Copy().
Subsequest Read() after Close() or CloseWithError() will receive io.ErrClosedPipe.
func (*BufPipeReader) Read ¶
func (br *BufPipeReader) Read(p []byte) (n int, err error)
Read implements the io.Reader interface. It reads written data from the buffer, and blocks until a written block arrives from the write side, or the read/write close happens. If write side closes normally (without an error), read side will receive io.EOF after reading the buffered data.
type BufPipeWriter ¶
type BufPipeWriter struct {
// contains filtered or unexported fields
}
BufPipeWriter is the write side of a buffered pipe.
func (*BufPipeWriter) Close ¶
func (bw *BufPipeWriter) Close() error
Close closes the writer and flushes the last block; subsequent reads from the read side of the buffered pipe will receive io.EOF until all the buffered data is read, if read side is not closed in between.
Close() must be called when the writer is no longer needed because it flushes the last block to the storage, and read side cannot stop reading until a close happens in the write or read side.
Close must happen after all the writes are done. Subsequent Write() after Close() will receive io.ErrClosedPipe error.
Close() always returns nil error.
Calling CloseWithError(nil) is the same as calling Close(). See CloseWithError() for closing with an error, and an example.
func (*BufPipeWriter) CloseWithError ¶
func (bw *BufPipeWriter) CloseWithError(err error) error
CloseWithError closes the writer; subsequent reads from the read side of the buffered pipe will receive this error err. If err is nil, io.EOF is used, which is the same as calling Close().
CloseWithError() with a non-nil error can be called anytime to stop read and write operations. Note that it does not flush the last block for the read side. Closing is only taken into account from the Write() before actual write to the underlying storage.
CloseWithError() always returns nil error.
Example:
pr, pw := BufPipe(/*...*/) // ... n, err := io.Copy(pw, source) pw.CloseWithError(err)
Subsequent Write() after Close() or CloseWithError() will receive io.ErrClosedPipe.
func (*BufPipeWriter) Write ¶
func (bw *BufPipeWriter) Write(p []byte) (n int, err error)
Write implements the io.Writer interface: it writes data to the underlying storage. After filling the current block, it enqueues the block for the read side, and tries to continue writing to the next available block, which is provided by the read side.
Returning n (number of bytes written) means that the data is written to the underlying storage, read side reads the data from the storage asynchronously. To flush the last block to the storage, call Close() otherwise the last block will not be seen by the read side.
type ErrOrEofReader ¶
type ErrOrEofReader struct {
Err error
}
ErrOrEofReader is an io.Reader implementation that always returns an error set in Err field or io.EOF if Err is nil.
type LruReaderAt ¶ added in v0.2.0
LruReaderAt wraps an io.ReaderAt and caches its data in an LRU cache. It is designed for reading random offsets efficiently, making it suitable for scenarios where repeated reads from non-contiguous regions occur. Use NewLruReaderAt to create an instance for your underlying reader. Underlying reader should not be modified while the LruReaderAt is in use to avoid data inconsistency.
It is safe for concurrent use, but method calls are synchronized with a mutex.
See ReadAt method for the read semantics.
Example (Simple) ¶
package main
import (
"fmt"
"github.com/ozanh/xio"
)
func main() {
// Simple example of using LruReaderAt with xio.StorageBuffer.
// Any io.ReaderAt can be used as the underlying reader.
const helloWorld = "Hello, World!"
rw := xio.NewStorageBuffer(make([]byte, 1024), false)
n, err := rw.WriteAt([]byte(helloWorld), 0)
if err != nil {
panic(err)
}
if n != len(helloWorld) {
panic("write failed")
}
const blockSize = 512
const cacheSize = 10
lruReader, err := xio.NewLruReaderAt(rw, blockSize, cacheSize)
if err != nil {
panic(err)
}
buf := make([]byte, len(helloWorld))
n, err = lruReader.ReadAt(buf, 0)
if err != nil {
panic(err)
}
if n != len(helloWorld) {
panic("read failed")
}
fmt.Printf("%s\n", buf)
}
Output: Hello, World!
func NewLruReaderAt ¶ added in v0.2.0
func NewLruReaderAt[T io.ReaderAt](reader T, blockSize, cacheSize int) (*LruReaderAt[T], error)
NewLruReaderAt creates a new CachingReaderAt with the given reader, blockSize, and cacheSize. blockSize and cacheSize should be a power of 2 for better performance. blockSize and cacheSize must be greater than 0. blockSize and cacheSize must be less than MaxUint32.
func (*LruReaderAt[T]) Metrics ¶ added in v0.2.0
func (lra *LruReaderAt[T]) Metrics() LruReaderAtMetrics
Metrics returns the current metrics of the LruReaderAt. Purge resets these metrics.
func (*LruReaderAt[T]) Purge ¶ added in v0.2.0
func (lra *LruReaderAt[T]) Purge()
Purge purges the underlying lru cache, and resets the metrics.
func (*LruReaderAt[T]) ReadAt ¶ added in v0.2.0
func (lra *LruReaderAt[T]) ReadAt(p []byte, offset int64) (n int, err error)
ReadAt implements the io.ReaderAt and reads len(p) bytes into p starting at offset, using the cache where possible. If a cached block doesn’t fully satisfy the request, it reads the remainder from the underlying reader.
If number of read bytes is equal to the len(p), it always returns nil error if EOF was reached.
If the underlying reader does not implement the io.ReaderAt interface correctly and error is nil when n < len(p) , it does not cache the read bytes that are less than the block size, and returns without reading the remaining bytes.
func (*LruReaderAt[T]) Reset ¶ added in v0.3.2
func (lra *LruReaderAt[T]) Reset(reader T)
Reset resets the LruReaderAt with a new reader and purges the cache for reuse.
type LruReaderAtMetrics ¶ added in v0.2.0
type LruReaderAtMetrics struct {
CacheInserts uint64
CacheCollisions uint64
CacheEvictions uint64
CacheRemovals uint64
CacheHits uint64
CacheMisses uint64
CacheHitBytes uint64
PoolAllocs uint64
CachedCount uint64
}
LruReaderAtMetrics contains the metrics of an LruReaderAt.
type ReadCounter ¶
ReadCounter is an interface that extends io.Reader with a Count method. The Count method returns the total number of bytes read from the reader.
type ReadDataAtomicCounter ¶
ReadDataAtomicCounter is a wrapper for an io.Reader that counts the number of bytes read. The Count method returns the total number of bytes read from the reader. Call Count to get total number of bytes read. Note: Count() is goroutine-safe.
func NewReadDataAtomicCounter ¶
func NewReadDataAtomicCounter[T io.Reader](r T) *ReadDataAtomicCounter[T]
func (*ReadDataAtomicCounter[T]) Count ¶
func (r *ReadDataAtomicCounter[T]) Count() uint64
Count returns the total number of bytes read from the reader. It is goroutine-safe.
type ReadDataCounter ¶
ReadDataCounter is a wrapper for an io.Reader that counts the number of bytes read. The Count method returns the total number of bytes read from the reader. Call Count to get total number of bytes read. Note: Count() is not goroutine-safe. Use ReadDataAtomicCounter[T] for goroutine-safe counting.
func NewReadDataCounter ¶
func NewReadDataCounter[T io.Reader](r T) *ReadDataCounter[T]
NewReadDataCounter returns a new ReadDataCounter that wraps the given reader.
func (*ReadDataCounter[T]) Count ¶
func (r *ReadDataCounter[T]) Count() uint64
Count returns the total number of bytes read from the reader. It is not goroutine-safe.
type ReadersDataCmpError ¶ added in v0.3.0
type ReadersDataCmpError struct {
Err error
ErrLeft error
ErrRight error
BytesLeft []byte
BytesRight []byte
Offset int64
}
ReadersDataCmpError is an error returned by CmpReadersData when the data read from the readers is not equal.
func (*ReadersDataCmpError) Error ¶ added in v0.3.0
func (e *ReadersDataCmpError) Error() string
func (*ReadersDataCmpError) Unwrap ¶ added in v0.3.0
func (e *ReadersDataCmpError) Unwrap() error
type Storage ¶
Storage is the interface that wraps the basic io.ReaderAt and io.WriterAt interfaces.
type StorageBuffer ¶
type StorageBuffer struct {
// contains filtered or unexported fields
}
StorageBuffer implements the Storage interface using a single byte slice. It is for testing, debug or small data storage. StorageBuffer methods are goroutine-safe.
func NewStorageBuffer ¶
func NewStorageBuffer(buf []byte, autoGrow bool) *StorageBuffer
NewStorageBuffer creates a new StorageBuffer with the given byte slice and auto grow flag. StorageBuffer is suitable for small data storage and testing, for large data storage, use BlockStorageBuffer.
func (*StorageBuffer) AutoGrow ¶
func (s *StorageBuffer) AutoGrow() bool
AutoGrow reports whether the storage buffer is auto growing, which is set when creating the storage buffer.
func (*StorageBuffer) Bytes ¶
func (s *StorageBuffer) Bytes() []byte
Bytes returns the underlying byte slice.
func (*StorageBuffer) Len ¶
func (s *StorageBuffer) Len() int
Len returns the length of the underlying byte slice.