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

 
 
 

Contributors

Languages