Skip to content

Latest commit

 

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GlobalSemaphore

CI

Cluster-wide semaphore with a dead-simple API.

# config :global_semaphore, :limits, pdf_api: 3

GlobalSemaphore.with_permit(:pdf_api, fn ->
  generate_pdf()  # max 3 concurrent calls across the entire cluster
end)

# With priority (lower number = higher priority, default 5)
GlobalSemaphore.with_permit({:pdf_api, 1}, fn ->
  generate_pdf()  # high priority, preempts normal/low requests in the queue
end)

Why?

  • Cluster-wide: Works across all nodes, not just one
  • Crash-safe: Automatically reclaims permits and removes queued calls when their processes exit
  • Simple: One config line, one function call. No complex concepts.
  • No dependencies: Uses Erlang's built-in :global module

Installation

def deps do
  [{:global_semaphore, "~> 0.4.0"}]
end

Usage

# config/runtime.exs
config :global_semaphore, :limits,
  pdf_api: 3,
  email_api: 5
# Recommended: automatic acquire/release
GlobalSemaphore.with_permit(:pdf_api, fn ->
  call_pdf_api()
end)

# With priority (lower number = higher priority, default 5)
GlobalSemaphore.with_permit({:pdf_api, 1}, fn -> call_pdf_api() end)   # high
GlobalSemaphore.with_permit({:pdf_api, 5}, fn -> call_pdf_api() end)   # normal (default)
GlobalSemaphore.with_permit({:pdf_api, 10}, fn -> call_pdf_api() end)  # low

Introspection

Inspect running semaphores across the cluster, e.g. for an admin dashboard:

# Names of all semaphores currently running in the cluster
GlobalSemaphore.list_names()
#=> [:pdf_api]

# Status of one semaphore (nil if not running).
# Holders are sorted by acquired_at, waiters are in grant order.
GlobalSemaphore.status(:pdf_api)
#=> %{
#     name: :pdf_api,
#     max: 3,
#     holders: [%{pid: #PID<0.123.0>, node: :"app@host1", acquired_at: ~U[...]}],
#     waiters: [%{pid: #PID<0.456.0>, node: :"app@host2", priority: 5, enqueued_at: ~U[...]}]
#   }

# Status of all running semaphores
GlobalSemaphore.status_all()

Limitations

  • Uses :global which has known issues during network partitions (rare in practice)
  • If the node hosting the semaphore dies, queued requests fail (next request auto-recovers)

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages