Expose persistent? and checkpoint! on queue write clients for durable input acknowledgement - #19362
Expose persistent? and checkpoint! on queue write clients for durable input acknowledgement#19362LolloneS wants to merge 6 commits into
Conversation
🤖 GitHub commentsJust comment with:
|
|
This pull request does not have a backport label. Could you fix it @LolloneS? 🙏
|
…st closed queue Page.ensurePersistedUpto used a strict > comparison against an exclusive upper bound, causing checkpoint! to no-op when exactly one event was written since the last checkpoint — silently breaking the at-least-once guarantee for single-record Kafka poll batches. Change the comparison to >=. Queue.ensurePersisted dereferenced headPage with no closed-queue check; close() nulls headPage under the same lock after its own final fsync, so a Kafka consumer thread racing shutdown would NPE. Return early when isClosed() — close() has already fsynced everything by that point so no data is lost. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TL;DRThe Remediation
Investigation detailsRoot CauseThe run terminates while polling for a specific debug log pattern in the test Relevant code paths:
PR diff context inspected:
Evidence
Verification
Follow-up
What is this? | From workflow: PR Buildkite Detective Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not. |
💛 Build succeeded, but was flaky
Failed CI StepsHistory
|
Release notes
Added
persistent?andcheckpoint!methods to the queue write client used by input plugins, allowing inputs to fsync the persistent queue before acknowledging upstream systems.What does this PR do?
Adds a small durability API to the queue write-client hierarchy so input plugins can guarantee events are persisted to disk before acknowledging them upstream (e.g. committing Kafka offsets):
Queue#ensurePersisted()— new no-arg method that takes the queue lock and fsyncs the head page up to the last written sequence number (delegates to the existingPage#ensurePersistedUpto; a no-op when the last head checkpoint already covers all writes). Tail pages need no handling: page rotation already checkpoints and fsyncs.JRubyAckedQueueExt#ensure_persisted— Ruby-visible bridge, wrappingIOExceptionin a RubyIOError(mirrorsrubyWrite).JRubyAbstractQueueWriteClientExt— exposespersistent?andcheckpoint!via@JRubyMethod, delegating to new abstract hooksisPersistent()/doCheckpoint(ThreadContext)(same pattern asrubyPush→doPush).JrubyAckedWriteClientExt—persistent?→ true;checkpoint!→ fsync viaensure_persisted.JrubyMemoryWriteClientExt—persistent?→ false;checkpoint!raisesNotImplementedError(raising rather than silently no-oping means a caller that skipped its own PQ guard fails loudly instead of committing offsets without durability).JRubyWrappedWriteClientExt(what input plugins actually receive inrun) — delegates both methods to the wrapped client, with no metrics wrapping (a checkpoint is not an event push).The change is purely additive: 0 deletions, no existing method's behavior changes, and nothing in core calls the new API — it is only reachable by plugins that opt in.
Why is it important/What is the impact to the user?
Today an input plugin cannot know when the persistent queue has fsynced the events it pushed:
queue << eventreturns once the event lands in the head-page buffer. For sources with commit/ack semantics (Kafka being the driving case), this means offsets can be committed for events that a crash would lose — silently breaking the at-least-once guarantee users expect fromqueue.type: persisted.With this API, the Kafka input (companion PR: logstash-plugins/logstash-integration-kafka — adds an opt-in
commit_after_pq_fsyncoption) can callcheckpoint!after pushing a poll batch and only then commit offsets, closing the window. Other acknowledging inputs can adopt the same pattern.Checklist
Author's Checklist
QueueTest(fsync-up-to-last-write + no-op/idempotency),JrubyAckedWriteClientExtTest(end-to-end fsync via checkpoint.head),JrubyMemoryWriteClientExtTest,JRubyWrappedWriteClientExtTest(delegation)How to test this PR locally
Full
:logstash-core:javaTestspasses.🤖 Generated with Claude Code