Azure Service Bus Peek-Lock Explained: Complete, Abandon & Dead-Letter

I once watched a worker “succeed” on a payment event while the database write never happened. The message was gone. The money path was half-done. That is the failure mode Azure Service Bus peek lock is designed to prevent: you receive work under a temporary lock, process it, then settle explicitly—complete, abandon, dead-letter, or defer—instead of deleting on wire transfer.

This guide is for engineers wiring receivers in .NET (and for engineering managers who need a plain scoreboard: at-least-once vs at-most-once, lock duration, and what “success” means in Azure Functions). Official settlement behaviour is documented by Microsoft under message transfers, locks, and settlement.

Azure Service Bus peek lock in one screen

Peek-Lock (often written PeekLock) is a two-stage receive:

  1. The broker hands you the next message and holds an exclusive lock so competing consumers on the same queue/subscription do not see it.
  2. Your code settles: Complete (remove), Abandon (release for retry), Dead-letter (park for investigation), Defer (hold aside until you fetch by sequence number), or the lock expires and the message becomes visible again.

The other mode is Receive-and-Delete: the broker treats the message as settled when it is put on the wire. Faster, fewer round-trips—and if your process dies mid-handler, that message is gone. Use it only when losing a message is acceptable.

Mode Delivery intent When I pick it
Peek-Lock At-least-once (you settle after side effects) Orders, payments, provisioning, anything with external writes
Receive-and-Delete At-most-once (settled on send to client) Cheap telemetry, best-effort fan-out, loss is OK

Important naming trap: message browsing (“peek”) is not the same as Peek-Lock receive. Browse/peek enumerates messages for diagnostics without locking them for processing. Peek-Lock is the production receive mode for work you must finish safely.

Settlement choices (the real API surface)

Action What the broker does Use when
Complete Removes the message Side effects succeeded (or you made them idempotent and recorded success)
Abandon Releases the lock; message can be received again Transient failure; retry soon is fine
Dead-letter Moves to dead-letter sub-queue with reason/description Poison message, bad schema, permanent business reject
Defer Holds message; retrieve later by sequence number Out-of-order workflows that need a deliberate pause
Lock expires Message becomes visible to other receivers Handler too slow or process crashed mid-flight

Default lock duration is often around 30 seconds (entity setting). Long work needs either shorter handlers (preferred) or lock renewal via the client (RenewMessageLockAsync / processor auto-renew). If the lock is already gone and you still try to complete, you get a lock-lost failure—design for idempotent handlers so a second delivery does not double-charge or double-email.

Why Peek-Lock matters in real systems

  1. Reliability — Fail processing → abandon (or let the lock expire) → another attempt without silent loss.
  2. Explicit control — The message leaves the queue only when you complete after durable work.
  3. Error parking — Repeated poison → dead-letter with a reason humans can triage.

Typical fits: financial events, order lines, access provisioning, critical notifications. If the business cannot tolerate a vanished message, do not use Receive-and-Delete.

Azure Functions: Peek-Lock is the default

Service Bus triggers in Azure Functions use Peek-Lock behaviour by default. The runtime completes on success and abandons (for retry) when the function fails with an unhandled exception. See Microsoft’s Service Bus trigger notes on PeekLock behaviour.

Do not swallow exceptions. A bare try/catch that logs and returns “success” tells Functions the message is done. Downstream failed; queue moved on. If you catch, rethrow—or dead-letter intentionally with a clear reason.

Example in C# (Azure.Messaging.ServiceBus)

using Azure.Messaging.ServiceBus;

var client = new ServiceBusClient(connectionString);
var receiver = client.CreateReceiver(
    queueName,
    new ServiceBusReceiverOptions
    {
        ReceiveMode = ServiceBusReceiveMode.PeekLock
    });

try
{
    ServiceBusReceivedMessage message =
        await receiver.ReceiveMessageAsync(TimeSpan.FromSeconds(10));

    if (message is null)
        return;

    // Do durable work first (DB, outbox, idempotency key)…
    await ProcessAsync(message);

    await receiver.CompleteMessageAsync(message);
}
catch (ServiceBusException ex) when (ex.Reason == ServiceBusFailureReason.MessageLockLost)
{
    // Another receiver may already own it — make ProcessAsync idempotent.
    throw;
}
catch
{
    // Optional: abandon explicitly, or let lock expire / Functions runtime handle.
    throw;
}
finally
{
    await receiver.DisposeAsync();
    await client.DisposeAsync();
}

What matters in that sketch: receive under Peek-Lock, finish side effects, then complete. Prefer the processor/host patterns in production (auto lock renewal, concurrency limits) over one-off receive loops.

Diagram 1 — Peek-Lock settlement loop
Queue / subscription
        │
        ▼
 Receive (Peek-Lock) ──► exclusive lock held
        │
        ▼
 Process side effects (idempotent)
        │
   ┌────┼────┬──────────┐
   ▼    ▼    ▼          ▼
Complete Abandon Dead-letter  (lock expires)
 remove  retry   DLQ         visible again

Decision guide: which receive mode?

Question If yes…
Is a lost message a business incident? Peek-Lock + idempotent handlers
Is work longer than lock duration? Shorten work, raise lock duration carefully, or renew locks
Can the same message run twice safely? Required for at-least-once; store processed message IDs / outbox
Is this Azure Functions? Assume Peek-Lock; never swallow exceptions on real failures

FAQ: Azure Service Bus peek lock

What is Azure Service Bus peek lock?

Peek-Lock is a receive mode where Service Bus delivers a message and locks it for your receiver until you settle it (complete, abandon, dead-letter, defer) or the lock expires. It supports at-least-once processing when handlers are idempotent.

Peek-Lock vs Receive-and-Delete — which should I use?

Use Peek-Lock when losing work is unacceptable. Use Receive-and-Delete only for disposable traffic where at-most-once loss is fine.

What happens when the message lock expires?

The message becomes available to other receivers. Your previous settle calls may fail with lock lost. Design for duplicate delivery.

Is “peek” the same as Peek-Lock?

No. Browsing/peeking inspects messages without the processing lock lifecycle. Peek-Lock is the locked receive path for real consumers.

Does Azure Functions use Peek-Lock?

Yes—Service Bus triggers default to Peek-Lock-style completion on success and retry behaviour on failure. Do not catch-and-ignore errors if the message must retry.

How do I fix low lock duration or long handlers?

Prefer smaller units of work. Otherwise renew the lock (or use the processor’s auto-renew) and keep an eye on lock-lost metrics. Dead-letter true poison instead of infinite retry.

Closing

Azure Service Bus peek lock is not a niche toggle—it is the difference between “we think we processed it” and “we settled after durable work.” Pair it with idempotent handlers, honest failure paths (abandon vs dead-letter), and Functions exception discipline. That combination is what keeps order and payment pipelines boring in production—which is exactly what you want.

Similar Posts