Skip to content
All writing Part 09 of 11 · From 19 Goroutines to Exit 137
Engineering · 2 min read

Load Shedding with TryAcquire: Fast Rejection Beats Slow Queuing

Acquire queuing just relocates the OOM. TryAcquire + HTTP 429 is real load shedding: fast rejection beats slow queuing when holding the resource is the problem.

Acquire queuing just relocates the OOM to the waiting area. TryAcquire + 429 is real shedding.

Acquire vs TryAcquire

request arrives slots.TryAcquire(1) true false do the work release(1) return 429 (load-shed) caller retries new conn + jitter (L4 re-pick)
  • Acquire (blocking): over-limit callers wait in a FIFO queue. Each waiter holds a goroutine + a connection + memory. An unbounded queue just relocates the OOM.
  • TryAcquire (non-blocking): immediate falseHTTP 429. This is load shedding, fast rejection that lets the caller retry elsewhere (keep-alive pins retries).
  • Choose shedding when holding the resource is itself the problem (for example, browser tabs). Choose a bounded queue when short bursts should be absorbed.

semaphore.Weighted Internals

Think of a parking lot with 6 spots (size: 6), 4 currently taken (cur: 4):

TryAcquire (non-blocking): check for an open spot. If one exists, take it. If not, leave immediately. Never waits in line.

Acquire (blocking): no open spot? Wait in line until someone leaves. Can give up early via context cancellation.

Release: leave, free up a spot. If anyone is waiting, let the next one in (FIFO order).

TryAcquire(1):             Acquire(ctx, 1):
  lock                       lock
  remaining >= 1               remaining < 1?
    && no one queued?            → join waiters, block on channel
    → cur++, return true         (blocks until someone Releases)
    (never blocks)

Release(1):
  cur-- → wake next waiter in FIFO order
  • Here n=1 (one spot at a time), size=6 → max 6 concurrent captures.
  • When TryAcquire returns false, the handler returns 429 immediately — no queuing, no goroutine held, no memory spent.
  • The waiters.Len()==0 guard enforces fairness: newcomers cannot skip ahead of callers already waiting in line.

Role in This OOM

The screenshot service’s /capture uses slots.TryAcquire(1) to cap concurrent captures at 6. The 7th+ request gets an immediate 429. No new browser tab opens, and the browser sidecar’s memory stays bounded (shared resource blast radius).

A fast 429 is cheaper than a slow 500. Shed requests still have a chance; requests queued into an OOM are all lost.

References:

Related: see shared resource blast radius and keep-alive pins retries, or go back to the series overview.

Tags #go #concurrency #reliability
// connect

Be brave | Be wise | Be grateful

21 BreakinCode

// elsewhere
LinkedInMedium (lang: en)Life RecordYoutube
wh:~$William Hung· © 2026 Taipei · GMT+8 · Available for collaboration