mirror of
https://github.com/nabbar/golib.git
synced 2025-12-24 11:51:02 +08:00
- 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
256 lines
8.7 KiB
Go
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
|