Add standalone DELETE operation with lossless dispatch lifecycle - #185
Merged
Conversation
signalDispatch() previously unparked the dispatch thread and then took dispatchLock to signal dispatchReady. The unpark had no effect on a thread inside Condition.await() (AQS re-parks on a spurious wake), so every completion on a Netty event loop still acquired dispatchLock. The dispatch task now parks its own thread at its three wait sites and signalDispatch() only unparks it. The existing double-checks plus the sticky unpark permit rule out lost wake-ups, and an interrupt still surfaces as InterruptedException so stop() behaves as before. The Condition constructor parameter is retained for extension compatibility but is unused. Also name the generator's no-progress back-off and correct the recycle path comment that still described a yield.
Per-thread context-switch sampling of the write/10KiB/T4 arms showed the generator's fixed 50 us park accounting for ~2.4 of the candidate's +2.5 voluntary switches per operation (~9.4K wake-ups per second per worker) while the driver's input queue was full. The dispatcher and event loops were unchanged against the release. The no-progress output wait now starts at 50 us and doubles per consecutive blocked iteration up to 1 ms, resetting on any progress. The drain interval is far longer than the cap, so a refilled queue is still picked up promptly. The recycle and retry-idle waits keep the fixed short park: they resolve at request-latency cadence, where a growing park would sit on the re-admission path.
With the input queue full the dispatcher frees one slot per completed operation, so the generator's output attempts alternate between one-op successes and refusals at request frequency. Resetting the back-off on any progress restarted the 50 us wait each time, leaving the generator at ~1.3 wake-ups per operation after the adaptive back-off landed. Only a fully accepted output now resets the wait; a partial acceptance holds it. At the 1 ms cap several slots accumulate per wake-up, so the generator admits a small batch each time it runs instead of one op.
A rate throttle refusing permits produced the same "nothing accepted" outcome as a full output queue, so the adaptive back-off grew to its 1 ms cap while waiting for permits that return every few hundred microseconds. At an 8K ops/s limit the generator woke ~600 times per second per worker and throughput collapsed to ~1.6K ops/s. Decide the wait in one place: a throttle refusal parks the fixed short interval and leaves the queue back-off untouched; only an output refusal with permits in hand grows it; acceptance returns immediately.
RateThrottle.tryAcquire(int) returns the schedule deficit as a negative count once its quota is exhausted, and its first call grants the whole request eagerly, so the second call under a rate limit returns roughly minus the batch size. Since 0d345d3 the generator asserts the permit count is within [0, pending]; that assertion turned every rate-limited run into a single burst followed by a stopped generator. The release never asserted and simply saw n <= 0. Clamp each throttle's answer to zero before the next throttle and the range check. The regression test drives the recycle path with a throttle returning the deficit and requires the generator to survive and resume.
Main now fails compilation on Error Prone warnings. Give the verification ledger's Presence an explicit byte code instead of relying on ordinal, raise the pending-phase sentinel on demand instead of caching a static Throwable, and parenthesize mixed boolean operators.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Standalone DELETE operation
Lossless dispatch lifecycle
Dispatch-path remediation
The lifecycle accounting introduced a 4-5% WRITE throughput regression against 5.14.2 through
dispatchLockcontention and cross-thread hand-offs on the critical path. The remediation commits recover it:Thread.yield()spin with an adaptive back-off that holds on partial output acceptance and excludes throttle waitsRateThrottle.tryAcquire(int)so an exhausted quota is treated as a refusal rather than a negative permit countAn 18-run balanced Williams crossover campaign confirmed throughput parity with the release while reducing client CPU utilization by 37% and voluntary context switches by 8%.