Documentation
¶
Overview ¶
Package queue — the consumer engine: the pull loop that runs a Handler on a CONSUMER'S goroutine, which is the third axis of ADR 0053's frontier.
Package queue — the consumer engine's configuration, its one mandatory assertion, and the clamps that make every other field safe to omit.
Package queue — who else can write a queue directory, asked of the mode bits: the whole answer on Unix, where a directory's other-write bit and its sticky bit are exactly the two facts the question needs.
Package queue — who else can write a queue directory, asked of Windows in the only vocabulary it has for it: the directory's DACL, read by the one reader this repository has (internal/kernel/fs/winacl — the lock domain's reader, ADR 0084/0086, moved to the kernel so that the queue no longer reaches into another service for it; ADR 0159).
Why the mode rule cannot run here ¶
os.Stat SYNTHESISES a mode from FILE_ATTRIBUTE_READONLY on Windows, so every writable directory reports 0777 with no sticky bit, and the Unix rule refused every queue directory a caller could name — as QUEUE_DIRECTORY_UNUSABLE, an error that blames the deployment for the platform (ADR 0018 §(a)'s failure mode). The first Windows run of the whole suite found it: every /file case.
The same two questions, asked of the DACL ¶
The root is refused when an identifier meaning anybody may take away an entry it did not create — winacl.ReplaceRights, the Windows spelling of "world-writable and not sticky". A state is refused when such an identifier may put an entry there at all — a file (a planted message) or a directory or junction — or take one away, or alter a file created there through what that file would inherit: on Unix a message is written 0600 whatever the directory allows, here it inherits the directory's list.
Where it does not reach ¶
The broker cannot yet RUN here: internal/service/data/vfs refuses Windows by design (no flushable directory handle, a mode that is not an ACL), so NewFile returns UNSUPPORTED_PLATFORM once these checks pass. They run anyway, so the refusal a caller meets on Windows is the platform's rather than a false verdict on their directory — and so the rules are right on the day vfs gains a Windows backend.
Package queue — the durable broker: one directory, three subdirectories, and rename(2) as the only thing that changes a fact.
Package queue — the durable broker's configuration, and the directory checks it runs before anything is opened.
Package queue — the durable broker's failure path: handing a message back, renewing a lease, and the record left behind for whoever investigates.
Package queue — the durable queue's state machine is a NAME. This file is its grammar: what a queued, an in-flight and an abandoned message are called, and how a name is read back into the facts it carries.
Package queue — the durable broker's read path: reclaiming the leases of consumers that died, and leasing what is visible.
Package queue — the durable broker's dead-letter decisions: putting a dead letter back into the queue, or deleting it — and writing one there as it lies; and its live messages read and written as they lie, for a copy.
Package queue — one entry in the in-memory broker's lease-deadline heap.
Package queue — the record the in-memory broker keeps about one message.
Package queue — the in-memory broker: the double a consumer's own tests run against, and the control the durability measurements are read against.
Package queue — the in-memory broker's configuration.
Package queue implements the asynchronous, durable message queue declared in internal/core/data/queue (ADR 0054): three brokers — one in the heap, one on the filesystem, one in a table of the caller's own SQL database (ADR 0151) — the consumer engine that drives a corequeue.Handler against any of them, and the dead-letter record they all write.
Three brokers, one set of refusals ¶
NewMemory exists so a consumer can test its own code without a directory or a database, and that is only worth something if the brokers answer identically. They therefore share the core policy guard (corequeue.PolicyValue.Validate), the retry delay (retryDelay), the same typed sentinels, and a table-driven conformance suite that runs the SAME cases against all three. Where they cannot be identical — surviving a restart, crossing a process boundary, joining the caller's transaction, what a flush costs — the difference is named rather than discovered by a reader.
The one thing the SQL broker does that neither other can ¶
NewSQL keeps the queue in one table of the database the application already writes, and every call runs on the transaction its context carries. So a message published inside the caller's transaction exists if and only if that transaction commits: the transactional outbox, which no broker whose publication is durable on its own can offer.
The one thing the file broker does that the memory broker cannot ¶
It survives the consumer. Every piece of state the durable queue keeps — which messages are queued, which are leased, when each lease lapses, how many times each has been delivered — lives in a DIRECTORY ENTRY, not in this process's heap. A consumer that is SIGKILLed mid-handler leaves an entry whose deadline is in the past, and the next consumer to look, in any process on that machine, renames it back and delivers it again. TestAKilledConsumerLosesItsLeaseAndTheMessageComesBack does exactly that, with a real subprocess and a real SIGKILL, because a test that calls Nack proves only that Nack works.
Why the durable broker holds no long-lived handle ¶
There is no Close in this package and no descriptor kept between calls. Every operation opens what it needs, moves what it must, and returns. That costs a syscall or two per call and buys the property the domain is for: a process that dies leaves nothing half-open, nothing to recover, and no state that only it could have interpreted. It is also what makes the broker genuinely inter-process — two brokers in two processes over one directory are the same queue, with no coordination beyond the filesystem.
Why there is no lock file ¶
Exclusion between consumers is os.Rename, not a lock. Renaming a queued message into the in-flight directory is atomic and it FAILS for the loser, so two consumers reaching for the same message resolve it in the kernel with no lock, no lease and no polling — and it resolves the same way for two goroutines in one process as for two processes, which ADR 0052 measured that flock(2) emphatically does not.
Five levels, and where each broker keeps them ¶
Every broker serves the visible messages of the highest corequeue.Priority first, strictly (ADR 0054, amended 2026-11-03). The file broker names a normal message exactly as it did before levels existed, byte for byte, so a queue on disk keeps working, and opens the names of every other level with one digit — the highest level 0, the lowest 4 — which its listing sorts first; a name without one is the normal level. The SQL broker keeps a level only in the column SQLPriorityMigration adds, when SQLConfig Priority is set; without it every statement is the one it always sent. The memory broker keeps one list per level.
Package queue — how long a nacked message waits, the same way in all three brokers.
Package queue — the SQL broker: the queue in one table of the caller's own database, every call on the transaction its context carries (ADR 0151).
Package queue — the SQL broker's construction parameters, and the refusals a configuration no SQL broker could run gets.
Package queue — the SQL broker's dead letters: burying one a handler rejected, reading them back, the two decisions an operator takes, and writing one as it lies; and its live messages read and written as they lie, for a copy.
Package queue — the only place the SQL broker renders SQL. Every statement it sends is built here, once, at NewSQL, for its dialect and its table; the two whose length depends on a batch are rendered per call from the same parts. Every one is spelled with the vocabulary core/data/sql's Dialect owns: the bind markers, the quoting and the row lock.
Package queue — the SQL broker's table, as a migration the caller runs under its own version table.
Package queue — the SQL broker's read path: leasing what is due, and burying the leases whose consumers died with no attempt left.
Package queue — a message held back until an instant, a publication made at most once per key over a window, and a nack that names its own delay, the same way in all three brokers (ADR 0183).
Package queue — waking an idle consumer: the broadcast every broker closes on a publication, and the table that lets two durable brokers over one queue — one directory, or one database and table — in one process wake each other's consumers.
Index ¶
- Constants
- func Consume(ctx context.Context, broker corequeue.Broker, cfg ConsumerConfig) error
- func NewFile(cfg FileConfig) (broker corequeue.Broker, err error)
- func NewMemory(cfg MemoryConfig) (broker corequeue.Broker, err error)
- func NewSQL(cfg SQLConfig) (broker corequeue.Broker, err error)
- func SQLMigration(dialect coresql.Dialect, table string, version uint64) (coresql.MigrationValue, error)
- func SQLPriorityMigration(dialect coresql.Dialect, table string, version uint64) (coresql.MigrationValue, error)
- func SQLTrailMigration(dialect coresql.Dialect, table string, version uint64) (coresql.MigrationValue, error)
- type ConsumerConfig
- type FileConfig
- type MemoryConfig
- type SQLConfig
Constants ¶
const DefaultPollInterval time.Duration = 100 * time.Millisecond
DefaultPollInterval is how long an idle worker waits before asking again, when ConsumerConfig.PollInterval is left at zero.
It is a CLAMP and not a refusal (ADR 0031): its zero has one reading — "you did not think about polling" — and 100 ms is defensible for every queue, because it bounds idle latency at a tenth of a second while costing one probe per tenth of a second — on the file broker a stat of each state directory while the queue is quiet, a read of them once it moves. A caller who cares picks their own.
Every broker in this package is a corequeue.Waker, so for them it bounds only what a wake cannot see — a publication made by ANOTHER process into a durable queue — and a caller that has no such producer can raise it a hundredfold. It stays at 100 ms because a zero must keep meaning what it always meant for a broker that cannot wake anyone.
const DefaultSettleTimeout time.Duration = 5 * time.Second
DefaultSettleTimeout bounds one acknowledgement, nack, rejection or release Consume sends once its context has ended, and one lease renewal, when ConsumerConfig.SettleTimeout is left at zero.
It is a CLAMP and not a refusal (ADR 0031): its zero has one reading — "you did not think about how long the medium may take to record an outcome" — and five seconds is far above what a flush-free rename or one indexed statement costs while still letting a stop end when the medium has stopped answering. It is also the most a handler is stopped early by: the margin kept before a lease ends is a tenth of the lease, at most the SettleTimeout in force.
const MaxSQLTableLen int = 63
MaxSQLTableLen is the longest table name NewSQL and SQLMigration accept, in bytes: PostgreSQL's identifier limit. The broker derives no table from it, so the whole limit is the caller's.
Variables ¶
This section is empty.
Functions ¶
func Consume ¶
Consume runs cfg.Handler against broker until ctx is done.
It is the third column of ADR 0053's frontier made concrete: the handler runs on a goroutine this function owns, not on the publisher's, and it runs in a different process from the publisher whenever the broker is a durable one. Publish returned long ago and knows nothing about any of this.
It returns nil when ctx ends — a cancelled consumer is a stopped consumer, not a failed one — and returns the broker's error when the BROKER fails, because a queue whose storage has stopped answering is not something a consumer can poll its way out of. A HANDLER's error is never returned: it is nacked, retried, and eventually dead-lettered, which is the entire point of the domain.
Every worker returns before Consume does, so a caller that cancels and waits knows no handler is still running.
A stop hands back what it did not do (kitsunium/sdk#474) ¶
When ctx ends, a worker leases nothing more. The message its handler is running gets ConsumerConfig.DrainTimeout to finish before the handler's context is cancelled; what finishes is acknowledged, and what the stop cut short is handed back through the broker's corequeue.Releaser — receivable at once, its delivery not counted — as is every message of the batch the handler never started. A broker without the sibling is nacked instead. Each of those outcomes is sent past the stop, on ctx without its cancellation, bounded by ConsumerConfig.SettleTimeout on the consumer's clock, so a caller's stop budget must cover DrainTimeout plus SettleTimeout.
A handler says when to try again (ADR 0183) ¶
A failure the handler marked with corequeue.RetryAfter is handed back through the broker's corequeue.DelayedNacker with the delay it names, its delivery counted as a nack counts it; a broker without the sibling is nacked, and the message waits the policy's retry delay instead. corequeue.DoNotRetry wins over it, and a failure a stop cut short is handed back uncounted, as any other is.
A handler ends before its lease does ¶
The handler's context is cancelled a margin before the lease it runs under ends — a tenth of the lease, at most SettleTimeout, read on the consumer's clock, which must be the broker's — with corequeue.LeaseExpired as its context.Cause: past that instant the broker may hand the message to another consumer. A message of a batch whose lease is already within that margin when its turn comes is never started; it is handed back like the batch a stop interrupts. With ConsumerConfig.AutoExtend the lease is renewed instead, through the broker's corequeue.LeaseExtender.
A renewal the handler makes itself is not one Consume sees: the bound stays the lease the handler was handed, and the outcome is reported on that lease's receipt, which the renewal voided. So a handler that extends its own lease returns right after — parking its message, as the mail spool does, which the void receipt's LEASE_EXPIRED leaves parked — and a handler that must run longer than its lease sets AutoExtend and does not extend it itself.
func NewFile ¶
func NewFile(cfg FileConfig) (broker corequeue.Broker, err error)
NewFile returns a broker whose messages survive the process that published them.
Why internal/service/data/vfs, and where it stops ¶
The durable publication of a message is exactly what corevfs.AtomicWriter promises: a temporary in the same directory, the payload, an fsync of the FILE, a rename, an fsync of the DIRECTORY — five steps whose failure paths ADR 0056 tested by injecting a failure at each one and comparing the destination's hash. Rewriting that here would be eighty lines of subtle code duplicating something the SDK merged, measured and proved, and the first thing to rot would be the failure paths, which are the only part that matters.
It stops at corevfs.WritableFS, which has WriteFile, MkdirAll, Remove and RemoveAll and NO RENAME. That is not an oversight in vfs and it is not worked around here: the port is FROZEN (ADR 0039), so adding a fifth method to carry this domain's state machine would break every downstream implementation at compile time. The state transitions therefore go through os.Root directly, which confines them to the queue directory by the same mechanism vfs itself uses, and the boundary between the two is exactly the boundary between "publish a whole file" and "move one".
It refuses, at construction rather than at first use: a policy it cannot honour (corequeue.QueueMisconfigured), a directory it cannot use safely (corequeue.QueueDirectoryUnusable), and a platform with no atomic replace or no flushable directory handle — core/proc.UnsupportedPlatform, raised by internal/service/data/vfs.NewOS and passed through unmodified (ADR 0018).
func NewMemory ¶
func NewMemory(cfg MemoryConfig) (broker corequeue.Broker, err error)
NewMemory returns an in-process broker holding its messages in the heap.
It refuses a policy it cannot honour at CONSTRUCTION rather than at first use, through the same core/data/queue.PolicyValue.Validate the file broker runs — which is what makes this an honest double for it.
func NewSQL ¶
NewSQL returns a broker whose queue is one table of the caller's database, created by SQLMigration. It sends no statement: everything decidable without a database is decided here.
It refuses, at construction rather than at first use, a policy it cannot honour (core/data/queue.QueueMisconfigured) and a transactor, dialect or table it cannot use (corequeue.SQLQueueMisconfigured).
func SQLMigration ¶
func SQLMigration(dialect coresql.Dialect, table string, version uint64) (coresql.MigrationValue, error)
SQLMigration returns the migration that creates the one table an SQL queue named table keeps on dialect, as a core/data/sql MigrationValue numbered version, for the caller's Migrator to run under the caller's version table. Its Down drops the table, and every message in it — queued, leased and dead.
The SDK numbers nothing: a migration is a value its consumer constructs (ADR 0055 §D12), and only the consumer knows where the queue's table sits in its own history. Its one statement does nothing when the table already exists — or is already gone — so applying it over a table a previous deployment made changes nothing (ADR 0151). The table it creates has the trail column SQLConfig.Trail writes; one an earlier release created gets it from SQLTrailMigration.
Its Checksum fingerprints the statements Up runs (ADR 0055, amended 2026-11-02): a version table that recorded it refuses a build whose statements differ, MIGRATION_CHANGED, and one that recorded the version without a checksum is given this one by the next locked run. The statements are therefore never edited: a later column comes as a migration of its own.
func SQLPriorityMigration ¶ added in v0.27.0
func SQLPriorityMigration(dialect coresql.Dialect, table string, version uint64) (coresql.MigrationValue, error)
SQLPriorityMigration returns the migration that adds the priority column — a small integer, NOT NULL, 0 by default, where a broker with SQLConfig.Priority keeps a message's level — and the index over (dead, priority, due, id) its lease reads, to the table an SQL queue named table keeps on dialect, as a core/data/sql MigrationValue numbered version, beside the one SQLMigration returns. The rows already there read as core/data/queue.PriorityNormal.
It is a migration of its own because SQLMigration's statements are never edited (ADR 0055, amended 2026-11-02). It does nothing when the table has the column or when there is no table, so it may run twice over one table. Its Down drops the index and the column, and every level in it: unset SQLConfig.Priority before running it. SQLite drops a column from version 3.35 on.
func SQLTrailMigration ¶ added in v0.25.0
func SQLTrailMigration(dialect coresql.Dialect, table string, version uint64) (coresql.MigrationValue, error)
SQLTrailMigration returns the migration that adds the nullable trail column, where a broker with SQLConfig.Trail keeps a dead letter's trail, to the table an SQL queue named table keeps on dialect, as a core/data/sql MigrationValue numbered version, beside the one SQLMigration returns. The rows already there keep their columns as they are, and their dead letters read back with no trail.
It does nothing when the table has the column — SQLMigration creates it with one — or when there is no table, so it may run before or after SQLMigration and twice over one table. Its Down drops the column, and every trail in it, when the table has one: unset SQLConfig.Trail before running it. SQLite drops a column from version 3.35 on.
Types ¶
type ConsumerConfig ¶
type ConsumerConfig struct {
// Handler processes one delivery. It must be non-nil.
Handler corequeue.Handler
// Clock drives the idle wait, the handler's lease bound, the drain, the
// bound on each outcome and the lease renewals. Nil means clock.System.
//
// Nothing in this package calls time.Sleep; an idle worker waits through
// Waiter.After and a cancelled context wakes it immediately.
//
// A lease's end is the broker's instant and is read on this clock, so it
// must be the broker's: a test drives both with one ManualClock. A lease
// that already looks ended on this clock the moment it is received —
// two clocks that disagree — is not bounded at all, as before the bound
// existed, rather than never run.
Clock clock.Timed
// PollInterval is how long a worker waits after finding the queue empty.
// Zero means [DefaultPollInterval]; negative is read as zero.
//
// When the broker is a [corequeue.Waker] — every broker in this package
// is — it is an upper bound and not a cadence: a Publish or a Nack in
// this process wakes the worker at once, and a retry delay ending or a
// lease lapsing wakes it at that instant. What is left for the poll to
// find is a publication another process made into a durable queue, so set
// it to how late such a message may be noticed — seconds, not
// milliseconds, is the usual answer — and an idle consumer then costs
// nothing between polls.
PollInterval time.Duration
// Parallelism is how many independent workers run. Each one polls, leases
// and processes on its OWN goroutine, which is what makes this the
// consumer's goroutine rather than the publisher's.
//
// Non-positive is clamped to 1: one worker is what a caller who did not
// think about concurrency wants, it is never wrong, and there is nothing
// here for the SDK to invent.
//
// Above 1 there is NO ordering left, in any sense.
Parallelism int
// BatchSize is how many messages one worker leases per poll. Non-positive
// is clamped to 1.
//
// Above 1 a worker processes its batch SEQUENTIALLY, so the extra
// messages are held under a lease they are not yet being worked on: a
// batch of 50 with a 30-second visibility timeout gives the last message
// 30 seconds minus the other 49's processing time. Batch for throughput
// against a durable broker, where one poll is a directory read; do not
// batch to get parallelism, which is what Parallelism is.
//
// A message whose lease has run down to the margin [Consume] keeps
// before its turn comes is not started: it is handed back uncounted
// and leased again by a later poll — or, with AutoExtend, renewed
// before it starts.
BatchSize int
// SettleTimeout bounds each outcome a worker reports once ctx has
// ended — an acknowledgement, a nack, a rejection or a release — and
// each lease renewal AutoExtend sends. Zero means
// [DefaultSettleTimeout]; negative is read as zero.
//
// Every outcome is sent on the consumer's context WITHOUT its
// cancellation, so a stop landing between a handler's return and its
// acknowledgement does not abandon it (kitsunium/sdk#472) and a batch a
// stop interrupts is still handed back. That is what needs a bound: a
// medium that stopped answering would otherwise hold the stop forever.
// It runs on Clock, from the instant ctx ends — while the consumer
// runs an outcome is not timed, and nothing but the worker waits on
// it. An outcome the bound cut short is QueueBackendFailed, which a
// stopping consumer reads as its stop, not as a failure.
//
// It also caps the margin a handler is stopped by before its lease
// ends: a tenth of the lease, at most this.
SettleTimeout time.Duration
// DrainTimeout is how long the message a worker is handling when ctx
// ends may still run before its handler's context is cancelled — River's
// SoftStopTimeout. Zero cancels it at once, which is what a stop always
// did; negative is read as zero.
//
// A handler that finishes inside it is acknowledged; one the drain cut
// short is handed back uncounted, through the broker's
// [corequeue.Releaser]. No new message is leased once ctx has ended,
// and the lease bound still applies: a drain never keeps a handler past
// the margin before its lease ends. Measured on Clock. A caller's stop
// budget — a lifecycle component's — must cover it plus SettleTimeout.
DrainTimeout time.Duration
// AckedWindow is how many message identifiers the engine remembers
// having handled: the last AckedWindow messages whose handler returned
// nil or [corequeue.Acknowledged] in this process. A delivery of one of
// them — its lease lapsed before the acknowledgement landed, and the
// broker delivered it again — is acknowledged without running the
// handler a second time.
//
// It narrows the duplicates at-least-once delivery leaves, as the mail
// spool's memory of what it delivered does, for every broker; it does
// not remove them. The window is this process's and is lost when it
// stops: a redelivery to another consumer, or after a restart, still
// runs the handler, which is still idempotent. Zero, or negative,
// remembers nothing — every delivery runs the handler, what Consume
// always did.
AckedWindow int
// AutoExtend renews the lease of the message a handler is running, a
// third of the lease after it was granted or last renewed, for as long
// as the handler runs — the heartbeat of SQS, JetStream's in-progress
// acknowledgement, Temporal's activity heartbeat. A message of a batch
// whose lease is a third spent when its turn comes is renewed before
// its handler starts. Each renewal is as long as the lease the broker
// first granted, and is bounded by SettleTimeout.
//
// It needs the broker's [corequeue.LeaseExtender]; without it, or when
// false, the lease is never renewed and the handler is stopped a margin
// before it ends. A renewal the broker refuses — the lease lapsed, the
// medium failed — cancels the handler's context with that refusal as
// its cause: its work is now a duplicate in the making.
//
// A renewal replaces the receipt, and the engine acknowledges with the
// newest one. So a handler that extends its own lease — parking its
// message, as the mail spool does — must leave AutoExtend false: the
// receipt it holds is void after the first renewal.
AutoExtend bool
// HandlerIsIdempotent asserts, IN CODE, that Handler is safe to run more
// than once for the same message.
//
// It must be true. Its zero value is false and is REFUSED
// ([corequeue.ConsumerMisconfigured]), and that refusal is the domain's
// one piece of deliberate ceremony.
//
// The reason is that this queue delivers AT LEAST ONCE and cannot do
// otherwise. A consumer that dies between finishing the work and
// acknowledging it has done the work and not said so, and the message
// comes back — that is not a bug to be fixed, it is the only honest
// behaviour available to a queue whose consumers can die. Exactly-once
// delivery does not exist over a transport; what exists is at-least-once
// plus an idempotent consumer, and the second half is the caller's.
//
// The SDK cannot check it, so it makes the caller assert it where a
// reviewer will see it. It is precisely
// resilience.HedgeConfig.Idempotent's instrument, for precisely its
// reason, and it has the same useful side effect:
// `grep -rn HandlerIsIdempotent` enumerates every place in a codebase
// where somebody promised this.
HandlerIsIdempotent bool
}
ConsumerConfig configures Consume: what to run, how many of it, how often to look, and the one thing about the handler the SDK cannot check for itself.
Every field except Handler and HandlerIsIdempotent is CLAMPED, so a caller with no opinion writes two fields and gets a correct consumer. The zero of each field added since — DrainTimeout, AutoExtend — is what Consume did before it existed.
type FileConfig ¶
type FileConfig struct {
// Clock reads time. It is the narrow half of kernel/clock because this
// broker never waits: a lapsed lease is noticed by the next Receive, not
// by a timer.
//
// Injecting a ManualClock makes every deadline in a SINGLE-process test
// instant. It cannot make a cross-process test instant, and that
// asymmetry is not an oversight: a lease deadline is a wall-clock instant
// written into a filename precisely so that another process can read it,
// and another process does not share this one's fake clock. The
// consumer-death test therefore waits on the real clock, and says so.
//
// Nil means clock.System.
Clock clock.Clock
// Random draws the retry jitter [corequeue.PolicyValue].RetryJitter asks
// for: a number in [0, 1), as math/rand/v2.Float64 returns. Nil means
// math/rand/v2.Float64. A test injects a fixed sequence and asserts every
// retry instant to the nanosecond, as it does without a jitter; the
// three brokers draw from it identically, so the memory broker stays an
// exact double. It may be called by several goroutines at once.
Random func() float64
// Dir is the queue directory. It is created owner-only when absent, and
// refused when it exists and any account outside the owner and group
// could replace its entries. Its three state directories are held to the
// same rule — created owner-only, checked when somebody else made them —
// and refused besides when one is a symlink or not a directory at all.
// On Windows "owner-only" is not something a mode can say: a directory
// inherits its parent's access control list, and the rule reads that list
// instead (dirtrust_windows.go).
//
// Two brokers over one Dir — in one process or in two — are ONE queue.
// That is the domain's inter-process claim, and it is the whole reason
// the directory is the configuration rather than an implementation
// detail.
Dir string
// Policy is the delivery discipline. Its zero value is refused; see
// core/data/queue.PolicyValue.
Policy corequeue.PolicyValue
}
FileConfig configures NewFile: which directory holds the queue, which clock reads its deadlines, and what delivery discipline it enforces.
Its Policy's zero value is refused at construction — see core/data/queue.PolicyValue — and so is a directory whose entries any account could replace.
type MemoryConfig ¶
type MemoryConfig struct {
// Clock reads time. A memory broker never WAITS — expiry is noticed on
// the next Receive — so it asks for the narrow half of kernel/clock and
// a test can drive every deadline in this package with a ManualClock and
// no sleeping at all.
//
// Nil means clock.System.
Clock clock.Clock
// Random draws the retry jitter [corequeue.PolicyValue].RetryJitter asks
// for: a number in [0, 1), as math/rand/v2.Float64 returns. Nil means
// math/rand/v2.Float64. A test injects a fixed sequence and asserts every
// retry instant to the nanosecond, as it does without a jitter; the
// three brokers draw from it identically, so the memory broker stays an
// exact double. It may be called by several goroutines at once.
Random func() float64
// Policy is the delivery discipline. Its zero value is refused; see
// core/data/queue.PolicyValue.
Policy corequeue.PolicyValue
}
MemoryConfig configures NewMemory: which clock reads its deadlines, and what delivery discipline it enforces.
Its Policy's zero value is refused at construction through the SAME guard NewFile runs, which is what makes this broker an honest double for that one.
type SQLConfig ¶
type SQLConfig struct {
// Transactor is the transaction manager of the database the queue lives
// in — the one the caller's own transactions are opened with, so a call
// made under one of them joins it. It must also be a core/data/sql Joiner and
// Deferrer, which the SDK's own transactor is: the first is how a
// publication joins the caller's transaction, the second how it wakes a
// consumer only once that transaction has committed.
Transactor coresql.Transactor
// Clock stamps every instant the broker writes — a message's visibility,
// a lease's deadline, a dead letter's failure — and reads the instant a
// lease is judged against. Nil means clock.System.
//
// The instants are the BROKER's, never the database's NOW(): a test
// drives every deadline with a ManualClock, as it does the other two
// brokers'. Brokers in several processes over one table compare
// instants their own clocks wrote, so those clocks must agree to within
// the precision a lease needs — as they must for the file broker.
Clock clock.Clock
// Random draws the retry jitter [corequeue.PolicyValue].RetryJitter asks
// for: a number in [0, 1), as math/rand/v2.Float64 returns. Nil means
// math/rand/v2.Float64. A test injects a fixed sequence and asserts every
// retry instant to the nanosecond, as it does without a jitter; the
// three brokers draw from it identically, so the memory broker stays an
// exact double. It may be called by several goroutines at once.
Random func() float64
// Table names the queue's one table, which [SQLMigration] creates. It is
// a lower-case SQL identifier of at most [MaxSQLTableLen] bytes, holds no
// three underscores in a row — docstore's derived tables do — and does
// not start with "sqlite_".
//
// Two brokers over one database and one Table — in one process or in
// twenty — are ONE queue, as two file brokers over one directory are.
Table string
// Policy is the delivery discipline. Its zero value is refused; see
// core/data/queue.PolicyValue.
Policy corequeue.PolicyValue
// Dialect is the engine's, and must be the Transactor's own: the SQL the
// broker speaks to it.
Dialect coresql.Dialect
// Trail makes the broker keep a dead letter's trail
// (core/data/queue.DeadLetterValue.Trail) in the table's trail column,
// which [SQLMigration] creates and [SQLTrailMigration] adds to a table an
// earlier SQLMigration created: run that migration before setting it.
// Unset, the broker sends the statements it sent before the column
// existed, and a dead letter it buries or reads has no trail.
Trail bool
// Priority makes the broker keep a message's level
// (core/data/queue.PriorityPublisher) in the table's priority column,
// which [SQLPriorityMigration] adds, and hand out the visible rows of
// the highest level first: run that migration before setting it.
// Unset, the broker sends the statements it sent before the column
// existed, reads every message as core/data/queue.PriorityNormal, and
// refuses to publish or write one at any other level.
Priority bool
}
SQLConfig configures NewSQL: the database the queue lives in, its table, which clock stamps its instants, and what delivery discipline it enforces. Transactor, Dialect and Table are required, and Policy's zero value is refused as it is for every broker.
Source Files
¶
- bulk.go
- consume.go
- consume_acked.go
- consume_config.go
- consume_idle.go
- consume_lease.go
- dirtrust_posix.go
- doc.go
- file.go
- file_bulk.go
- file_config.go
- file_dead.go
- file_listing.go
- file_name.go
- file_once.go
- file_receive.go
- file_replay.go
- file_stats.go
- lease_expiry.go
- lifetime.go
- mem_record.go
- memory.go
- memory_bulk.go
- memory_config.go
- once_end.go
- queue.go
- retry.go
- sql.go
- sql_bulk.go
- sql_bulk_dialect.go
- sql_config.go
- sql_dead.go
- sql_dialect.go
- sql_migration.go
- sql_once.go
- sql_priority.go
- sql_receive.go
- sql_trail.go
- wake.go