Files
golib/ioutils/iowrapper/doc.go
nabbar 96ed6f9a1f [Package IOUtils/Delim]
- FIX: potential CWE-400 with bufio.ReadBytes & bufio.ReadSlices, with
  no limited read buffer
- ADD: test to check overflow buffer with discard or error
- REFACTOR: all buffering package, parsing process
- UPDATE: doc, examples, test following changes
- OPTIMIZE: rework code to optimize process
- REWORK: benchmark to check benefice of optimization
- FIX: wording error

Package IOUtils/Multi:
- REWORK: re-design all package to allow sequential/parallel mode
- UPDATE: package with adaptive mode to allow switch automaticly between
  sequential and parallel mode following measurment of sample
- OPTIMIZE: code to maximize bandwith and reduce time of write
- UPDATE: documentation, test and comments
- REWORK: testing organization and benchmark aggregation

Package HttpServer:
- FIX: bug with dial addr rewrite for healtcheck & testing PortUse

Package Logger/HookFile:
- FIX: bug with race condition on aggregator counter file

Other:
- Bump dependencies
- FIX: format / import file
2025-12-21 16:56:13 +01:00

256 lines
8.7 KiB
Go

/*
* MIT License
*
* Copyright (c) 2020 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
/*
Package iowrapper provides a flexible I/O wrapper that enables customization and interception
of read, write, seek, and close operations on any underlying I/O object without modifying
its implementation.
# Philosophy
The iowrapper package follows these core principles:
- Transparency: Wrap any I/O object without changing its interface
- Flexibility: Customize operations at runtime with custom functions
- Thread Safety: All operations and updates are thread-safe via atomic operations
- Zero Overhead: Minimal performance cost when no customization is used
- Composability: Wrappers can be chained for complex transformations
# Architecture
The package consists of two main components:
1. IOWrapper Interface: Implements all standard Go I/O interfaces (io.Reader, io.Writer,
io.Seeker, io.Closer) and provides methods to set custom functions for each operation.
2. Custom Functions: Four function types (FuncRead, FuncWrite, FuncSeek, FuncClose) that
allow intercepting and transforming I/O operations.
The wrapper stores custom functions using atomic values from github.com/nabbar/golib/atomic,
ensuring thread-safe updates without mutex locks. When no custom function is set, operations
are delegated to the underlying object if it implements the corresponding interface.
# Operation Flow
For each I/O operation:
1. Load the custom function atomically
2. If a custom function is set, call it
3. Otherwise, check if the underlying object implements the required interface
4. If yes, delegate to the underlying object
5. If no, return an appropriate error (io.ErrUnexpectedEOF for Read/Write/Seek, nil for Close)
# Error Handling
The package uses a specific error handling strategy:
- Custom functions returning nil indicate an error condition, causing Read/Write to return io.ErrUnexpectedEOF
- Empty slice return ([]byte{}) from custom functions indicates 0 bytes processed without error
- Underlying object errors are encapsulated in the return values (nil for errors, partial data otherwise)
- Close operations return nil for non-closeable objects (graceful degradation)
# Thread Safety
All operations are thread-safe:
- Custom functions are stored and loaded using atomic operations
- Multiple goroutines can safely call Read, Write, Seek, or Close concurrently
- Custom functions can be updated (SetRead, SetWrite, etc.) while I/O operations are in progress
- The underlying object reference is immutable after wrapper creation
# Performance Characteristics
The wrapper introduces minimal overhead:
- Wrapper creation: ~5.7ms per 10,000 operations
- Read/Write (default): ~0-100ns per operation
- Read/Write (custom): ~0-100ns + custom function cost
- Function updates: ~100ns per operation
- Memory: ~64 bytes per wrapper instance
- Allocations: 0 per I/O operation (unless custom function allocates)
# Advantages
- Non-invasive: No modification to underlying I/O objects
- Flexible: Change behavior at runtime without code changes
- Composable: Chain multiple wrappers for complex transformations
- Efficient: Near-zero overhead for delegation
- Safe: Thread-safe by design using atomic operations
- Standard: Implements all standard Go I/O interfaces
# Limitations
- Error Context: Underlying errors are not directly propagated; they are encapsulated in return values
- No Context Support: Operations do not support context.Context for cancellation
- Single Object: Wraps a single underlying object; cannot multiplex multiple sources
- No Buffering: No built-in buffering; use bufio if needed
# Typical Use Cases
1. Logging and Monitoring
Track I/O operations for debugging or metrics without modifying application code.
2. Data Transformation
Apply transformations (encryption, compression, encoding) transparently during I/O.
3. Rate Limiting
Control throughput by intercepting operations and applying rate limits.
4. Validation
Verify data integrity or format during read/write operations.
5. Checksumming
Calculate checksums (MD5, SHA256) while reading or writing data.
6. Testing
Mock I/O behavior for unit tests without complex test fixtures.
7. Wrapper Chaining
Compose multiple transformations by wrapping wrappers (e.g., log → compress → encrypt).
# Example Usage Patterns
Basic wrapping and reading:
buf := bytes.NewBuffer([]byte("hello"))
wrapper := iowrapper.New(buf)
data := make([]byte, 5)
n, _ := wrapper.Read(data)
Custom read with transformation:
wrapper.SetRead(func(p []byte) []byte {
n, _ := reader.Read(p)
// Transform data to uppercase
for i := 0; i < n; i++ {
if p[i] >= 'a' && p[i] <= 'z' {
p[i] -= 32
}
}
return p[:n]
})
Logging wrapper:
var cnt atomic.Int64
wrapper.SetRead(func(p []byte) []byte {
n, _ := reader.Read(p)
cnt.Add(int64(n))
log.Printf("Read %d bytes (total: %d)", n, cnt.Load())
return p[:n]
})
Wrapper chaining:
// Chain: file → logging → compression → encryption
logged := iowrapper.New(file)
logged.SetRead(logFunc)
compressed := iowrapper.New(logged)
compressed.SetRead(compressFunc)
encrypted := iowrapper.New(compressed)
encrypted.SetRead(encryptFunc)
# Package Dependencies
The package depends on:
- Standard library: io (for interfaces)
- Internal: github.com/nabbar/golib/atomic (for thread-safe value storage)
# Design Decisions
1. Atomic Operations over Mutexes
The package uses atomic operations instead of mutexes for custom function storage.
This provides better performance for high-frequency I/O operations and simpler
lock-free concurrency.
2. Slice-Based Function Signatures
Custom functions use []byte return values instead of (int, error). This simplifies
the implementation and allows functions to return partial data (via slice length)
while using nil to signal errors. This design trades some Go idiomaticity for
simplicity and flexibility.
3. Immutable Underlying Object
The underlying object is set at creation and cannot be changed. This ensures
predictable behavior and avoids race conditions when accessing the underlying object.
4. Graceful Degradation
Operations on non-implementing objects return sensible defaults (error for Read/Write/Seek,
nil for Close) rather than panicking, allowing wrappers to work with partial I/O objects.
# Testing
The package has 100% test coverage with 114 specifications covering:
- Basic I/O operations
- Custom function registration and execution
- Edge cases and boundary conditions
- Error handling and propagation
- Thread safety and concurrency
- Integration scenarios (logging, transformation, checksumming)
- Performance benchmarking
Run tests with:
go test -v -cover .
go test -race . # With race detector
# Thread Safety Guarantees
- All exported methods are safe for concurrent use
- Custom functions can be updated while I/O operations are in progress
- No data races when accessed from multiple goroutines
- Atomic operations ensure visibility across CPU cores
# Best Practices
1. Minimize allocations in custom functions by reusing the provided buffer
2. Return nil from custom functions to signal errors (results in io.ErrUnexpectedEOF)
3. Use atomic types (atomic.Int64, etc.) for counters in custom functions
4. Chain wrappers for complex transformations instead of one large custom function
5. Reset custom functions to nil when returning to default behavior
6. Be aware that underlying errors are encapsulated, not directly propagated
# See Also
- io.Reader, io.Writer, io.Seeker, io.Closer: Standard Go I/O interfaces
- github.com/nabbar/golib/atomic: Thread-safe atomic value storage
- bufio: For buffered I/O on top of wrappers
*/
package iowrapper