# Subscriptions Overview The subscription system maps published subjects to interested subscribers. It consists of four classes: `Subscription` (the subscription model), `SubListResult` (a match result container), `SubjectMatch` (subject validation and wildcard matching), and `SubList` (the trie that routes messages to subscribers). Go reference: `golang/nats-server/server/sublist.go` --- ## Subjects A NATS subject is a dot-separated string of tokens: `foo.bar.baz`. Each token is a non-empty sequence of non-whitespace characters. The token separator is `.`. ### Wildcards Two wildcard tokens are supported: | Token | Name | Matches | |-------|------|---------| | `*` | Partial wildcard (Pwc) | Exactly one token at that position | | `>` | Full wildcard (Fwc) | One or more remaining tokens | Rules: - `foo.*` matches `foo.bar` but not `foo.bar.baz` (only one token after `foo`). - `foo.>` matches `foo.bar` and `foo.bar.baz` (one or more tokens after `foo`). - `>` must be the last token in a subject. `foo.>.bar` is invalid. - Publishers cannot use wildcards — only subscribers can. ### Queue Groups Subscribers with the same queue name form a queue group. When a message matches a queue group, exactly one member of that group receives the message. The server selects a member per published message, which distributes load across the group. Queue subscriptions are independent of plain subscriptions. If a subject has both plain subscribers and queue group subscribers, all plain subscribers receive the message and one member from each queue group receives it. --- ## The `Subscription` Class A `Subscription` represents one subscriber's interest in a subject. ```csharp public sealed class Subscription { public required string Subject { get; init; } public string? Queue { get; init; } public required string Sid { get; init; } public long MessageCount; // Interlocked public long MaxMessages; // 0 = unlimited public NatsClient? Client { get; set; } } ``` - `Subject` — the subject pattern, which may contain wildcards. - `Queue` — the queue group name. `null` means this is a plain subscription. - `Sid` — the subscription ID string assigned by the client. Used to identify the subscription in `UNSUB` commands. - `MessageCount` — updated with `Interlocked.Increment` on each delivered message. Compared against `MaxMessages` to implement `UNSUB maxMessages`. - `MaxMessages` — the auto-unsubscribe limit. `0` means unlimited. - `Client` — back-reference to the `NatsClient` that owns this subscription. Used during message delivery to write `MSG` frames to the connection. --- ## The `SubListResult` Class `SubListResult` is the return type of `SubList.Match()`. It separates plain subscribers from queue group subscribers. ```csharp public sealed class SubListResult { public static readonly SubListResult Empty = new([], []); public Subscription[] PlainSubs { get; } public Subscription[][] QueueSubs { get; } // outer = groups, inner = members public SubListResult(Subscription[] plainSubs, Subscription[][] queueSubs) { PlainSubs = plainSubs; QueueSubs = queueSubs; } } ``` - `PlainSubs` — all plain subscribers whose subject pattern matches the published subject. - `QueueSubs` — one entry per queue group. Each inner array contains all members of that group. The message router picks one member per group. - `Empty` — a static singleton returned when no subscriptions match. Avoids allocation on the common case of no interest. --- ## The `SubjectMatch` Class `SubjectMatch` is a static utility class for subject validation and wildcard pattern matching. It defines the three fundamental constants used throughout the subscription system: ```csharp public static class SubjectMatch { public const char Pwc = '*'; // partial wildcard public const char Fwc = '>'; // full wildcard public const char Sep = '.'; // token separator ... } ``` ### Validation methods **`IsValidSubject(string subject)`** — returns `true` if the subject is a well-formed NATS subject. Rejects: - Empty strings. - Empty tokens (consecutive separators, or leading/trailing separators). - Tokens containing whitespace characters. - Any token after `>` (full wildcard must be last). **`IsLiteral(string subject)`** — returns `true` if the subject contains no wildcards. A character is treated as a wildcard only when it appears as a complete token (preceded and followed by `.` or the string boundary). **`IsValidPublishSubject(string subject)`** — returns `true` if the subject is both valid and literal. Publishers must use literal subjects; this is the combined check applied before inserting a message into the routing path. ### Pattern matching **`MatchLiteral(string literal, string pattern)`** — tests whether a literal subject matches a pattern that may contain wildcards. Used by the cache invalidation logic to determine which cached results are affected when a wildcard subscription is added or removed. ```csharp public static bool MatchLiteral(string literal, string pattern) { int li = 0, pi = 0; while (pi < pattern.Length) { // Get next pattern token int pTokenStart = pi; while (pi < pattern.Length && pattern[pi] != Sep) pi++; int pTokenLen = pi - pTokenStart; if (pi < pattern.Length) pi++; // skip separator // Full wildcard -- matches everything remaining if (pTokenLen == 1 && pattern[pTokenStart] == Fwc) return li < literal.Length; // must have at least one token left // Get next literal token if (li >= literal.Length) return false; int lTokenStart = li; while (li < literal.Length && literal[li] != Sep) li++; int lTokenLen = li - lTokenStart; if (li < literal.Length) li++; // skip separator // Partial wildcard -- matches any single token if (pTokenLen == 1 && pattern[pTokenStart] == Pwc) continue; // Literal comparison if (pTokenLen != lTokenLen) return false; if (string.Compare(literal, lTokenStart, pattern, pTokenStart, pTokenLen, StringComparison.Ordinal) != 0) return false; } return li >= literal.Length; // both exhausted } ``` The algorithm walks both strings token by token without allocating. `>` short-circuits immediately, returning `true` as long as at least one literal token remains. `*` skips the corresponding literal token without comparison. Literal tokens are compared with `StringComparison.Ordinal`. --- ## Related Documentation - [SubList Trie](../Subscriptions/SubList.md)