⏳ Fair, FIFO-style locking for Go. Everyone waits their turn — no queue jumping allowed! 🎯
go get github.com/decoi-io/waitinlineThen import it:
import "github.com/decoi-io/waitinline"waitinline is a fair locking mechanism for goroutines — ensuring that whoever comes first, gets served first. Just like lining up at your favorite coffee shop ☕️.
✅ FIFO fairness ⛔ Context-aware timeout + cancellation 🔄 Clean shutdowns 🧵 Safe for concurrent use 🪶 Lightweight and dependency-free
line := waitinline.New(10) // queue size 10
defer line.Close()
ctx := context.Background()
pass, err := line.Lock(ctx)
if err != nil {
log.Fatal("Failed to lock:", err)
}
// 🚧 Critical section begins
pass.Unlock()package main
import (
"context"
"fmt"
"sync"
"time"
"github.com/decoi-io/waitinline"
)
func main() {
lock := waitinline.New(10)
defer lock.Close()
wg := sync.WaitGroup{}
order := []int{}
for i := range 10 {
wg.Add(1)
go func(index int) {
defer wg.Done()
ticket, err := lock.Lock(context.Background())
if err != nil {
fmt.Printf("lock failed: %v", err)
return
}
defer ticket.Unlock()
fmt.Printf("Appending index: %d\n", index)
order = append(order, index)
}(i)
time.Sleep(time.Millisecond * 100)
}
fmt.Printf("Order: %v\n", order)
}📈 Output (expected FIFO):
Appending index: 0
Appending index: 1
Appending index: 2
Appending index: 3
Appending index: 4
Appending index: 5
Appending index: 6
Appending index: 7
Appending index: 8
Appending index: 9
Order: [0 1 2 3 4 5 6 7 8 9]
Create a new fair queue (Line) with given size (max number of waiting goroutines).
Waits in line to acquire the lock.
Returns a LockHandler which should be unlocked when done.
- Respects
ctxcancellation or timeout ⏱️ - If the
Lineis closed, returnsErrQueueClosed
Closes the lock system. All waiting goroutines are cancelled gracefully.
type LockHandler interface {
Unlock() error
}Returned by .Lock(). Call .Unlock() to allow the next person in line to proceed.
ErrQueueClosed– Line was closed before lock could be acquiredErrContextCancelled– Context timed out or was cancelledErrFailedUnlock– Unlock failed (shouldn't happen under normal usage)
- Always
defer pass.Unlock()once you get the lock. - Call
.Close()when your app/server shuts down to clean up waiters. - Queue size defines how many can wait — beyond that,
.Lock()will block until room.
- Job queues 🧺
- Rate-limiting gateways 🚥
- Multiplayer turn management 🎮
- Fair scheduling in microservices 🔄
- Enforcing request order 🛒
Great question! While sync.Mutex is fast and simple, it's not fair — it doesn’t guarantee the order in which goroutines acquire the lock. Whoever gets scheduled next, wins. 🏃♂️💨
But with waitinline, you get:
- First goroutine to request the lock is guaranteed to get it first.
- This is crucial for predictable behavior in systems where ordering matters (like job queues, rate-limiting, or gameplay turns).
- You can
Lock(ctx)with cancellation or timeouts. - No more blocked goroutines forever — you stay responsive to timeouts, cancellations, or shutdowns.
- You can
.Close()the line and gracefully cancel all waiters. No stuck goroutines or deadlocks.
- You can wrap this as a shared lock mechanism between components, without requiring them to share memory directly.
- Makes your architecture more modular 🔧.
| Situation | Use sync.Mutex |
Use waitinline |
|---|---|---|
| 🧍 Fairness between goroutines matters | ❌ | ✅ |
| ⏱ You need timeouts / cancellation support | ❌ | ✅ |
| 🚦 Need to cancel all waiters cleanly | ❌ | ✅ |
| 🧩 Components may not be tightly coupled | ❌ | ✅ |
| 🔁 High-speed critical sections | ✅ | ❌ |
go test ./...We welcome issues, ideas, and pull requests! Here's how to join the fun:
- ⭐ Star the repo to show love!
- 🐛 Found a bug? Open an issue
- 🌱 Want to improve something? Fork, commit, and submit a pull request!
- 🧪 Run
go test ./...before sending PRs
- Keep the package light — no third-party dependencies.
- Maintain full test coverage for all changes.
- Use
go fmt,go vet, andgolangci-lintif you like staying sharp 💅
MIT © decoi-io
If Go had a theme park, waitinline would be the velvet rope queue keeping everyone in order 🎢
Made with ❤️ and a dash of concurrency.