Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions tests/NATS.Net.DocsExamples/Advanced/SlowConsumerPage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// ReSharper disable SuggestVarOrType_SimpleTypes
// ReSharper disable SuggestVarOrType_Elsewhere
#pragma warning disable SA1123
#pragma warning disable SA1124
#pragma warning disable SA1509
#pragma warning disable IDE0007
#pragma warning disable IDE0008

using System.Threading.Channels;
using NATS.Client.Core;

namespace NATS.Net.DocsExamples.Advanced;

public class SlowConsumerPage
{
public async Task Run()
{
Console.WriteLine("____________________________________________________________");
Console.WriteLine("NATS.Net.DocsExamples.Advanced.SlowConsumerPage");

{
#region message-dropped
await using NatsConnection nc = new NatsConnection();

nc.MessageDropped += (sender, args) =>
{
Console.WriteLine($"Dropped message on '{args.Subject}' with {args.Pending} pending messages");
return default;
};
#endregion
}

{
#region slow-consumer-detected
await using NatsConnection nc = new NatsConnection();

nc.SlowConsumerDetected += (sender, args) =>
{
Console.WriteLine($"Slow consumer detected on '{args.Subscription.Subject}'");
return default;
};
#endregion
}

{
#region tuning
// Set global default capacity
NatsOpts opts = new NatsOpts { SubPendingChannelCapacity = 4096 };
await using NatsConnection nc = new NatsConnection(opts);

// Override capacity for a specific subscription
NatsSubOpts subOpts = new NatsSubOpts
{
ChannelOpts = new NatsSubChannelOpts { Capacity = 8192 },
};

await foreach (NatsMsg<string> msg in nc.SubscribeAsync<string>("events.>", opts: subOpts))
{
Console.WriteLine($"Received: {msg.Data}");
}
#endregion
}

{
#region suppress-warnings
NatsOpts opts = new NatsOpts
{
SuppressSlowConsumerWarnings = true,
};

await using NatsConnection nc = new NatsConnection(opts);

// Events still fire even when log warnings are suppressed
nc.SlowConsumerDetected += (sender, args) =>
{
Console.WriteLine($"Slow consumer on '{args.Subscription.Subject}'");
return default;
};
#endregion
}
}
}
1 change: 1 addition & 0 deletions tools/site_src/documentation/advanced/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ essentially sent back to back after they're picked up from internal queues and b

## What's Next

- [Slow Consumers](slow-consumers.md) explains how to detect and handle subscribers that can't keep up with the message rate, including the `MessageDropped` and `SlowConsumerDetected` events.
- [Serialization](serialization.md) is the process of converting an object into a format that can be stored or transmitted.
- [Security](security.md) is an important aspect of any distributed system. NATS provides a number of security features to help you secure your applications.
- [AOT Deployment](aot.md) is a way to deploy your applications as native platform executables, which produces faster startup times and better performance in most cases.
Expand Down
42 changes: 42 additions & 0 deletions tools/site_src/documentation/advanced/slow-consumers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Slow Consumers

When a subscriber can't keep up with the rate of incoming messages, its internal bounded channel fills up.
By default, new messages are dropped when this happens. This is aligned with NATS at-most-once delivery.

## Detecting Dropped Messages

The `MessageDropped` event fires every time a message is dropped from a subscription's internal channel:

[!code-csharp[](../../../../tests/NATS.Net.DocsExamples/Advanced/SlowConsumerPage.cs#message-dropped)]

## Detecting Slow Consumers

The `SlowConsumerDetected` event (added in v2.7.2) fires once per slow consumer episode.
It resets after the subscription catches up (channel drains), so it will fire again if the subscription falls behind a second time:

[!code-csharp[](../../../../tests/NATS.Net.DocsExamples/Advanced/SlowConsumerPage.cs#slow-consumer-detected)]

## Tuning Channel Capacity

Each subscription has an internal bounded channel with a default capacity of 1024 messages.
You can tune this globally or per subscription:

[!code-csharp[](../../../../tests/NATS.Net.DocsExamples/Advanced/SlowConsumerPage.cs#tuning)]

## Channel Full Mode

The `SubPendingChannelFullMode` option controls what happens when the channel is full:

- `BoundedChannelFullMode.DropNewest` (default for `NatsConnection`) - newest messages are dropped
- `BoundedChannelFullMode.Wait` (default for `NatsClient`) - backpressure is applied, but the server may disconnect you as a slow consumer
Comment on lines +28 to +31

Copilot AI Feb 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation states that BoundedChannelFullMode.DropNewest is the "default for NatsConnection" and BoundedChannelFullMode.Wait is the "default for NatsClient". While technically accurate, this could be misleading because NatsClient is a higher-level wrapper that modifies the NatsOpts.SubPendingChannelFullMode from its base default.

Consider clarifying that BoundedChannelFullMode.DropNewest is the default for the underlying NatsOpts, which NatsConnection uses directly, while NatsClient explicitly overrides this to use BoundedChannelFullMode.Wait. This would make it clearer that the difference is due to NatsClient being an opinionated wrapper rather than two separate default configurations.

Suggested change
The `SubPendingChannelFullMode` option controls what happens when the channel is full:
- `BoundedChannelFullMode.DropNewest` (default for `NatsConnection`) - newest messages are dropped
- `BoundedChannelFullMode.Wait` (default for `NatsClient`) - backpressure is applied, but the server may disconnect you as a slow consumer
The `SubPendingChannelFullMode` option controls what happens when the channel is full.
The base default is defined on `NatsOpts` (used directly by `NatsConnection`), while `NatsClient` is a higher-level, opinionated wrapper that overrides this default:
- `BoundedChannelFullMode.DropNewest` (default on `NatsOpts` / `NatsConnection`) - newest messages are dropped
- `BoundedChannelFullMode.Wait` (default when using `NatsClient`, which overrides `SubPendingChannelFullMode`) - backpressure is applied, but the server may disconnect you as a slow consumer

Copilot uses AI. Check for mistakes.

> [!WARNING]
> Using `BoundedChannelFullMode.Wait` prevents message loss at the client level, but if the subscriber
> can't keep up, the NATS server itself may disconnect the client as a slow consumer.

## Suppressing Warnings

By default, a warning is logged once per slow consumer episode. You can suppress these logs while still
receiving the events:

[!code-csharp[](../../../../tests/NATS.Net.DocsExamples/Advanced/SlowConsumerPage.cs#suppress-warnings)]
2 changes: 2 additions & 0 deletions tools/site_src/documentation/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
- name: Advanced Options
href: advanced/intro.md
items:
- name: Slow Consumers
href: advanced/slow-consumers.md
- name: Serialization
href: advanced/serialization.md
- name: Security
Expand Down
Loading