Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Semaphore Bundle Laravel Package

zerkalica/semaphore-bundle

Symfony bundle that brings semaphore-based locking to your app: guard critical sections and shared resources with a simple, configurable service so concurrent requests and workers don't collide. Lightweight integration for safe, predictable concurrency.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: zerkalica/semaphore-bundle is a Symfony bundle, not a Laravel package. It has 2 stars, 0 dependents, and no recent activity — treat it as abandoned reference code, not a dependency you install into a Laravel app. There is no extra.laravel block, so Package Discovery won't register anything even if Composer pulls it in.

What it actually does: it wires the underlying zerkalica/semaphore library into Symfony's DI container as configurable services, so you can inject named semaphores. The library itself is a thin wrapper over PHP's SysV semaphore functions (sem_get / sem_acquire / sem_release, ext-sysvsem) — i.e. process-level mutual exclusion on a single machine.

Minimal path if you want to evaluate it:

  1. composer require zerkalica/semaphore-bundle in a scratch Symfony app (not in Laravel) just to read the code, or clone the repo.
  2. Look at the bundle's DependencyInjection/ extension and Resources/config/ first — that's where the named-semaphore configuration schema lives. Then read the library's semaphore/factory classes.
  3. Do not try to boot the bundle in Laravel. Instead, install the underlying library and bind it yourself in a service provider:
// app/Providers/AppServiceProvider.php
$this->app->singleton('semaphore.exports', fn () => new \Zerkalica\Semaphore\Semaphore('nightly-export'));
  1. First use case: guard a single non-reentrant CLI/cron task (a nightly export, a cache warmer) so overlapping invocations on the same host can't run at once.
if (! $sem->acquire()) {           // check the actual signature in the source
    $this->warn('already running, exiting');
    return self::SUCCESS;
}

try {
    $this->doTheWork();
} finally {
    $sem->release();
}

Before you go further, compare against what Laravel gives you for free — for most tasks the native options are strictly better and maintained:

Cache::lock('nightly-export', 600)->get();          // Redis/database/array driver
// or on a queued job:
public function middleware(): array { return [(new WithoutOverlapping('exports'))->expireAfter(600)]; }

Implementation Patterns

Service-provider binding (the only sane integration). Skip the bundle, wrap the library once and inject it. Keep the semaphore key in config so environments can't collide:

// config/services.php
'semaphores' => ['exports' => 'app-'.env('APP_ENV').'-exports'],
$this->app->singleton(Semaphore::class, fn ($app) =>
    new Semaphore(config('services.semaphores.exports'))
);

Try/finally is mandatory. Every acquire must be paired with a release in finally. Wrap the whole pattern in a small helper so nobody on the team hand-rolls it:

final class Exclusive
{
    public function __construct(private Semaphore $sem) {}

    public function run(callable $fn, mixed $fallback = null): mixed
    {
        if (! $this->sem->acquire()) {
            Log::info('semaphore busy, skipping', ['name' => $this->sem->getName()]);
            return $fallback;
        }
        try { return $fn(); }
        finally { $this->sem->release(); }
    }
}

Command-level guard. In handle(), acquire at the top; if it fails, return self::SUCCESS rather than an error — scheduled overlaps are normal, not failures. Pair with withoutOverlapping() in routes/console.php for the scheduler, which is the Laravel-native equivalent.

Job middleware. If you like the bundle's ergonomics, model a job middleware on it — but ship WithoutOverlapping in production and keep the semaphore version for jobs that must not share a process-level resource (local disk spool, a fixed temp path, a hardware device).

Named-semaphore registry. If you port the bundle's config approach, keep a single map of name → purpose so audit is trivial, and give each critical section its own name. Sharing one semaphore across unrelated tasks serializes them for no reason.

Where the pattern genuinely earns its keep vs. Redis locks: SysV semaphores don't need a network round-trip and don't expire. For very hot, very short critical sections on one box (a local file index rebuild), that's a real advantage. For anything else, use Cache::lock().

Gotchas and Tips

  • Wrong framework. A Symfony bundle in a Laravel app is dead weight. At best you get a service definition you can't consume; at worst you get unexpected Symfony DI classes in your autoloader. Vendor the pattern, not the package.
  • ext-sysvsem is required. Check function_exists('sem_get') at boot, not at 2 a.m. when the cron fails. Many containers, Alpine images, and shared hosts ship PHP without it.
  • Per-host, per-kernel only. Semaphores are not shared across machines, containers, or PHP-FPM pools with separate IPC namespaces. If you deploy multiple replicas, this library provides no protection at all — use Redis or a database lock. This is the single most common way people mis-apply it.
  • Not reentrant. Acquiring the same semaphore twice in one request will hang or fail depending on the timeout. Guard against nested calls in your helper (a request-scoped depth counter), or you'll debug a mysterious stall.
  • Release on fatal error. SysV semaphores opened with auto-release (sem_get's fourth argument) are cleaned up when the process dies, so SIGKILL/OOM is usually survivable — but a thrown exception with no finally is not. Don't rely on auto-release as your error handling.
  • Leaked semaphores are invisible. On Linux, inspect with ipcs -s and remove stale ones with ipcrm -s <id>. In Docker, leaked semaphores die with the container, which masks bugs locally and makes them reappear in long-lived VMs.
  • Timeouts are the design decision. Blocking acquire vs. non-blocking "skip if busy" changes behavior at scale. Default to non-blocking + log + skip for cron and queue jobs; only block for interactive, user-triggered paths and always with a bounded timeout.
  • Abandonment risk. 2 stars, 0 dependents means no security patches and no PHP 8.x compatibility guarantees. If you adopt it, fork it into your org, pin the commit, and own the code. The library is small enough to reimplement over sem_* in an afternoon.
  • Extension point worth knowing: the bundle's value is entirely in one container service definition. Read Resources/config/*.xml|yaml to see the exact constructor arguments, then copy that wiring into your own provider — that's the whole migration.
  • Debugging tip: log the semaphore name, the PID, and acquire/release timestamps around every critical section. When a job "randomly doesn't run," that log tells you instantly whether it lost the race or leaked a lock.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels