kopia/kopia · warning

unable to acquire semaphore

Error message

unable to acquire semaphore

What it means

Thrown in the gRPC session server when s.sem.Acquire(ctx, 1) fails while enforcing the limit on concurrently handled requests. Acquire only fails when the provided context is cancelled or its deadline expires while waiting for a slot, meaning the request was abandoned before a worker became available.

Solutions

  1. Retry the request; this often means the client gave up while waiting.
  2. Increase the server's concurrency limit if handlers are habitually saturated.
  3. Investigate slow/stuck handlers blocking semaphore slots.
  4. Align client timeout settings with realistic server queue wait times.

Example fix

// before: default low concurrency
kopia server start --max-concurrency=4
// after
kopia server start --max-concurrency=32
Defensive patterns

Strategy: retry

Validate before calling

// client-side: use a deadline long enough to cover queue wait
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()

Try / catch

err := client.Connect(ctx)
if err != nil && strings.Contains(err.Error(), "unable to acquire semaphore") {
    // server saturated or client timed out waiting; back off and retry
    return retryWithBackoff(ctx, Connect)
}

Prevention

When it happens

Trigger: Client request arrives while the concurrency semaphore is full and the client's context is cancelled or times out before a slot frees up.

Common situations: Many concurrent kopia client operations saturating the server; client-side timeout shorter than the queue wait; server wedged on slow handlers so the queue never drains.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of kopia/kopia@82495e54b5 (2026-09-07). Data as JSON: /api/errors/ec2e6468471dba04. Report an issue: GitHub.

Appendix: source

Thrown at internal/server/grpc_session.go:145

		for req, err := srv.Recv(); err == nil; req, err = srv.Recv() {
			// propagate any error from the goroutines
			select {
			case err := <-lastErr:
				userLog(ctx).Errorf("error handling session request: %v", err)

				contentlog.Log1(ctx, log,
					"error handling session request",
					logparam.Error("error", err))

				return err

			default:
			}

			// enforce limit on concurrent handling
			if err := s.sem.Acquire(ctx, 1); err != nil {
				return errors.Wrap(err, "unable to acquire semaphore")
			}

			go func() {
				defer s.sem.Release(1)

				s.handleSessionRequest(ctx, dw, authz, usernameAtHostname, req, func(resp *grpcapi.SessionResponse) {
					if err := s.send(srv, req.GetRequestId(), resp); err != nil {
						select {
						case lastErr <- err:
						default:
						}
					}
				})
			}()
		}

		return nil
	})

View on GitHub (pinned to 82495e54b5)