Skip to content

decoi-io/waitinline

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🎟️ waitinline

⏳ Fair, FIFO-style locking for Go. Everyone waits their turn — no queue jumping allowed! 🎯


📦 Installation

go get github.com/decoi-io/waitinline

Then import it:

import "github.com/decoi-io/waitinline"

⚙️ What It Does

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


✨ Quick Start

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()

👷 Example: Coordinated Workers

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]

📘 API

func New(size int) *Line

Create a new fair queue (Line) with given size (max number of waiting goroutines).


func (l *Line) Lock(ctx context.Context) (LockHandler, error)

Waits in line to acquire the lock. Returns a LockHandler which should be unlocked when done.

  • Respects ctx cancellation or timeout ⏱️
  • If the Line is closed, returns ErrQueueClosed

func (l *Line) Close()

Closes the lock system. All waiting goroutines are cancelled gracefully.


type LockHandler interface

type LockHandler interface {
    Unlock() error
}

Returned by .Lock(). Call .Unlock() to allow the next person in line to proceed.


🚨 Errors

  • ErrQueueClosed – Line was closed before lock could be acquired
  • ErrContextCancelled – Context timed out or was cancelled
  • ErrFailedUnlock – Unlock failed (shouldn't happen under normal usage)

💡 Tips

  • 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.

🌍 Use Cases

  • Job queues 🧺
  • Rate-limiting gateways 🚥
  • Multiplayer turn management 🎮
  • Fair scheduling in microservices 🔄
  • Enforcing request order 🛒

🔍 Why Not Just Use sync.Mutex?

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:

Fairness (FIFO)

  • 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).

Context-Aware Locking

  • You can Lock(ctx) with cancellation or timeouts.
  • No more blocked goroutines forever — you stay responsive to timeouts, cancellations, or shutdowns.

Explicit Shutdown

  • You can .Close() the line and gracefully cancel all waiters. No stuck goroutines or deadlocks.

Lock-as-a-Service

  • You can wrap this as a shared lock mechanism between components, without requiring them to share memory directly.
  • Makes your architecture more modular 🔧.

When to prefer waitinline over sync.Mutex:

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

🧪 Running Tests

go test ./...

🤝 Contributing

We welcome issues, ideas, and pull requests! Here's how to join the fun:

  1. ⭐ Star the repo to show love!
  2. 🐛 Found a bug? Open an issue
  3. 🌱 Want to improve something? Fork, commit, and submit a pull request!
  4. 🧪 Run go test ./... before sending PRs

Development Tips

  • Keep the package light — no third-party dependencies.
  • Maintain full test coverage for all changes.
  • Use go fmt, go vet, and golangci-lint if you like staying sharp 💅

🪪 License

MIT © decoi-io


💬 Final Thought

If Go had a theme park, waitinline would be the velvet rope queue keeping everyone in order 🎢


Made with ❤️ and a dash of concurrency.

About

Fair, FIFO-style locking for Go.

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages