A lightweight, goroutine-safe wrapper around
k8s.io/client-go/util/workqueuefor managing tasks (envelopes) with a fixed worker pool, periodic scheduling, deadlines, hooks, and stamps (middleware). Adds a safe queue lifecycle (Start/Stop/Start), buffering before first start, and queue capacity limiting.
Under the hood it uses
workqueue.TypedRateLimitingInterface. Deduplication happens by pointer to the element: repeatedAddof the same pointer while it is in-flight is ignored.
- Features
- Installation
- Quick Start
- Concepts & Contracts
- Worker Behavior
- Stop Modes
- Capacity Limiting
- Benchmarks
- Metrics (Prometheus)
- Examples
- License
Goroutine-safe in-memory queue for your service:
- All public methods are safe to call from multiple goroutines.
Sendcan be called concurrently. Multiple workers are supported vialimit.Start/Stopare serialized internally. - This is not a distributed queue: there are no guarantees across processes/hosts. Ensure your hooks/invokers are thread-safe around shared state.
What you get:
- A clear lifecycle FSM:
init β running β stopping β stopped - Both one-off and periodic tasks
- Middleware chain via
Stamp - Hooks:
before/after/failure/success - Capacity accounting (quota)
- Graceful or fast stop (
Drain/Stop) and restartable queues (Start()afterStop())
Error/Hook semantics:
ErrStopEnvelopeβ intentional stop of a specific envelope:- the envelope is forgotten, not rescheduled;
- if raised in
beforeHook/invoke, theafterHookstill runs (within a time-bounded context);successHookdoes not run.
context.Canceled/context.DeadlineExceededβ not a success:- envelope is forgotten; periodic ones are rescheduled, one-off ones are not.
- Any other error:
- periodic β rescheduled (if queue is alive);
- one-off β defer to
failureHookdecision (RetryNow/RetryAfter/Drop).
- Each hook runs with its own timeout: a fraction
frac=0.5of the envelope'sdeadline, but at leasthardHookLimit(800ms). Hook timeouts are derived from the task contexttctx, so hooks never outlive the envelope deadline.
Concurrency controls (brief):
stateMuguards the FSM state (RLock read / Lock write)lifecycleMuserializes Start/Stop/queue swapqueueMuguards the inner workqueue pointerpendingMuguards the pre-start bufferrunis an atomic fast flag for βqueue aliveβ- Capacity accounting is atomic via
tryReserve/inc/dec/unreserve
Other highlights:
- Worker pool via
WithLimitOption(n) - Start/Stop/Start: tasks sent before first start are buffered and flushed on
Start() - Periodic vs one-off:
interval > 0means periodic;interval == 0means one-off - Deadlines:
deadline > 0boundsinvoketime viacontext.WithTimeoutin the worker - Stamps: both global (queue-level) and per-envelope (task-level), with predictable execution order
- Panic safety: panics inside task are handled (
Forget+Done) and logged with stack; worker keeps running - Prometheus metrics: use
client-goworkqueue metrics
go get github.com/PavelAgarkov/rate-envelope-queueRecommended pins (compatible with this package):
go get k8s.io/client-go@v0.34.0
go get k8s.io/component-base@v0.34.0Requires: Go 1.24+.
See full examples in examples/:
queue_with_simple_start_stop_dynamic_execute.gosimple_queue_with_simple_preset_envelopes.gosimple_queue_with_simple_schedule_envelopes.gosimple_queue_with_simple_dynamic_envelopes.gosimple_queue_with_simple_combine_envelopes.go
Capacity scenarios (accounting correctness):
Drain + waiting=true β wait for all workers; all dec() happen; no remainder.
envelopeQueue := NewRateEnvelopeQueue(
parent,
"test_queue",
WithLimitOption(5),
WithWaitingOption(true),
WithStopModeOption(Drain),
WithAllowedCapacityOption(50),
)Stop + waiting=true β after wg.Wait() we subtract the βtailβ (cur - pend), the counter converges.
envelopeQueue := NewRateEnvelopeQueue(
parent,
"test_queue",
WithLimitOption(5),
WithWaitingOption(true),
WithStopModeOption(Stop),
WithAllowedCapacityOption(50),
)Unlimited capacity β WithAllowedCapacityOption(0) removes admission limits; the currentCapacity metric still reflects actual occupancy.
envelopeQueue := NewRateEnvelopeQueue(
parent,
"test_queue",
WithLimitOption(5),
WithWaitingOption(true),
WithStopModeOption(Drain),
WithAllowedCapacityOption(0),
)Minimal API sketch:
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
q := NewRateEnvelopeQueue(
ctx,
"emails",
WithLimitOption(4),
WithWaitingOption(true),
WithStopModeOption(Drain),
WithAllowedCapacityOption(1000),
WithStamps(LoggingStamp()),
)
emailOnce, _ := NewEnvelope(
WithId(1),
WithType("email"),
WithScheduleModeInterval(0), // one-off
WithDeadline(3*time.Second),
WithInvoke(func(ctx context.Context, e *Envelope) error { return nil }),
)
ticker, _ := NewEnvelope(
WithId(2),
WithType("metrics"),
WithScheduleModeInterval(5*time.Second), // periodic
WithDeadline(2*time.Second),
WithInvoke(func(ctx context.Context, e *Envelope) error { return nil }),
)
q.Start()
_ = q.Send(emailOnce, ticker)
// ...
q.Stop()
q.Start() // restart if needede, err := NewEnvelope(
WithId(123), // optional, for logs
WithType("my_task"), // optional, for logs
WithScheduleModeInterval(time.Second), // 0 = one-off
WithDeadline(500*time.Millisecond), // 0 = no deadline
WithBeforeHook(func(ctx context.Context, e *Envelope) error { return nil }),
WithInvoke(func(ctx context.Context, e *Envelope) error { return nil }), // required
WithAfterHook(func(ctx context.Context, e *Envelope) error { return nil }),
WithFailureHook(func(ctx context.Context, e *Envelope, err error) Decision {
return DefaultOnceDecision() // Drop by default
// return RetryOnceAfterDecision(5 * time.Second)
// return RetryOnceNowDecision()
}),
WithSuccessHook(func(ctx context.Context, e *Envelope) {}),
WithStampsPerEnvelope(LoggingStamp()),
WithPayload(myPayload),
)Validation:
invokeis required;interval >= 0;deadline >= 0- For periodic:
deadlinemust not exceedintervalβErrAdditionEnvelopeToQueueBadIntervals
Special error:
ErrStopEnvelopeβ gracefully stops this envelope only (no reschedule)
q := NewRateEnvelopeQueue(ctx, "queue-name",
WithLimitOption(n),
WithWaitingOption(true|false),
WithStopModeOption(Drain|Stop),
WithAllowedCapacityOption(cap), // 0 = unlimited
WithWorkqueueConfigOption(conf),
WithLimiterOption(limiter),
WithStamps(stamps...),
)
q.Start()
err := q.Send(e1, e2, e3) // ErrAllowedQueueCapacityExceeded on overflow
q.Stop()Pre-start buffer. In init, Send() pushes envelopes into an internal buffer; on Start() they are flushed into the workqueue.
type (
Invoker func(ctx context.Context, envelope *Envelope) error
Stamp func(next Invoker) Invoker
)Order: global stamps (outer) wrap per-envelope stamps (inner), then Envelope.invoke.
A sample LoggingStamp() is provided for demonstration.
| Result / condition | Queue action |
|---|---|
invoke returns nil |
Forget; if interval > 0 and alive β AddAfter(interval) |
context.Canceled / DeadlineExceeded |
Forget; if periodic and alive β AddAfter(interval) |
ErrStopEnvelope |
Forget; no reschedule |
| Error on periodic | Forget; if alive β AddAfter(interval) |
Error on one-off + failureHook |
Use decision: RetryNow / RetryAfter(d) / Drop |
| Panic in task | Forget + Done + stack log; worker continues |
βQueue is aliveβ =
run == true, state isrunning, base context not done, andworkqueuenot shutting down.
| Waiting \ StopMode | Drain (graceful) |
Stop (fast) |
|---|---|---|
true |
Wait for workers; ShutDownWithDrain() |
Wait for workers; ShutDown() |
false |
No wait; ShutDownWithDrain() |
Immediate stop; ShutDown() |
After Stop() you can call Start() again: a fresh inner workqueue will be created.
WithAllowedCapacityOption(cap) limits the total number of in-flight/queued/delayed items (including reschedules).
If the limit would be exceeded, Send() returns ErrAllowedQueueCapacityExceeded.
currentCapacity is updated on add, reschedule, and completion.
cap == 0β unlimited admission; thecurrentCapacitymetric still tracks actual occupancy.Stop + waiting=false + StopMode=Stopβ documented tail leakage in accounting. UseDrainorwaiting=truefor accurate capacity convergence.
Command examples:
go test -bench=BenchmarkQueueFull -benchmem
go test -bench=BenchmarkQueueInterval -benchmemNumbers provided by the author (your CPU/env will vary):
BenchmarkQueueFull-8 3212882 348.7 ns/op 40 B/op 1 allocs/op
BenchmarkQueueInterval-8 110313 12903 ns/op 1809 B/op 24 allocs/op
Workqueue metrics are enabled via blank import:
import (
_ "k8s.io/component-base/metrics/prometheus/workqueue"
"k8s.io/component-base/metrics/legacyregistry"
"net/http"
)
func serveMetrics() {
mux := http.NewServeMux()
mux.Handle("/metrics", legacyregistry.Handler())
go http.ListenAndServe(":8080", mux)
}Your queue name (QueueConfig.Name) is included in workqueue metric labels (workqueue_*: adds, depth, work_duration, retries, etc.).
See the examples/ folder for runnable snippets covering one-off jobs, periodic schedules, combined modes, and dynamic dispatch.
MIT β see LICENSE.
ΠΡΠ³ΠΊΠ°Ρ, ΠΏΠΎΡΠΎΠΊΠΎΠ±Π΅Π·ΠΎΠΏΠ°ΡΠ½Π°Ρ ΠΎΠ±ΡΡΡΠΊΠ° Π½Π°Π΄
k8s.io/client-go/util/workqueueΠ΄Π»Ρ ΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΡ Π·Π°Π΄Π°ΡΠ°ΠΌΠΈ (envelopes) Ρ ΡΠΈΠΊΡΠΈΡΠΎΠ²Π°Π½Π½ΡΠΌ ΠΏΡΠ»ΠΎΠΌ Π²ΠΎΡΠΊΠ΅ΡΠΎΠ², ΠΏΠ΅ΡΠΈΠΎΠ΄ΠΈΡΠ΅ΡΠΊΠΈΠΌ ΠΏΠ»Π°Π½ΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ, Π΄Π΅Π΄Π»Π°ΠΉΠ½Π°ΠΌΠΈ, Ρ ΡΠΊΠ°ΠΌΠΈ ΠΈ stamps (middleware). ΠΠΎΠ±Π°Π²Π»ΡΠ΅Ρ Π±Π΅Π·ΠΎΠΏΠ°ΡΠ½ΡΠΉ ΠΆΠΈΠ·Π½Π΅Π½Π½ΡΠΉ ΡΠΈΠΊΠ» ΠΎΡΠ΅ΡΠ΅Π΄ΠΈ (Start/Stop/Start), Π±ΡΡΠ΅ΡΠΈΠ·Π°ΡΠΈΡ Π·Π°Π΄Π°Ρ Π΄ΠΎ ΡΡΠ°ΡΡΠ° ΠΈ ΠΎΠ³ΡΠ°Π½ΠΈΡΠ΅Π½ΠΈΠ΅ ΡΠΌΠΊΠΎΡΡΠΈ ΠΎΡΠ΅ΡΠ΅Π΄ΠΈ.
Π ΠΎΡΠ½ΠΎΠ²Π΅ β
workqueue.TypedRateLimitingInterface. ΠΠ΅Π΄ΡΠΏΠ»ΠΈΠΊΠ°ΡΠΈΡ ΠΏΡΠΎΠΈΡΡ ΠΎΠ΄ΠΈΡ ΠΏΠΎ ΡΠΊΠ°Π·Π°ΡΠ΅Π»Ρ Π½Π° ΡΠ»Π΅ΠΌΠ΅Π½Ρ: ΠΏΠΎΠ²ΡΠΎΡΠ½ΡΠΉAddΡΠΎΠ³ΠΎ ΠΆΠ΅ ΡΠΊΠ°Π·Π°ΡΠ΅Π»Ρ ΠΏΠΎΠΊΠ° ΠΎΠ½ Π² ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠ΅ β ΠΈΠ³Π½ΠΎΡΠΈΡΡΠ΅ΡΡΡ.
- ΠΠ»ΡΡΠ΅Π²ΡΠ΅ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΠΈ
- Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ°
- ΠΡΡΡΡΡΠΉ ΡΡΠ°ΡΡ
- ΠΠΎΠ½ΡΠ΅ΠΏΡΠΈΠΈ ΠΈ ΠΊΠΎΠ½ΡΡΠ°ΠΊΡΡ
- ΠΠΎΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ Π²ΠΎΡΠΊΠ΅ΡΠ°
- Π Π΅ΠΆΠΈΠΌΡ ΠΎΡΡΠ°Π½ΠΎΠ²ΠΊΠΈ
- ΠΠ³ΡΠ°Π½ΠΈΡΠ΅Π½ΠΈΠ΅ ΡΠΌΠΊΠΎΡΡΠΈ
- ΠΠ΅Π½ΡΠΌΠ°ΡΠΊΠΈ
- ΠΠ΅ΡΡΠΈΠΊΠΈ (Prometheus)
- ΠΡΠΈΠΌΠ΅ΡΡ
- ΠΠΈΡΠ΅Π½Π·ΠΈΡ
ΠΠΎΡΠΎΠΊΠΎΠ±Π΅Π·ΠΎΠΏΠ°ΡΠ½Π°Ρ Π»ΠΎΠΊΠ°Π»ΡΠ½Π°Ρ ΠΎΡΠ΅ΡΠ΅Π΄Ρ Π² ΠΏΠ°ΠΌΡΡΠΈ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ:
- ΠΡΠ΅ ΠΏΡΠ±Π»ΠΈΡΠ½ΡΠ΅ ΠΌΠ΅ΡΠΎΠ΄Ρ Π±Π΅Π·ΠΎΠΏΠ°ΡΠ½Ρ ΠΏΡΠΈ Π²ΡΠ·ΠΎΠ²Π°Ρ
ΠΈΠ· Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΈΡ
Π³ΠΎΡΡΡΠΈΠ½.
SendΠΌΠΎΠΆΠ½ΠΎ Π²ΡΠ·ΡΠ²Π°ΡΡ ΠΏΠ°ΡΠ°Π»Π»Π΅Π»ΡΠ½ΠΎ. ΠΠΎΡΠΊΠ΅ΡΠΎΠ² ΠΌΠΎΠΆΠ΅Ρ Π±ΡΡΡ Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΎ (limit). ΠΡΠ·ΠΎΠ²ΡStart/StopΡΠ΅ΡΠΈΠ°Π»ΠΈΠ·ΡΡΡΡΡ Π²Π½ΡΡΡΠΈ. - ΠΡΠΎ Π½Π΅ ΡΠ°ΡΠΏΡΠ΅Π΄Π΅Π»ΡΠ½Π½Π°Ρ ΠΎΡΠ΅ΡΠ΅Π΄Ρ: Π³Π°ΡΠ°Π½ΡΠΈΠΉ ΠΌΠ΅ΠΆΠ΄Ρ ΡΠ°Π·Π½ΡΠΌΠΈ ΠΏΡΠΎΡΠ΅ΡΡΠ°ΠΌΠΈ/ΡΠ·Π»Π°ΠΌΠΈ Π½Π΅Ρ. ΠΠΎΠ΄ Ρ ΡΠΊΠΎΠ²/ΠΈΠ½Π²ΠΎΠΊΠ΅ΡΠΎΠ² Π΄ΠΎΠ»ΠΆΠ΅Π½ ΡΠ°ΠΌ ΠΎΠ±Π΅ΡΠΏΠ΅ΡΠΈΠ²Π°ΡΡ ΠΏΠΎΡΠΎΠΊΠΎΠ±Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎΡΡΡ ΠΏΡΠΈ Π΄ΠΎΡΡΡΠΏΠ΅ ΠΊ ΠΎΠ±ΡΠΈΠΌ ΡΠ΅ΡΡΡΡΠ°ΠΌ.
Π§ΡΠΎ Π²Π½ΡΡΡΠΈ:
- ΠΡΠΎΠ·ΡΠ°ΡΠ½ΡΠΉ Π°Π²ΡΠΎΠΌΠ°Ρ ΡΠΎΡΡΠΎΡΠ½ΠΈΠΉ:
init β running β stopping β stopped - ΠΠ΄Π½ΠΎΡΠ°Π·ΠΎΠ²ΡΠ΅ ΠΈ ΠΏΠ΅ΡΠΈΠΎΠ΄ΠΈΡΠ΅ΡΠΊΠΈΠ΅ Π·Π°Π΄Π°ΡΠΈ
- Π¦Π΅ΠΏΠΎΡΠΊΠ° middleware ΡΠ΅ΡΠ΅Π·
Stamp - Π₯ΡΠΊΠΈ:
before/after/failure/success - Π£ΡΡΡ ΡΠΌΠΊΠΎΡΡΠΈ (quota)
- ΠΡΠ³ΠΊΠΈΠΉ ΠΈΠ»ΠΈ Π±ΡΡΡΡΡΠΉ ΠΎΡΡΠ°Π½ΠΎΠ² (
Drain/Stop) ΠΈ ΠΏΠΎΠ²ΡΠΎΡΠ½ΡΠΉ ΡΡΠ°ΡΡ (Start()ΠΏΠΎΡΠ»Π΅Stop())
Π‘Π΅ΠΌΠ°Π½ΡΠΈΠΊΠ° ΠΎΡΠΈΠ±ΠΎΠΊ ΠΈ Ρ ΡΠΊΠΎΠ²:
ErrStopEnvelopeβ Π½Π°ΠΌΠ΅ΡΠ΅Π½Π½Π°Ρ ΠΎΡΡΠ°Π½ΠΎΠ²ΠΊΠ° ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½ΠΎΠ³ΠΎ ΠΊΠΎΠ½Π²Π΅ΡΡΠ°:- ΠΊΠΎΠ½Π²Π΅ΡΡ Π·Π°Π±ΡΠ²Π°Π΅ΡΡΡ, Π½Π΅ ΠΏΠ΅ΡΠ΅ΠΏΠ»Π°Π½ΠΈΡΡΠ΅ΡΡΡ;
- Π΅ΡΠ»ΠΈ ΠΎΡΠΈΠ±ΠΊΠ° Π²ΠΎΠ·Π½ΠΈΠΊΠ»Π° Π²
beforeHook/invoke,afterHookΠ²ΡΡ ΡΠ°Π²Π½ΠΎ Π²ΡΠ·ΠΎΠ²Π΅ΡΡΡ (Ρ ΠΎΠ³ΡΠ°Π½ΠΈΡΠ΅Π½Π½ΡΠΌ Π²ΡΠ΅ΠΌΠ΅Π½Π΅ΠΌ);successHookΠ½Π΅ Π²ΡΠ·ΡΠ²Π°Π΅ΡΡΡ.
context.Canceled/context.DeadlineExceededβ ΡΡΠΎ Π½Π΅ ΡΡΠΏΠ΅Ρ :- ΠΊΠΎΠ½Π²Π΅ΡΡ Π·Π°Π±ΡΠ²Π°Π΅ΡΡΡ; ΠΏΠ΅ΡΠΈΠΎΠ΄ΠΈΡΠ΅ΡΠΊΠΈΠΉ β ΠΏΠ΅ΡΠ΅ΠΏΠ»Π°Π½ΠΈΡΡΠ΅ΡΡΡ, ΠΎΠ΄Π½ΠΎΡΠ°Π·ΠΎΠ²ΡΠΉ β Π½Π΅Ρ.
- ΠΡΠ±Π°Ρ Π΄ΡΡΠ³Π°Ρ ΠΎΡΠΈΠ±ΠΊΠ°:
- ΠΏΠ΅ΡΠΈΠΎΠ΄ΠΈΡΠ΅ΡΠΊΠ°Ρ β ΠΏΠ΅ΡΠ΅ΠΏΠ»Π°Π½ΠΈΡΡΠ΅ΡΡΡ (Π΅ΡΠ»ΠΈ ΠΎΡΠ΅ΡΠ΅Π΄Ρ Β«ΠΆΠΈΠ²Π°Β»);
- ΠΎΠ΄Π½ΠΎΡΠ°Π·ΠΎΠ²Π°Ρ β ΡΠ΅ΡΠ΅Π½ΠΈΠ΅ ΡΠ΅ΡΠ΅Π·
failureHook(RetryNow/RetryAfter/Drop).
- ΠΠ°ΠΆΠ΄ΡΠΉ Ρ
ΡΠΊ Π²ΡΠΏΠΎΠ»Π½ΡΠ΅ΡΡΡ Ρ ΡΠΎΠ±ΡΡΠ²Π΅Π½Π½ΡΠΌ ΡΠ°ΠΉΠΌΠ°ΡΡΠΎΠΌ: Π΄ΠΎΠ»Ρ
frac=0.5ΠΎΡdeadlineΠΊΠΎΠ½Π²Π΅ΡΡΠ°, Π½ΠΎ Π½Π΅ ΠΌΠ΅Π½ΡΡΠ΅hardHookLimit(800ΠΌΡ). Π’Π°ΠΉΠΌΠ°ΡΡΡ Β«Π²ΠΈΡΡΡΒ» Π½Π°tctx, Ρ.Π΅. Ρ ΡΠΊΠΈ Π½ΠΈΠΊΠΎΠ³Π΄Π° Π½Π΅ ΠΏΠ΅ΡΠ΅ΠΆΠΈΠ²ΡΡ Π΄Π΅Π΄Π»Π°ΠΉΠ½ ΠΊΠΎΠ½Π²Π΅ΡΡΠ°.
ΠΠΎΡΠΎΠΊΠΎΠ±Π΅Π·ΠΎΠΏΠ°ΡΠ½ΠΎΡΡΡ (ΠΊΠΎΡΠΎΡΠΊΠΎ):
stateMuβ ΡΡΠ΅Π½ΠΈΠ΅/Π·Π°ΠΏΠΈΡΡ ΡΠΎΡΡΠΎΡΠ½ΠΈΡlifecycleMuβ ΡΠ΅ΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΡ Start/Stop/ΡΠΌΠ΅Π½Ρ ΠΎΡΠ΅ΡΠ΅Π΄ΠΈqueueMuβ Π΄ΠΎΡΡΡΠΏ ΠΊ Π²Π½ΡΡΡΠ΅Π½Π½Π΅ΠΉ ΠΎΡΠ΅ΡΠ΅Π΄ΠΈpendingMuβ Π±ΡΡΠ΅Ρ Π·Π°Π΄Π°Ρ Π΄ΠΎ ΡΡΠ°ΡΡΠ°runβ Π°ΡΠΎΠΌΠ°ΡΠ½ΡΠΉ ΡΠ»Π°Π³ Β«ΠΆΠΈΠ²Π° Π»ΠΈ ΠΎΡΠ΅ΡΠ΅Π΄ΡΒ»- Π£ΡΡΡ ΡΠΌΠΊΠΎΡΡΠΈ β Π°ΡΠΎΠΌΠ°ΡΠ½ΡΠ΅ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΈ
tryReserve/inc/dec/unreserve
ΠΡΠΎΡΠ΅Π΅:
- ΠΡΠ» Π²ΠΎΡΠΊΠ΅ΡΠΎΠ²:
WithLimitOption(n) - Start/Stop/Start: Π·Π°Π΄Π°ΡΠΈ, Π΄ΠΎΠ±Π°Π²Π»Π΅Π½Π½ΡΠ΅ Π΄ΠΎ ΠΏΠ΅ΡΠ²ΠΎΠ³ΠΎ
Start(), Π±ΡΡΠ΅ΡΠΈΠ·ΡΡΡΡΡ ΠΈ ΠΏΠ΅ΡΠ΅Π»ΠΈΠ²Π°ΡΡΡΡ Π² ΠΎΡΠ΅ΡΠ΅Π΄Ρ ΠΏΡΠΈ ΡΡΠ°ΡΡΠ΅ - ΠΠ΅ΡΠΈΠΎΠ΄ΠΈΡΠ΅ΡΠΊΠΈΠ΅ / ΠΎΠ΄Π½ΠΎΡΠ°Π·ΠΎΠ²ΡΠ΅:
interval > 0β ΠΏΠ΅ΡΠΈΠΎΠ΄ΠΈΡΠ΅ΡΠΊΠ°Ρ;interval == 0β ΠΎΠ΄Π½ΠΎΡΠ°Π·ΠΎΠ²Π°Ρ - ΠΠ΅Π΄Π»Π°ΠΉΠ½Ρ:
deadline > 0ΠΎΠ³ΡΠ°Π½ΠΈΡΠΈΠ²Π°Π΅Ρ Π²ΡΠ΅ΠΌΡinvokeΡΠ΅ΡΠ΅Π·context.WithTimeout - Stamps: Π³Π»ΠΎΠ±Π°Π»ΡΠ½ΡΠ΅ ΠΈ Π½Π° ΡΡΠΎΠ²Π½Π΅ ΠΊΠΎΠ½Π²Π΅ΡΡΠ°, ΠΏΠΎΡΡΠ΄ΠΎΠΊ Π²ΡΠΏΠΎΠ»Π½Π΅Π½ΠΈΡ ΠΏΡΠ΅Π΄ΡΠΊΠ°Π·ΡΠ΅ΠΌ
- ΠΠ°ΡΠΈΡΠ° ΠΎΡ ΠΏΠ°Π½ΠΈΠΊ: ΠΏΠ°Π½ΠΈΠΊΠ° Π² Π·Π°Π΄Π°ΡΠ΅ β
Forget+DoneΠΈ Π»ΠΎΠ³ ΡΡΠ΅ΠΊΠ°; Π²ΠΎΡΠΊΠ΅Ρ ΠΏΡΠΎΠ΄ΠΎΠ»ΠΆΠ°Π΅Ρ ΡΠ°Π±ΠΎΡΡ - ΠΠ΅ΡΡΠΈΠΊΠΈ Prometheus: ΠΈΠ·
client-goworkqueue
go get github.com/PavelAgarkov/rate-envelope-queueΠ Π΅ΠΊΠΎΠΌΠ΅Π½Π΄ΡΠ΅ΠΌΡΠ΅ Π²Π΅ΡΡΠΈΠΈ:
go get k8s.io/client-go@v0.34.0
go get k8s.io/component-base@v0.34.0Π’ΡΠ΅Π±ΠΎΠ²Π°Π½ΠΈΡ: Go 1.24+.
Π‘ΠΌΠΎΡΡΠΈΡΠ΅ ΠΊΠ°ΡΠ°Π»ΠΎΠ³ examples/:
queue_with_simple_start_stop_dynamic_execute.gosimple_queue_with_simple_preset_envelopes.gosimple_queue_with_simple_schedule_envelopes.gosimple_queue_with_simple_dynamic_envelopes.gosimple_queue_with_simple_combine_envelopes.go
Π‘ΡΠ΅Π½Π°ΡΠΈΠΈ ΡΠΌΠΊΠΎΡΡΠΈ (ΠΊΠΎΡΡΠ΅ΠΊΡΠ½ΠΎΡΡΡ ΡΡΡΡΠ°):
Drain + waiting=true β Π΄ΠΎΠΆΠΈΠ΄Π°Π΅ΠΌΡΡ Π²ΡΠ΅Ρ
Π²ΠΎΡΠΊΠ΅ΡΠΎΠ²; Π²ΡΠ΅ dec() ΠΏΡΠΎΡΠ»ΠΈ; ΠΎΡΡΠ°ΡΠΊΠ° Π½Π΅Ρ.
envelopeQueue := NewRateEnvelopeQueue(
parent,
"test_queue",
WithLimitOption(5),
WithWaitingOption(true),
WithStopModeOption(Drain),
WithAllowedCapacityOption(50),
)Stop + waiting=true β ΠΏΠΎΡΠ»Π΅ wg.Wait() ΡΠ½ΠΈΠΌΠ°Π΅ΡΡΡ Β«Ρ
Π²ΠΎΡΡΒ» (cur - pend), ΡΡΡΡΡΠΈΠΊ ΡΡ
ΠΎΠ΄ΠΈΡΡΡ.
envelopeQueue := NewRateEnvelopeQueue(
parent,
"test_queue",
WithLimitOption(5),
WithWaitingOption(true),
WithStopModeOption(Stop),
WithAllowedCapacityOption(50),
)ΠΠ΅Π·Π»ΠΈΠΌΠΈΡΠ½Π°Ρ ΡΠΌΠΊΠΎΡΡΡ β WithAllowedCapacityOption(0) ΡΠ±ΠΈΡΠ°Π΅Ρ ΠΎΠ³ΡΠ°Π½ΠΈΡΠ΅Π½ΠΈΠ΅ ΠΏΡΠΈΡΠΌΠ°; ΠΌΠ΅ΡΡΠΈΠΊΠ° currentCapacity ΠΎΡΡΠ°ΠΆΠ°Π΅Ρ ΡΠ°ΠΊΡΠΈΡΠ΅ΡΠΊΡΡ Π·Π°Π½ΡΡΠΎΡΡΡ.
envelopeQueue := NewRateEnvelopeQueue(
parent,
"test_queue",
WithLimitOption(5),
WithWaitingOption(true),
WithStopModeOption(Drain),
WithAllowedCapacityOption(0),
)ΠΠΈΠ½ΠΈβΠΏΡΠΈΠΌΠ΅Ρ:
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
q := NewRateEnvelopeQueue(
ctx,
"emails",
WithLimitOption(4),
WithWaitingOption(true),
WithStopModeOption(Drain),
WithAllowedCapacityOption(1000),
WithStamps(LoggingStamp()),
)
emailOnce, _ := NewEnvelope(
WithId(1),
WithType("email"),
WithScheduleModeInterval(0), // ΠΎΠ΄Π½ΠΎΡΠ°Π·ΠΎΠ²Π°Ρ
WithDeadline(3*time.Second),
WithInvoke(func(ctx context.Context, e *Envelope) error { return nil }),
)
ticker, _ := NewEnvelope(
WithId(2),
WithType("metrics"),
WithScheduleModeInterval(5*time.Second), // ΠΏΠ΅ΡΠΈΠΎΠ΄ΠΈΡΠ΅ΡΠΊΠ°Ρ
WithDeadline(2*time.Second),
WithInvoke(func(ctx context.Context, e *Envelope) error { return nil }),
)
q.Start()
_ = q.Send(emailOnce, ticker)
// ...
q.Stop()
q.Start() // ΠΏΡΠΈ Π½Π΅ΠΎΠ±Ρ
ΠΎΠ΄ΠΈΠΌΠΎΡΡΠΈ ΠΌΠΎΠΆΠ½ΠΎ ΡΠ½ΠΎΠ²Π° ΡΡΠ°ΡΡΠΎΠ²Π°ΡΡe, err := NewEnvelope(
WithId(123), // ΠΎΠΏΡΠΈΠΎΠ½Π°Π»ΡΠ½ΠΎ, Π΄Π»Ρ Π»ΠΎΠ³ΠΎΠ²
WithType("my_task"), // ΠΎΠΏΡΠΈΠΎΠ½Π°Π»ΡΠ½ΠΎ, Π΄Π»Ρ Π»ΠΎΠ³ΠΎΠ²
WithScheduleModeInterval(time.Second), // 0 = ΠΎΠ΄Π½ΠΎΡΠ°Π·ΠΎΠ²Π°Ρ
WithDeadline(500*time.Millisecond), // 0 = Π±Π΅Π· Π΄Π΅Π΄Π»Π°ΠΉΠ½Π°
WithBeforeHook(func(ctx context.Context, e *Envelope) error { return nil }),
WithInvoke(func(ctx context.Context, e *Envelope) error { return nil }), // ΠΎΠ±ΡΠ·Π°ΡΠ΅Π»ΡΠ½ΠΎ
WithAfterHook(func(ctx context.Context, e *Envelope) error { return nil }),
WithFailureHook(func(ctx context.Context, e *Envelope, err error) Decision {
return DefaultOnceDecision() // ΠΏΠΎ ΡΠΌΠΎΠ»ΡΠ°Π½ΠΈΡ Drop
// return RetryOnceAfterDecision(5 * time.Second)
// return RetryOnceNowDecision()
}),
WithSuccessHook(func(ctx context.Context, e *Envelope) {}),
WithStampsPerEnvelope(LoggingStamp()),
WithPayload(myPayload),
)ΠΠ°Π»ΠΈΠ΄Π°ΡΠΈΡ:
invokeΠΎΠ±ΡΠ·Π°ΡΠ΅Π»Π΅Π½;interval >= 0;deadline >= 0- ΠΠ»Ρ ΠΏΠ΅ΡΠΈΠΎΠ΄ΠΈΡΠ΅ΡΠΊΠΈΡ
:
deadlineΠ½Π΅ Π΄ΠΎΠ»ΠΆΠ΅Π½ ΠΏΡΠ΅Π²ΡΡΠ°ΡΡintervalβErrAdditionEnvelopeToQueueBadIntervals
Π‘ΠΏΠ΅ΡβΠΎΡΠΈΠ±ΠΊΠ°:
ErrStopEnvelopeβ ΠΊΠΎΡΡΠ΅ΠΊΡΠ½ΠΎ ΠΏΡΠ΅ΠΊΡΠ°ΡΠ°Π΅Ρ ΡΠΎΠ»ΡΠΊΠΎ ΡΡΠΎΡ ΠΊΠΎΠ½Π²Π΅ΡΡ (Π±Π΅Π· ΠΏΠ΅ΡΠ΅ΠΏΠ»Π°Π½ΠΈΡΠΎΠ²Π°Π½ΠΈΡ)
q := NewRateEnvelopeQueue(ctx, "queue-name",
WithLimitOption(n),
WithWaitingOption(true|false),
WithStopModeOption(Drain|Stop),
WithAllowedCapacityOption(cap), // 0 = Π±Π΅Π· Π»ΠΈΠΌΠΈΡΠ°
WithWorkqueueConfigOption(conf),
WithLimiterOption(limiter),
WithStamps(stamps...),
)
q.Start()
err := q.Send(e1, e2, e3) // ErrAllowedQueueCapacityExceeded ΠΏΡΠΈ ΠΏΠ΅ΡΠ΅ΠΏΠΎΠ»Π½Π΅Π½ΠΈΠΈ
q.Stop()ΠΡΡΠ΅Ρ Π΄ΠΎ ΡΡΠ°ΡΡΠ°. Π ΡΠΎΡΡΠΎΡΠ½ΠΈΠΈ init Send() ΡΠΊΠ»Π°Π΄ΡΠ²Π°Π΅Ρ Π·Π°Π΄Π°ΡΠΈ Π²ΠΎ Π²Π½ΡΡΡΠ΅Π½Π½ΠΈΠΉ Π±ΡΡΠ΅Ρ; ΠΏΡΠΈ Start() β ΠΎΠ½ΠΈ ΠΏΠ΅ΡΠ΅Π»ΠΈΠ²Π°ΡΡΡΡ Π² workqueue.
type (
Invoker func(ctx context.Context, envelope *Envelope) error
Stamp func(next Invoker) Invoker
)ΠΠΎΡΡΠ΄ΠΎΠΊ: ΡΠ½Π°ΡΠ°Π»Π° Π³Π»ΠΎΠ±Π°Π»ΡΠ½ΡΠ΅ stamps (Π²Π½Π΅ΡΠ½ΠΈΠ΅), Π·Π°ΡΠ΅ΠΌ perβenvelope (Π²Π½ΡΡΡΠ΅Π½Π½ΠΈΠ΅), ΠΏΠΎΡΠ»Π΅ β Envelope.invoke.
LoggingStamp() β ΠΏΡΠΈΠΌΠ΅Ρ Π΄Π»Ρ ΠΈΠ»Π»ΡΡΡΡΠ°ΡΠΈΠΈ (Π½Π΅ Β«ΡΠ΅ΡΠ΅Π±ΡΡΠ½Π°Ρ ΠΏΡΠ»ΡΒ» Π΄Π»Ρ ΠΏΡΠΎΠ΄Π°ΠΊΡΠ΅Π½Π°).
| Π‘ΠΎΠ±ΡΡΠΈΠ΅ / ΡΠ΅Π·ΡΠ»ΡΡΠ°Ρ | ΠΠ΅ΠΉΡΡΠ²ΠΈΠ΅ ΠΎΡΠ΅ΡΠ΅Π΄ΠΈ |
|---|---|
invoke Π²Π΅ΡΠ½ΡΠ» nil |
Forget; Π΅ΡΠ»ΠΈ interval > 0 ΠΈ ΠΎΡΠ΅ΡΠ΅Π΄Ρ Β«ΠΆΠΈΠ²Π°Β» β AddAfter(interval) |
context.Canceled / DeadlineExceeded |
Forget; Π΅ΡΠ»ΠΈ ΠΏΠ΅ΡΠΈΠΎΠ΄ΠΈΡΠ΅ΡΠΊΠ°Ρ ΠΈ ΠΎΡΠ΅ΡΠ΅Π΄Ρ Β«ΠΆΠΈΠ²Π°Β» β AddAfter(interval) |
ErrStopEnvelope |
Forget; Π½Π΅ ΠΏΠ΅ΡΠ΅ΠΏΠ»Π°Π½ΠΈΡΡΠ΅ΠΌ |
| ΠΡΠΈΠ±ΠΊΠ° Ρ ΠΏΠ΅ΡΠΈΠΎΠ΄ΠΈΡΠ΅ΡΠΊΠΎΠΉ | Forget; Π΅ΡΠ»ΠΈ ΠΎΡΠ΅ΡΠ΅Π΄Ρ Β«ΠΆΠΈΠ²Π°Β» β AddAfter(interval) |
ΠΡΠΈΠ±ΠΊΠ° Ρ ΠΎΠ΄Π½ΠΎΡΠ°Π·ΠΎΠ²ΠΎΠΉ + failureHook |
Π Π΅ΡΠ΅Π½ΠΈΠ΅ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ: RetryNow / RetryAfter(d) / Drop |
| ΠΠ°Π½ΠΈΠΊΠ° Π² Π·Π°Π΄Π°ΡΠ΅ | Forget + Done + Π»ΠΎΠ³ ΡΡΠ΅ΠΊΠ°; Π²ΠΎΡΠΊΠ΅Ρ ΠΏΡΠΎΠ΄ΠΎΠ»ΠΆΠ°Π΅Ρ ΡΠ°Π±ΠΎΡΡ |
Β«ΠΡΠ΅ΡΠ΅Π΄Ρ ΠΆΠΈΠ²Π°Β» =
run == true,state == running, Π±Π°Π·ΠΎΠ²ΡΠΉ ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡ Π½Π΅ Π·Π°Π²Π΅ΡΡΡΠ½ ΠΈworkqueueΠ½Π΅ Π² shutdown.
| Waiting \ StopMode | Drain (ΠΌΡΠ³ΠΊΠ°Ρ) |
Stop (ΠΆΡΡΡΠΊΠ°Ρ) |
|---|---|---|
true |
ΠΠ΄Π°ΡΡ Π²ΠΎΡΠΊΠ΅ΡΠΎΠ²; ShutDownWithDrain() |
ΠΠ΄Π°ΡΡ Π²ΠΎΡΠΊΠ΅ΡΠΎΠ²; ShutDown() |
false |
ΠΠ΅Π· ΠΎΠΆΠΈΠ΄Π°Π½ΠΈΡ Π²ΠΎΡΠΊΠ΅ΡΠΎΠ²; ShutDownWithDrain() |
ΠΠ³Π½ΠΎΠ²Π΅Π½Π½ΡΠΉ ΠΎΡΡΠ°Π½ΠΎΠ²; ShutDown() |
ΠΠΎΡΠ»Π΅ Stop() ΠΌΠΎΠΆΠ½ΠΎ Π²ΡΠ·ΡΠ²Π°ΡΡ Start() ΠΏΠΎΠ²ΡΠΎΡΠ½ΠΎ: ΡΠΎΠ·Π΄Π°ΡΡΡΡ Π½ΠΎΠ²ΡΠΉ Π²Π½ΡΡΡΠ΅Π½Π½ΠΈΠΉ workqueue.
WithAllowedCapacityOption(cap) ΠΎΠ³ΡΠ°Π½ΠΈΡΠΈΠ²Π°Π΅Ρ ΡΡΠΌΠΌΠ°ΡΠ½ΠΎΠ΅ ΡΠΈΡΠ»ΠΎ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠ² Π² ΡΠΈΡΡΠ΅ΠΌΠ΅ (Π²ΠΊΠ»ΡΡΠ°Ρ ΠΏΠ΅ΡΠ΅ΠΏΠ»Π°Π½ΠΈΡΠΎΠ²Π°Π½Π½ΡΠ΅).
ΠΡΠΈ ΠΏΠΎΠΏΡΡΠΊΠ΅ ΠΏΡΠ΅Π²ΡΡΠ΅Π½ΠΈΡ Π»ΠΈΠΌΠΈΡΠ° Send() Π²ΠΎΠ·Π²ΡΠ°ΡΠ°Π΅Ρ ErrAllowedQueueCapacityExceeded.
currentCapacity ΠΎΠ±Π½ΠΎΠ²Π»ΡΠ΅ΡΡΡ ΠΏΡΠΈ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠΈ, ΠΏΠ΅ΡΠ΅ΠΏΠ»Π°Π½ΠΈΡΠΎΠ²Π°Π½ΠΈΠΈ ΠΈ Π·Π°Π²Π΅ΡΡΠ΅Π½ΠΈΠΈ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΈ.
cap == 0β Π±Π΅Π·Π»ΠΈΠΌΠΈΡ ΠΏΠΎ ΠΏΡΠΈΡΠΌΡ; ΠΌΠ΅ΡΡΠΈΠΊΠ°currentCapacityΠΎΡΡΠ°ΠΆΠ°Π΅Ρ ΡΠ°ΠΊΡΠΈΡΠ΅ΡΠΊΡΡ Π·Π°Π½ΡΡΠΎΡΡΡ.Stop + waiting=false + StopMode=Stopβ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠΈΡΠΎΠ²Π°Π½Π½Π°Ρ ΡΡΠ΅ΡΠΊΠ° Β«Ρ Π²ΠΎΡΡΠ°Β» Π² ΡΡΡΡΠ΅. ΠΠ»Ρ ΡΠΎΡΠ½ΠΎΠΉ ΡΡ ΠΎΠ΄ΠΈΠΌΠΎΡΡΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠΉΡΠ΅DrainΠΈΠ»ΠΈwaiting=true.
ΠΠ°ΠΊ Π·Π°ΠΏΡΡΠΊΠ°ΡΡ:
go test -bench=BenchmarkQueueFull -benchmem
go test -bench=BenchmarkQueueInterval -benchmemΠ¦ΠΈΡΡΡ Π°Π²ΡΠΎΡΠ° (Π·Π°Π²ΠΈΡΡΡ ΠΎΡ CPU/ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ):
BenchmarkQueueFull-8 3212882 348.7 ns/op 40 B/op 1 allocs/op
BenchmarkQueueInterval-8 110313 12903 ns/op 1809 B/op 24 allocs/op
ΠΠ΅ΡΡΠΈΠΊΠΈ workqueue Π°ΠΊΡΠΈΠ²ΠΈΡΡΡΡΡΡ Π±Π»Π°Π½ΠΊβΠΈΠΌΠΏΠΎΡΡΠΎΠΌ:
import (
_ "k8s.io/component-base/metrics/prometheus/workqueue"
"k8s.io/component-base/metrics/legacyregistry"
"net/http"
)
func serveMetrics() {
mux := http.NewServeMux()
mux.Handle("/metrics", legacyregistry.Handler())
go http.ListenAndServe(":8080", mux)
}ΠΠΌΡ ΠΎΡΠ΅ΡΠ΅Π΄ΠΈ (QueueConfig.Name) ΠΏΠΎΠΏΠ°Π΄Π°Π΅Ρ Π² Π»Π΅ΠΉΠ±Π»Ρ ΠΌΠ΅ΡΡΠΈΠΊ (workqueue_*: adds, depth, work_duration, retries ΠΈ Ρ.Π΄.).
Π‘ΠΌΠΎΡΡΠΈΡΠ΅ ΠΊΠ°ΡΠ°Π»ΠΎΠ³ examples/ β ΡΠ°ΠΌ Π΅ΡΡΡ Π³ΠΎΡΠΎΠ²ΡΠ΅ Π²Π°ΡΠΈΠ°Π½ΡΡ Π΄Π»Ρ ΠΎΠ΄Π½ΠΎΡΠ°Π·ΠΎΠ²ΡΡ
Π·Π°Π΄Π°Ρ, ΠΏΠ΅ΡΠΈΠΎΠ΄ΠΈΡΠ΅ΡΠΊΠΈΡ
ΡΠ°ΡΠΏΠΈΡΠ°Π½ΠΈΠΉ, ΠΊΠΎΠΌΠ±ΠΈΠ½ΠΈΡΠΎΠ²Π°Π½Π½ΡΡ
ΡΡΠ΅Π½Π°ΡΠΈΠ΅Π² ΠΈ Π΄ΠΈΠ½Π°ΠΌΠΈΡΠ΅ΡΠΊΠΎΠ³ΠΎ Π΄ΠΈΡΠΏΠ°ΡΡΠ°.
MIT β ΡΠΌ. LICENSE.