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

kitpages/semaphore-bundle

kitpages/semaphore-bundle is a Symfony bundle for semaphore-based locking, offering services to acquire and release locks so concurrent processes can coordinate access to shared resources. Useful for guarding critical sections, preventing race conditions, and limiting parallel execution in PHP appli...

View on GitHub
Deep Wiki
Context7

Getting Started

Install (Symfony context). This bundle predates Symfony Flex, so it is not auto-discovered. Add it to composer.json and register it manually:

composer require kitpages/semaphore-bundle
// app/AppKernel.php
$bundles = [
    // ...
    new Kitpages\SemaphoreBundle\KitpagesSemaphoreBundle(),
];

If you are a Laravel developer, the honest first step is to check whether you actually need this package at all — Laravel has first-class atomic locks (Cache::lock(), Illuminate\Contracts\Cache\Lock) with Redis/Database/File drivers. The bundle is only worth pulling in if you are working inside an older Symfony app where no lock primitive exists in the framework.

Where to look first. Browse DependencyInjection/Configuration.php and Resources/config/services.yml in the repo to see the actual driver options and service IDs — the README is thin, and service naming conventions changed across Symfony versions. Verify the manager service ID (something like kitpages_semaphore.manager or kitpages_semaphore.semaphore_manager) with bin/console debug:container | grep semaphore rather than trusting docs.

First use case. The canonical scenario is a scheduled/cron job or a long-running worker that must not overlap with itself:

$semaphore = $container->get('kitpages_semaphore.manager')->get('report_generator');

if (!$semaphore->acquire()) {
    // another process is already doing this; exit quietly
    return;
}

try {
    $this->generateNightlyReport();
} finally {
    $semaphore->release();
}

That is the whole mental model: named lock, acquire, try/finally, release. Everything else is driver and timeout configuration.

Implementation Patterns

Wrap in a try/finally, always. The single most important pattern. A semaphore leaked by a fatal error or an uncaught exception is a semaphore that is never released until the driver's TTL (if any) expires. Never call acquire() without a finally { release(); }.

Make it a named Symfony service decorator. Rather than scattering $container->get(...) across the app, wrap the critical operation:

class LockedReportGenerator
{
    public function __construct(private ReportGenerator $inner, private $semaphoreManager) {}

    public function generate(): void
    {
        $sem = $this->semaphoreManager->get('nightly_report');
        if (!$sem->acquire()) {
            return; // idempotent no-op, safe for cron
        }
        try { $this->inner->generate(); }
        finally { $sem->release(); }
    }
}

Tag or alias it so the rest of the code depends on the locked version.

Use distinct, stable names per resource. The lock key is the resource identity. Derive it deterministically, e.g. sprintf('invoice_pdf_%d', $invoiceId) for per-record locking, versus a fixed string for global singleton jobs. Never make the name depend on uniqid() or request data that won't reproduce.

Integration with queue workers. For Symfony's Enqueue/Messenger consumers, acquire around the message handler rather than the whole loop, so a stuck batch doesn't hold the lock forever. This keeps lock duration proportional to actual work.

Pair with a heartbeat for long jobs. If a job can run longer than the driver's TTL, either extend the TTL/reacquire inside the loop, or you will get a split-brain: the lock expires while process A is still running and process B acquires it. This is the failure mode that silently corrupts shared resources, so explicitly reason about max runtime vs TTL.

For Laravel developers porting the concept: map acquire() → Cache::lock($name, $ttl)->get() (non-blocking) and block($seconds) → ->block($seconds). The semantics are close enough that the mental model transfers cleanly, but Laravel's lock is TTL-bound by design, so it self-heals where a naive semaphore implementation may not.

Gotchas and Tips

Last release was 2017 — assume Symfony 2.x/3.x era. It will not "just work" on Symfony 4+/5+. Expect to fork it or vendor the class for PHP 7.4/8.x compatibility (return types, ContainerAware, deprecated get() injection). For a 10-star, zero-dependent bundle, a fork is often more realistic than waiting for upstream.

Check the driver before trusting it. The description says "synchronization between parallel PHP processes," but the mechanism matters enormously:

  • File/flock-based: locks are released when the process dies, which is good — but only reliable on the same filesystem, so it fails silently across multiple hosts or containers.
  • Database-based: survives process death only via TTL/cleanup jobs; prone to orphaned rows after a hard kill. Also a row-lock contention point.
  • SysV semaphores (sem_get/sem_acquire): not shareable across machines and can leak if not removed; cleanup on deploy/restart is a real operational hazard.

Confirm which one you're getting and whether your deployment is single-host or multi-host. This is the #1 reason these locks appear to "not work."

Release in finally is not enough for SIGKILL. If the worker is OOM-killed or the container is SIGKILLed, no finally runs. Only driver-level TTLs or process-death-released flock save you. Design around it: keep TTLs short, and make the protected work idempotent so a duplicate run is harmless.

Beware of deadlock with nested locks. If two code paths acquire semaphores in different orders, you can deadlock. Establish a global ordering for lock names, or avoid holding more than one lock at a time. Since acquire() is likely non-blocking, deadlock manifests as "job silently skipped forever" rather than a hang — harder to debug.

Debugging: log acquire/release with identity. Add a wrapper that logs the lock name, PID, and a stack hint on acquire and release. When a resource is mysteriously locked, you need to know which process holds it. Also expose a debug:semaphore console command listing currently held locks and their holders — invaluable operationally, and cheap to build.

Extension points. The bundle's real value is its driver abstraction. If you must extend it, add a driver (e.g. Redis or MySQL GET_LOCK) rather than patching the manager — a Redis-backed driver gives you multi-host safety and TTLs that the file driver lacks, and is usually the one line that makes the package production-viable.

Config quirks. Older Symfony bundles nest options one level deeper than Symfony 3 config norms, and driver keys may be positional. Validate your config.yml with bin/console config:dump-reference to see the exact shape; if that command doesn't exist in your Symfony version, read Configuration.php directly. Also confirm whether the bundle defaults to a "blocking" or "non-blocking" acquire — the sample code in the README often assumes one while the driver does the other.

Recommendation. For a Laravel developer starting fresh: do not adopt this bundle. Use the framework's atomic locks with Redis. Only reach for kitpages/semaphore-bundle if you inherit a Symfony 3 codebase that already depends on it, and in that case budget time to fork, add TTL-based release, and build the debug command — because the package as shipped (10 stars, no dependents, frozen since 2017) gives you a working primitive but not the operational tooling you will need in production.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings