EIP processors (extra)¶
pkg/processor/eip_extra.go rounds out the EIP catalog with producer-driven enrichers, an in-memory idempotent repository, and richer variants of Delay, Loop, and Sample. These are the building blocks the reifier reaches for when you wire enrich:, poll-enrich:, sample: etc. against pre-resolved producers.
Processors in this file¶
NewEnrichProcessor¶
Sends a copy of the exchange to a pre-resolved producer (any engine.Processor) and merges the response into the original. Differs from Enrich in that it takes a producer directly, not a URI to resolve.
| Field | Type | Default | Description |
|---|---|---|---|
Producer |
engine.Processor |
required | The endpoint to call. |
Strategy |
engine.AggregationStrategy |
nil | Merger. If nil, the producer's body replaces the original. |
Constructor: NewEnrichProcessor(producer engine.Processor, strategy engine.AggregationStrategy) *EnrichProcessor.
NewPollEnrichProcessor¶
Like EnrichProcessor but creates a fresh exchange to send to the producer rather than copying the current one. Used for polling consumers (a queue/topic) where the input message is irrelevant. Supports an optional timeout.
| Field | Type | Default | Description |
|---|---|---|---|
Producer |
engine.Processor |
required | The polling endpoint. |
Strategy |
engine.AggregationStrategy |
nil | Merger. If nil, the polled body replaces the original. |
Timeout |
duration | 0 (no timeout) |
Maximum wait for the producer. |
Constructor: NewPollEnrichProcessor(producer, strategy, timeout) *PollEnrichProcessor.
Compare to PollEnrich, which resolves a URI each call and supports PollingEndpointResolver.
NewDelayProcessor¶
Pure delay with optional async mode.
| Field | Type | Default | Description |
|---|---|---|---|
Duration |
duration | required | How long to sleep. |
Async |
bool | false |
If true: spawn a goroutine, return immediately. If false: block synchronously with context cancellation. |
Constructor: NewDelayProcessor(duration time.Duration, async bool) *DelayProcessor.
Synchronous mode honors ctx.Done() and returns ctx.Err() on early cancellation. Async mode discards the delay channel; useful only when you want to fire some side-effect on a timer but not wait. There is no downstream processor in this variant — for delay + run, see Delay.
NewLoopProcessor¶
Fixed-count loop with the option to run each iteration on a copy of the exchange (so iterations are independent) rather than the same exchange (so iterations accumulate state). Compare to Loop.
| Field | Type | Default | Description |
|---|---|---|---|
Count |
int | required | Number of iterations. |
Processor |
engine.Processor |
required | Inner pipeline. |
Copy |
bool | false |
If true, each iteration uses ex.Copy(). |
Constructor: NewLoopProcessor(count, processor, copyExchange bool) *LoopProcessor. Sets HelixLoopIndex and HelixLoopSize per iteration. Errors wrap the iteration index: loop processor: iteration N failed: ....
NewSampleProcessor¶
Probabilistic OR time-based sampler. Use Rate (a float 0.0-1.0) for random sampling, or Period for "at most one message per duration". If both are set, Period wins.
| Field | Type | Default | Description |
|---|---|---|---|
Rate |
float | 0 |
Probability of letting a message through, 0.0 to 1.0. Uses math/rand. |
Period |
duration | 0 |
If > 0: at most one message per period passes. |
Constructor: NewSampleProcessor(rate float64, period time.Duration) *SampleProcessor.
Behavior:
- Time-based: stores the last-sent timestamp atomically. If
now - last >= Period, updates the timestamp and lets the message through. Otherwise setsHelixSampleSkipped = trueand returnsnil. - Rate-based: rolls
rand.Float64(). If< Rate, passes. Otherwise setsHelixSampleSkipped = true.
Unlike Sample, this variant doesn't have an inner processor — it returns nil either way. Skipped messages are not surfaced as errors; downstream pipelines should check the property.
NewInMemoryIdempotentRepository¶
Thread-safe in-memory idempotent repository with optional size cap and TTL.
| Constructor arg | Description |
|---|---|
maxSize |
0 for unlimited; otherwise the soft cap. |
ttl |
0 for no expiry; otherwise per-key TTL. |
Constructor: NewInMemoryIdempotentRepository(maxSize int, ttl time.Duration) *InMemoryIdempotentRepository.
Implements the IdempotentRepository2 interface (see below). Add returns true if the key was new, false if a duplicate. Internally:
- Stores
map[string]time.Time(insertion time). - On
Addat capacity, first callsevictExpiredLockedto remove all expired keys; if still at capacity, callsevictOldestLockedto remove a single oldest key (linear scan). - TTL is checked on read, not on a background timer.
For Redis-backed distributed dedup see idempotent_redis.
IdempotentRepository2 interface¶
The non-context variant used by IdempotentConsumerProcessor:
type IdempotentRepository2 interface {
Add(key string) bool // returns true if key was new
Contains(key string) bool
Remove(key string)
Clear()
}
Distinct from IdempotentRepository (in routing.go) — Add returns a bool here, and there's an extra Clear. Distinct also from DistributedIdempotentRepository (in idempotent_redis.go) — no context, no error returns.
NewIdempotentConsumerProcessor¶
Deduplicates using any IdempotentRepository2. On a duplicate, the inner processor is skipped; the duplicate is either silently dropped (SkipDuplicate: true) or marked via HelixDuplicateMessage = true (SkipDuplicate: false).
| Field | Type | Default | Description |
|---|---|---|---|
Repository |
IdempotentRepository2 |
required | Backing store. |
KeyExpression |
engine.Expression |
required | Dedup key extractor. |
Processor |
engine.Processor |
required | Inner pipeline for new keys. |
SkipDuplicate |
bool | config | If true: drop duplicates silently. If false: continue processing without invoking Processor but set the property. |
Constructor: NewIdempotentConsumerProcessor(repo, keyExpr, processor, skipDuplicate bool) *IdempotentConsumerProcessor.
Pitfalls¶
DelayProcessorwithAsync: truereturns immediately and runs no downstream processor — it's a fire-and-forget sleep. For "delay then process" useDelayinresilience.go.SampleProcessortime-based mode has at-most-once semantics per period but uses wall clock — clock skew or backwards jumps can let extra messages through.InMemoryIdempotentRepositoryeviction is linear in map size. For very large keysets, use the Redis backend.IdempotentConsumerProcessorwithSkipDuplicate: truereturnsnilfor duplicates — downstream observers can't tell the difference between "skipped duplicate" and "ran successfully".
Examples¶
Probabilistic sampling:
Time-based sampling for log noise control:
In-memory dedup with TTL:
repo := processor.NewInMemoryIdempotentRepository(100_000, 24*time.Hour)
dedup := processor.NewIdempotentConsumerProcessor(
repo,
expression.NewStarlang(`header("X-Message-Id")`),
downstream,
true, // SkipDuplicate
)
Source¶
pkg/processor/eip_extra.go
See also¶
- eip — core EIPs.
- eip_advanced —
ContentEnricher(callback-based instead of producer-based). - poll_enrich — URI-resolving version with
PollingEndpointResolversupport. - idempotent_redis — distributed dedup with TTL.
- resilience —
Loop,Delay,Samplevariants that include a downstream processor.