Documentation
¶
Overview ¶
Package bandwidth provides bandwidth throttling and rate limiting for file I/O operations.
Design Philosophy ¶
The bandwidth package implements time-based throttling using atomic operations for thread-safe concurrent usage. It seamlessly integrates with the github.com/nabbar/golib/file/progress package to enforce bytes-per-second transfer limits on file operations.
Key principles:
- Zero-cost when unlimited: Setting limit to 0 disables throttling with no overhead
- Atomic operations: Thread-safe concurrent access without mutexes
- Callback integration: Seamless integration with progress tracking callbacks
- Time-based limiting: Enforces rate limits by introducing sleep delays
Architecture ¶
The package consists of two main components:
┌─────────────────────────────────────────────┐
│ BandWidth Interface │
│ ┌───────────────────────────────────────┐ │
│ │ RegisterIncrement(fpg, callback) │ │
│ │ RegisterReset(fpg, callback) │ │
│ └───────────────────────────────────────┘ │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ bw Implementation │
│ ┌───────────────────────────────────────┐ │
│ │ t: atomic.Value (timestamp) │ │
│ │ l: Size (bytes per second limit) │ │
│ └───────────────────────────────────────┘ │
│ ┌───────────────────────────────────────┐ │
│ │ Increment(size) - enforce limit │ │
│ │ Reset(size, current) - clear state │ │
│ └───────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ Progress Package Integration │
│ ┌───────────────────────────────────────┐ │
│ │ FctIncrement callbacks │ │
│ │ FctReset callbacks │ │
│ └───────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
The BandWidth interface provides registration methods that wrap user callbacks with bandwidth limiting logic. The internal bw struct tracks the last operation timestamp atomically and calculates sleep durations to enforce the configured limit.
Rate Limiting Algorithm ¶
The throttling algorithm works as follows:
- Store timestamp when bytes are transferred
- On next transfer, calculate elapsed time since last timestamp
- Calculate current rate: rate = bytes / elapsed_seconds
- If rate > limit, calculate required sleep: sleep = (rate / limit) * 1s
- Sleep to enforce limit (capped at 1 second maximum)
- Store new timestamp
This approach provides smooth rate limiting without strict per-operation delays, allowing burst transfers when the average rate is below the limit.
Performance ¶
The package is designed for minimal overhead:
- Zero-cost unlimited: No overhead when limit is 0
- Atomic operations: Lock-free timestamp storage
- No allocations: Reuses atomic.Value for timestamp storage
- Efficient calculation: Simple floating-point math for rate calculation
- Bounded sleep: Maximum 1 second sleep per operation prevents excessive delays
Typical overhead with limiting enabled: <1ms per operation for sleep calculation
Use Cases ¶
1. Network Bandwidth Control
Control upload/download speeds to avoid overwhelming network connections:
bw := bandwidth.New(size.SizeMiB) // 1 MB/s limit
fpg, _ := progress.Open("upload.dat")
bw.RegisterIncrement(fpg, nil)
io.Copy(networkConn, fpg) // Throttled to 1 MB/s
2. Disk I/O Rate Limiting
Prevent disk saturation during large file operations:
bw := bandwidth.New(10 * size.SizeMiB) // 10 MB/s
fpg, _ := progress.Open("large_backup.tar")
bw.RegisterIncrement(fpg, func(sz int64) {
fmt.Printf("Progress: %d bytes\n", sz)
})
io.Copy(destination, fpg)
3. Multi-File Shared Bandwidth
Control aggregate bandwidth across multiple concurrent transfers:
sharedBW := bandwidth.New(5 * size.SizeMiB) // Shared 5 MB/s
for _, file := range files {
go func(f string) {
fpg, _ := progress.Open(f)
sharedBW.RegisterIncrement(fpg, nil)
io.Copy(destination, fpg)
}(file)
}
4. Progress Monitoring with Rate Limiting
Combine bandwidth limiting with progress tracking:
bw := bandwidth.New(size.SizeMiB)
fpg, _ := progress.Open("data.bin")
bw.RegisterIncrement(fpg, func(sz int64) {
pct := float64(sz) / float64(fileSize) * 100
fmt.Printf("Progress: %.1f%%\n", pct)
})
io.Copy(writer, fpg) // 1 MB/s with progress updates
Limitations ¶
1. Single-byte granularity: Limit specified in bytes per second 2. Time-based accuracy: Depends on system clock resolution 3. No burst control: Does not enforce strict per-operation limits 4. No traffic shaping: Simple rate limiting without advanced QoS
Best Practices ¶
DO:
- Use 0 for unlimited bandwidth (zero overhead)
- Set reasonable limits based on expected transfer rates
- Share BandWidth instances across multiple files for aggregate limiting
- Monitor progress with callbacks for user feedback
DON'T:
- Use extremely small limits (<100 bytes/s) - may cause excessive sleep overhead
- Modify limit during active transfers - create new instance instead
- Rely on precise rate limiting - algorithm provides approximate limiting
Thread Safety ¶
The BandWidth instance is safe for concurrent use across multiple goroutines. The atomic.Value provides lock-free access to the timestamp, allowing concurrent RegisterIncrement and RegisterReset calls without contention.
However, the actual rate limiting is applied per-operation, so concurrent operations on the same BandWidth instance will each independently enforce the limit. For true aggregate bandwidth control, ensure operations are serialized or use separate instances per goroutine with appropriate limits.
Integration with Progress Package ¶
The bandwidth package is designed to work seamlessly with github.com/nabbar/golib/file/progress:
┌───────────┐ RegisterIncrement ┌────────────┐
│ BandWidth │◄───────────────────────►│ Progress │
└───────────┘ RegisterReset └────────────┘
│ │
│ Enforce rate limit │ Track bytes
▼ ▼
Increment(size) FctIncrement(size)
Reset(size, cur) FctReset(size, cur)
The BandWidth wrapper calls are inserted into the progress callback chain, ensuring rate limiting is applied transparently during file I/O operations.
Example Usage Patterns ¶
Basic usage with default options:
bw := bandwidth.New(size.SizeMiB)
fpg, _ := progress.Open("file.dat")
bw.RegisterIncrement(fpg, nil)
io.Copy(destination, fpg)
With progress callback:
bw := bandwidth.New(2 * size.SizeMiB)
fpg, _ := progress.Open("file.dat")
bw.RegisterIncrement(fpg, func(sz int64) {
fmt.Printf("Transferred: %d bytes\n", sz)
})
io.Copy(destination, fpg)
With reset callback:
bw := bandwidth.New(size.SizeMiB)
fpg, _ := progress.Open("file.dat")
bw.RegisterReset(fpg, func(sz, cur int64) {
fmt.Printf("Reset: max=%d current=%d\n", sz, cur)
})
io.Copy(destination, fpg)
Unlimited bandwidth (no throttling):
bw := bandwidth.New(0) // Zero overhead
fpg, _ := progress.Open("file.dat")
bw.RegisterIncrement(fpg, nil)
io.Copy(destination, fpg)
Related Packages ¶
- github.com/nabbar/golib/file/progress - Progress tracking for file I/O
- github.com/nabbar/golib/size - Size constants and utilities
- github.com/nabbar/golib/file/perm - File permissions handling
References ¶
- Go Atomic Operations: https://pkg.go.dev/sync/atomic
- Rate Limiting Patterns: https://en.wikipedia.org/wiki/Rate_limiting
- Token Bucket Algorithm: https://en.wikipedia.org/wiki/Token_bucket
Package bandwidth provides bandwidth throttling and rate limiting for file I/O operations.
This package integrates seamlessly with the file/progress package to control data transfer rates in bytes per second. It implements time-based throttling using atomic operations for thread-safe concurrent usage.
Key features:
- Configurable bytes-per-second limits
- Zero-cost when set to unlimited (0 bytes/second)
- Thread-safe atomic operations
- Seamless integration with progress tracking
Example usage:
import (
"github.com/nabbar/golib/file/bandwidth"
"github.com/nabbar/golib/file/progress"
"github.com/nabbar/golib/size"
)
// Create bandwidth limiter (1 MB/s)
bw := bandwidth.New(size.SizeMiB)
// Open file with progress tracking
fpg, _ := progress.Open("largefile.dat")
defer fpg.Close()
// Register bandwidth limiting
bw.RegisterIncrement(fpg, nil)
// All I/O operations will be throttled to 1 MB/s
Example (Basic) ¶
Example_basic demonstrates the simplest usage of the bandwidth package with unlimited bandwidth (no throttling).
package main
import (
"fmt"
"io"
"os"
"github.com/nabbar/golib/file/bandwidth"
libfpg "github.com/nabbar/golib/file/progress"
)
func main() {
// Create a temporary test file
tmpFile, _ := os.CreateTemp("", "example-basic-*.dat")
defer os.Remove(tmpFile.Name())
tmpFile.Write([]byte("Hello, World!"))
tmpFile.Close()
// Create bandwidth limiter with 0 (unlimited)
bw := bandwidth.New(0)
// Open file with progress tracking
fpg, _ := libfpg.Open(tmpFile.Name())
defer fpg.Close()
// Register bandwidth limiting (no-op with 0 limit)
bw.RegisterIncrement(fpg, nil)
// Read file content
data, _ := io.ReadAll(fpg)
fmt.Printf("Read %d bytes\n", len(data))
}
Output: Read 13 bytes
Example (CustomLimit) ¶
Example_customLimit demonstrates using a custom bandwidth limit value.
package main
import (
"fmt"
"io"
"os"
"github.com/nabbar/golib/file/bandwidth"
libfpg "github.com/nabbar/golib/file/progress"
libsiz "github.com/nabbar/golib/size"
)
func main() {
// Create a temporary test file
tmpFile, _ := os.CreateTemp("", "example-custom-*.dat")
defer os.Remove(tmpFile.Name())
tmpFile.Write(make([]byte, 512))
tmpFile.Close()
// Create bandwidth limiter with custom limit: 10 MB/s
customLimit := libsiz.Size(10 * libsiz.SizeMega)
bw := bandwidth.New(customLimit)
// Open file with progress tracking
fpg, _ := libfpg.Open(tmpFile.Name())
defer fpg.Close()
// Register with no callback
bw.RegisterIncrement(fpg, nil)
// Read file
data, _ := io.ReadAll(fpg)
fmt.Printf("Read %d bytes with custom limit\n", len(data))
}
Output: Read 512 bytes with custom limit
Example (HighBandwidth) ¶
Example_highBandwidth demonstrates using high bandwidth limits for fast transfers.
package main
import (
"fmt"
"io"
"os"
"github.com/nabbar/golib/file/bandwidth"
libfpg "github.com/nabbar/golib/file/progress"
libsiz "github.com/nabbar/golib/size"
)
func main() {
// Create a temporary test file
tmpFile, _ := os.CreateTemp("", "example-high-*.dat")
defer os.Remove(tmpFile.Name())
tmpFile.Write(make([]byte, 1024))
tmpFile.Close()
// Create bandwidth limiter: 100 MB/s (very high)
bw := bandwidth.New(100 * libsiz.SizeMega)
// Open file with progress tracking
fpg, _ := libfpg.Open(tmpFile.Name())
defer fpg.Close()
// Register bandwidth limiting
bw.RegisterIncrement(fpg, nil)
// Read file content (essentially no throttling for small files)
data, _ := io.ReadAll(fpg)
fmt.Printf("Read %d bytes with high bandwidth limit\n", len(data))
}
Output: Read 1024 bytes with high bandwidth limit
Example (MultipleCallbacks) ¶
Example_multipleCallbacks demonstrates using both increment and reset callbacks together.
package main
import (
"fmt"
"os"
"github.com/nabbar/golib/file/bandwidth"
libfpg "github.com/nabbar/golib/file/progress"
)
func main() {
// Create a temporary test file
tmpFile, _ := os.CreateTemp("", "example-multi-*.dat")
defer os.Remove(tmpFile.Name())
tmpFile.Write(make([]byte, 1024))
tmpFile.Close()
// Create bandwidth limiter: unlimited for testing
bw := bandwidth.New(0)
// Open file with progress tracking
fpg, _ := libfpg.Open(tmpFile.Name())
defer fpg.Close()
// Track increments
var incrementCount int
bw.RegisterIncrement(fpg, func(size int64) {
incrementCount++
})
// Track resets
var resetCount int
bw.RegisterReset(fpg, func(size, current int64) {
resetCount++
})
// Perform operations
buffer := make([]byte, 256)
fpg.Read(buffer)
fpg.Reset(1024)
fmt.Printf("Increments: %d, Resets: %d\n", incrementCount, resetCount)
}
Output: Increments: 1, Resets: 1
Example (NilCallbacks) ¶
Example_nilCallbacks demonstrates that nil callbacks are safely handled.
package main
import (
"fmt"
"io"
"os"
"github.com/nabbar/golib/file/bandwidth"
libfpg "github.com/nabbar/golib/file/progress"
)
func main() {
// Create a temporary test file
tmpFile, _ := os.CreateTemp("", "example-nil-*.dat")
defer os.Remove(tmpFile.Name())
tmpFile.Write(make([]byte, 256))
tmpFile.Close()
// Create bandwidth limiter: unlimited
bw := bandwidth.New(0)
// Open file with progress tracking
fpg, _ := libfpg.Open(tmpFile.Name())
defer fpg.Close()
// Register with nil callbacks (safe to do)
bw.RegisterIncrement(fpg, nil)
bw.RegisterReset(fpg, nil)
// Read file content
data, _ := io.ReadAll(fpg)
fmt.Printf("Read %d bytes with nil callbacks\n", len(data))
}
Output: Read 256 bytes with nil callbacks
Example (ProgressTracking) ¶
Example_progressTracking demonstrates combining bandwidth limiting with detailed progress tracking.
package main
import (
"fmt"
"io"
"os"
"github.com/nabbar/golib/file/bandwidth"
libfpg "github.com/nabbar/golib/file/progress"
)
func main() {
// Create a temporary test file
tmpFile, _ := os.CreateTemp("", "example-progress-*.dat")
defer os.Remove(tmpFile.Name())
testData := make([]byte, 4096) // 4KB
tmpFile.Write(testData)
tmpFile.Close()
// Create bandwidth limiter: unlimited for testing
bw := bandwidth.New(0)
// Open file with progress tracking
fpg, _ := libfpg.Open(tmpFile.Name())
defer fpg.Close()
// Track detailed progress
var totalBytes int64
bw.RegisterIncrement(fpg, func(size int64) {
totalBytes += size
})
// Read file content
io.ReadAll(fpg)
fmt.Printf("Transfer complete: %d bytes total\n", totalBytes)
}
Output: Transfer complete: 4096 bytes total
Example (VariousSizes) ¶
Example_variousSizes demonstrates bandwidth limiting with different size constants.
package main
import (
"fmt"
"io"
"os"
"github.com/nabbar/golib/file/bandwidth"
libfpg "github.com/nabbar/golib/file/progress"
libsiz "github.com/nabbar/golib/size"
)
func main() {
// Create test files
tmpFile1, _ := os.CreateTemp("", "example-sizes-1-*.dat")
tmpFile2, _ := os.CreateTemp("", "example-sizes-2-*.dat")
tmpFile3, _ := os.CreateTemp("", "example-sizes-3-*.dat")
defer os.Remove(tmpFile1.Name())
defer os.Remove(tmpFile2.Name())
defer os.Remove(tmpFile3.Name())
tmpFile1.Write(make([]byte, 128))
tmpFile2.Write(make([]byte, 256))
tmpFile3.Write(make([]byte, 512))
tmpFile1.Close()
tmpFile2.Close()
tmpFile3.Close()
// Different bandwidth limits (high values for testing)
limits := []libsiz.Size{
10 * libsiz.SizeMega, // 10 MB/s
100 * libsiz.SizeMega, // 100 MB/s
libsiz.SizeGiga, // 1 GB/s
}
files := []string{tmpFile1.Name(), tmpFile2.Name(), tmpFile3.Name()}
for i, limit := range limits {
bw := bandwidth.New(limit)
fpg, _ := libfpg.Open(files[i])
bw.RegisterIncrement(fpg, nil)
data, _ := io.ReadAll(fpg)
fpg.Close()
fmt.Printf("File %d: %d bytes with high bandwidth limit\n", i+1, len(data))
}
}
Output: File 1: 128 bytes with high bandwidth limit File 2: 256 bytes with high bandwidth limit File 3: 512 bytes with high bandwidth limit
Example (WithCallback) ¶
Example_withCallback demonstrates bandwidth limiting with a progress callback.
package main
import (
"fmt"
"io"
"os"
"github.com/nabbar/golib/file/bandwidth"
libfpg "github.com/nabbar/golib/file/progress"
libsiz "github.com/nabbar/golib/size"
)
func main() {
// Create a temporary test file
tmpFile, _ := os.CreateTemp("", "example-callback-*.dat")
defer os.Remove(tmpFile.Name())
tmpFile.Write(make([]byte, 2048)) // 2KB
tmpFile.Close()
// Create bandwidth limiter: 10 MB/s (high enough for testing)
bw := bandwidth.New(10 * libsiz.SizeMega)
// Open file with progress tracking
fpg, _ := libfpg.Open(tmpFile.Name())
defer fpg.Close()
// Track progress with callback
var totalBytes int64
bw.RegisterIncrement(fpg, func(size int64) {
totalBytes += size // Accumulate total
})
// Read file content
io.ReadAll(fpg)
fmt.Printf("Transferred %d bytes total\n", totalBytes)
}
Output: Transferred 2048 bytes total
Example (WithLimit) ¶
Example_withLimit demonstrates bandwidth limiting with a specific bytes-per-second rate.
package main
import (
"fmt"
"io"
"os"
"github.com/nabbar/golib/file/bandwidth"
libfpg "github.com/nabbar/golib/file/progress"
libsiz "github.com/nabbar/golib/size"
)
func main() {
// Create a temporary test file
tmpFile, _ := os.CreateTemp("", "example-limit-*.dat")
defer os.Remove(tmpFile.Name())
tmpFile.Write(make([]byte, 1024)) // 1KB
tmpFile.Close()
// Create bandwidth limiter: 10 MB/s (high enough to avoid throttling in test)
bw := bandwidth.New(10 * libsiz.SizeMega)
// Open file with progress tracking
fpg, _ := libfpg.Open(tmpFile.Name())
defer fpg.Close()
// Register bandwidth limiting
bw.RegisterIncrement(fpg, nil)
// Read file content (limited but fast for testing)
data, _ := io.ReadAll(fpg)
fmt.Printf("Read %d bytes with bandwidth limit\n", len(data))
}
Output: Read 1024 bytes with bandwidth limit
Example (WithResetCallback) ¶
Example_withResetCallback demonstrates handling reset events during file operations.
package main
import (
"fmt"
"os"
"github.com/nabbar/golib/file/bandwidth"
libfpg "github.com/nabbar/golib/file/progress"
)
func main() {
// Create a temporary test file
tmpFile, _ := os.CreateTemp("", "example-reset-*.dat")
defer os.Remove(tmpFile.Name())
tmpFile.Write(make([]byte, 1024))
tmpFile.Close()
// Create bandwidth limiter
bw := bandwidth.New(0)
// Open file with progress tracking
fpg, _ := libfpg.Open(tmpFile.Name())
defer fpg.Close()
// Register reset callback
var resetCalled bool
bw.RegisterReset(fpg, func(size, current int64) {
resetCalled = true
fmt.Printf("Reset called with size=%d, current=%d\n", size, current)
})
// Read part of file and reset
buffer := make([]byte, 512)
fpg.Read(buffer)
fpg.Reset(1024)
if resetCalled {
fmt.Println("Reset was triggered")
}
}
Output: Reset called with size=1024, current=512 Reset was triggered
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type BandWidth ¶
type BandWidth interface {
// RegisterIncrement registers a bandwidth-limited increment callback with a progress tracker.
//
// This method wraps the provided callback function with bandwidth throttling logic. When
// the progress tracker detects bytes transferred, the bandwidth limiter enforces the
// configured rate limit before invoking the user-provided callback.
//
// The callback function will be invoked with the total number of bytes transferred since
// the last increment. The callback is optional; if nil, only bandwidth limiting is applied
// without additional notification.
//
// Parameters:
// - fpg: Progress tracker to register the callback with
// - fi: Optional callback function with signature func(size int64)
//
// The callback is invoked:
// - After each read/write operation that transfers data
// - When the file reaches EOF
// - Even if the file is smaller than expected
//
// Thread safety: This method is safe to call concurrently with other BandWidth methods.
//
// Example:
// bw.RegisterIncrement(fpg, func(size int64) {
// fmt.Printf("Transferred %d bytes at limited rate\n", size)
// })
RegisterIncrement(fpg libfpg.Progress, fi libfpg.FctIncrement)
// RegisterReset registers a reset callback that clears bandwidth tracking state.
//
// This method registers a callback to be invoked when the progress tracker is reset.
// The bandwidth limiter clears its internal timestamp state, allowing a fresh rate
// calculation after the reset. The user-provided callback is then invoked with
// the reset parameters.
//
// Parameters:
// - fpg: Progress tracker to register the callback with
// - fr: Optional callback function with signature func(size, current int64)
//
// The callback receives:
// - size: Maximum progress reached before reset
// - current: Current progress at the time of reset
//
// The callback is invoked:
// - When fpg.Reset() is explicitly called
// - When the file is repositioned (seek operations)
// - When io.Copy completes and progress is finalized
//
// Thread safety: This method is safe to call concurrently with other BandWidth methods.
//
// Example:
// bw.RegisterReset(fpg, func(size, current int64) {
// fmt.Printf("Reset: max=%d current=%d\n", size, current)
// })
RegisterReset(fpg libfpg.Progress, fr libfpg.FctReset)
}
BandWidth defines the interface for bandwidth control and rate limiting.
This interface provides methods to register bandwidth limiting callbacks with progress-enabled file operations. It integrates seamlessly with the progress package to enforce bytes-per-second transfer limits.
All methods are safe for concurrent use across multiple goroutines.
func New ¶
New creates a new BandWidth instance with the specified rate limit.
This function returns a bandwidth limiter that enforces the given bytes-per-second transfer rate. The limiter uses time-based throttling with atomic operations for thread-safe concurrent usage.
Parameters:
- bytesBySecond: Maximum transfer rate in bytes per second
- Use 0 for unlimited bandwidth (no throttling overhead)
- Common values: size.SizeKilo (1KB/s), size.SizeMega (1MB/s), etc.
Behavior:
- When limit is 0: No throttling applied, zero overhead
- When limit > 0: Enforces rate by introducing sleep delays
- Rate calculation: bytes / elapsed_seconds
- Sleep duration: capped at 1 second maximum per operation
The returned instance is safe for concurrent use across multiple goroutines. All methods can be called concurrently without external synchronization.
Thread safety:
- Safe for concurrent RegisterIncrement/RegisterReset calls
- Internal state protected by atomic operations
- No mutexes required for concurrent access
Performance:
- Zero-cost when unlimited (bytesBySecond = 0)
- Minimal overhead when limiting enabled (<1ms per operation)
- Lock-free implementation using atomic.Value
Example usage:
// Unlimited bandwidth bw := bandwidth.New(0) // 1 MB/s limit bw := bandwidth.New(size.SizeMega) // Custom 512 KB/s limit bw := bandwidth.New(512 * size.SizeKilo)