Skip to content

queue: add try_evict_lowest API for priority-based queue eviction - #89

Merged
Connor1996 merged 3 commits into
tikv:masterfrom
mittalrishabh:master
Feb 26, 2026
Merged

queue: add try_evict_lowest API for priority-based queue eviction#89
Connor1996 merged 3 commits into
tikv:masterfrom
mittalrishabh:master

Conversation

@mittalrishabh

@mittalrishabh mittalrishabh commented Feb 19, 2026

Copy link
Copy Markdown
Member

issue # tikv/tikv#19386
Add eviction support to the priority queue so that when a queue is full, a higher-priority incoming task can evict the lowest-priority queued task.

The API is exposed through QueueCore, TaskInjector (priority and outer), and Remote. Only priority queues support eviction; other queue types return None.

Summary by CodeRabbit

  • New Features

    • Priority queues can now evict the lowest-priority task when a strictly higher-priority task arrives, improving scheduling under load.
  • Tests

    • Added comprehensive eviction tests covering higher-priority eviction, equal/non-strict priority cases, empty-queue behavior, and concurrent stress scenarios to validate correctness.

Add eviction support to the priority queue so that when a queue is full,
a higher-priority incoming task can evict the lowest-priority queued task.

The API is exposed through QueueCore, TaskInjector (priority and outer),
and Remote. Only priority queues support eviction; other queue types
return None.

Signed-off-by: Rishabh Mittal <rishabh_mittal@airbnb.com>
Co-Authored-By: Rishabh Mittal <rishabh_mittal@airbnb.com>
Signed-off-by: rishabh mittal <mittalrishabh@gmail.com>
@coderabbitai

coderabbitai Bot commented Feb 19, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉


📝 Walkthrough

Walkthrough

Adds a priority-based eviction API and implementation: tightened generic bounds to require 'static on Remote<T> and TaskInjector<T>, a new eviction call path through injector -> queue core -> priority queue, and tests exercising eviction and concurrency edge cases.

Changes

Cohort / File(s) Summary
Remote Handle API
src/pool/spawn.rs
Tightened impl bound to T: TaskCell + Send + 'static. Added pub fn try_evict_lowest(&self, incoming_priority: u64) -> Option<T> that returns None on shutdown and otherwise delegates to the core/global queue eviction.
TaskInjector Facade
src/queue.rs
Tightened impl bound to T: TaskCell + Send + 'static. Added pub fn try_evict_lowest(&self, incoming_priority: u64) -> Option<T> which delegates to the inner injector; returns None for non-priority injectors.
Priority Queue Core & Tests
src/queue/priority.rs
Tightened generic bound to include 'static. Added pub fn try_evict(&self, incoming_priority: u64) -> Option<T> on TaskInjector<T> and an internal fn try_evict_for_priority(&self, incoming_priority: u64) -> Result<Option<T>, ()> on QueueCore implementing race-resilient, strictly-higher-priority eviction semantics. Added comprehensive tests covering empty queue, successful eviction, non-eviction when not strictly higher, equal-priority behavior, and concurrent push/evict stress scenarios.

Sequence Diagram

sequenceDiagram
    participant Client as Incoming Task
    participant Remote as Remote<T>
    participant Injector as TaskInjector<T>
    participant QueueCore as QueueCore<T>
    participant PrioQ as Priority Queue

    Client->>Remote: try_evict_lowest(incoming_priority)
    Remote->>Injector: try_evict_lowest(incoming_priority)
    Injector->>QueueCore: try_evict(incoming_priority)
    QueueCore->>PrioQ: inspect back entry (lowest priority)
    alt queue empty
        PrioQ-->>QueueCore: empty
        QueueCore-->>Injector: Ok(None)
    else has entries
        PrioQ-->>QueueCore: back_priority
        alt incoming_priority > back_priority
            PrioQ->>PrioQ: remove back entry (race-resilient)
            PrioQ-->>QueueCore: Task
            QueueCore-->>Injector: Ok(Some(Task))
        else incoming_priority <= back_priority
            PrioQ-->>QueueCore: not strictly higher
            QueueCore-->>Injector: Err(())
        end
    end
    Injector-->>Remote: Option<T>
    Remote-->>Client: Option<T>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hop in queues and test the plight,
I nudge the weakest when the stakes are high,
With careful bounds and nimble paws,
I evict the low and keep the ties,
A rabbit's cheer for cleaner skies. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding a new try_evict_lowest API for priority-based queue eviction, which aligns with the PR objectives and file modifications across all three modified files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/queue.rs (1)

153-157: ⚠️ Potential issue | 🟠 Major

Pre-existing CI pipeline failure blocks merge

The pipeline reports:

[error] 153-157: This 'impl Default for QueueType' can be derived. Replace with #[derive(Default)] and mark the default variant.

This is pre-existing code but is failing CI and blocking the PR. Fix by deriving Default and annotating the unit variant:

🛠️ Proposed fix
+#[derive(Default)]
 pub enum QueueType {
+    #[default]
     SingleLevel,
     Multilevel(multilevel::Builder),
     Priority(priority::Builder),
 }
-
-impl Default for QueueType {
-    fn default() -> QueueType {
-        QueueType::SingleLevel
-    }
-}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/queue.rs` around lines 153 - 157, Replace the manual impl Default for
QueueType with a derived Default: remove the impl Default block and add
#[derive(Default)] to the QueueType enum declaration, and mark the unit variant
SingleLevel with #[default] so the compiler can derive the default; this
replaces the impl Default for QueueType { fn default() -> QueueType {
QueueType::SingleLevel } } with a derived default on the enum.
src/pool/spawn.rs (1)

219-222: ⚠️ Potential issue | 🟠 Major

Pre-existing CI pipeline failures block merge

The pipeline reports:

[error] 219-219: trait AssertSync is never used.
[error] 221-221: trait AssertSend is never used.

These are intentional compile-time assertions (the accompanying comment confirms this). Suppress the dead-code lint with #[allow(dead_code)]:

🛠️ Proposed fix
+#[allow(dead_code)]
 trait AssertSync: Sync {}
 impl<T: Send> AssertSync for Remote<T> {}
+#[allow(dead_code)]
 trait AssertSend: Send {}
 impl<T: Send> AssertSend for Remote<T> {}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pool/spawn.rs` around lines 219 - 222, The unused-trait lint is firing
for the intentional compile-time assertion traits AssertSync and AssertSend; add
#[allow(dead_code)] above the trait declarations for AssertSync and AssertSend
(the impls for Remote<T>) so the dead-code lint is suppressed while preserving
the compile-time check of Remote<T> implementing Send/Sync; locate the trait
declarations named AssertSync and AssertSend and annotate them with
#[allow(dead_code)].
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/queue/priority.rs`:
- Around line 540-594: The stress test test_evict_concurrent_stress only uses
incoming_priority = 0 for all evictors and therefore never exercises the TOCTOU
race where a back entry with priority ≤ incoming_priority can be exposed by a
concurrent evictor; add a complementary test (e.g.,
test_evict_concurrent_mixed_priorities) that pushes tasks with a known priority
(e.g., 30) via MockTask and spawns multiple evictor threads calling try_evict
with different incoming_priority values (e.g., 25 and 28) so evictors can race
on entries around the threshold; in each evictor validate that any returned task
has priority > the caller's incoming_priority (use MockTask::priority or key().0
to check) and also assert total pushed == evicted + remaining to keep
conservation checks (use injector, try_evict, evicted_count, and
injector.queue.pop() like the original test).
- Around line 152-171: The try_evict_for_priority check-then-pop has a TOCTOU
bug: after reading self.pq.back() you must atomically remove that specific entry
instead of calling pop_back() which may remove a different (higher-priority)
entry; change the logic in try_evict_for_priority to call Entry::remove() on the
Entry returned by self.pq.back() (and if it returns true then call
entry.value().take() to extract the task and return Ok(Some(task)), if remove()
returns false return Ok(None)), and stop using pop_back() for the validated
entry so you never accidentally evict a different key than the one you
inspected.

---

Outside diff comments:
In `@src/pool/spawn.rs`:
- Around line 219-222: The unused-trait lint is firing for the intentional
compile-time assertion traits AssertSync and AssertSend; add #[allow(dead_code)]
above the trait declarations for AssertSync and AssertSend (the impls for
Remote<T>) so the dead-code lint is suppressed while preserving the compile-time
check of Remote<T> implementing Send/Sync; locate the trait declarations named
AssertSync and AssertSend and annotate them with #[allow(dead_code)].

In `@src/queue.rs`:
- Around line 153-157: Replace the manual impl Default for QueueType with a
derived Default: remove the impl Default block and add #[derive(Default)] to the
QueueType enum declaration, and mark the unit variant SingleLevel with
#[default] so the compiler can derive the default; this replaces the impl
Default for QueueType { fn default() -> QueueType { QueueType::SingleLevel } }
with a derived default on the enum.

Comment thread src/queue/priority.rs
Comment thread src/queue/priority.rs
Signed-off-by: rishabh mittal <mittalrishabh@gmail.com>
Signed-off-by: rishabh mittal <mittalrishabh@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/queue.rs (1)

49-49: The 'static bound is redundant since TaskCell: 'static.

TaskCell already has a 'static supertrait bound (line 22). The added + 'static on the impl is harmless but unnecessary — the compiler infers it from T: TaskCell.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/queue.rs` at line 49, The impl for TaskInjector redundantly repeats the
'static bound; update the impl header so the generic constraint relies on the
existing TaskCell:'static supertrait instead of duplicating it. Specifically,
edit the impl declaration for TaskInjector<T> (currently declared as impl<T:
TaskCell + Send + 'static> TaskInjector<T>) to remove the extra + 'static so it
becomes impl<T: TaskCell + Send> TaskInjector<T>, leaving the TaskCell trait
definition unchanged.
src/queue/priority.rs (2)

129-145: Consider defensive handling in pop() to avoid panic if eviction races with consumption.

Line 144 uses .take().unwrap() which will panic if take() returns None. While crossbeam-skiplist should prevent pop_front() from returning an already-removed entry, a defensive .take()? (or filtering with .and_then()) would make pop() resilient to any unexpected interaction with the new eviction path.

🛡️ Defensive take
     pub fn pop(&self) -> Option<Pop<T>> {
         fn into_pop<T>(mut t: T) -> Pop<T>
         where
             T: TaskCell,
         {
             let schedule_time = t.mut_extras().schedule_time.unwrap();
             Pop {
                 task_cell: t,
                 schedule_time,
                 from_local: false,
             }
         }
 
         self.pq
             .pop_front()
-            .map(|e| into_pop(e.value().take().unwrap()))
+            .and_then(|e| e.value().take().map(into_pop))
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/queue/priority.rs` around lines 129 - 145, The pop() implementation
currently calls .take().unwrap() inside map which can panic if an entry was
evicted concurrently; update pop() (and the helper into_pop<T>) to handle a None
from take() defensively by using .and_then() / .map(|e|
e.value().take()).and_then(...) or using .take()? so that if take() returns None
the whole operation returns None rather than panicking; ensure the returned
Option<Pop<T>> remains None on a failed take and keep references to Pop,
TaskCell, into_pop, and self.pq.pop_front() to locate the change.

43-43: Same note as src/queue.rs: the 'static bound is already implied by TaskCell: 'static (line 22 of src/queue.rs).

Redundant but not harmful. Consistent with the outer TaskInjector impl, so fine to keep for symmetry.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/queue/priority.rs` at line 43, The explicit 'static bound on the generic
T (the "T: TaskCell + Send + 'static" in the TaskInjector impl in priority.rs)
is redundant because TaskCell already implies 'static; remove the trailing
"'static" from that generic bound so it reads "T: TaskCell + Send" to match the
outer TaskInjector impl and avoid redundant constraints.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/queue.rs`:
- Line 49: The impl for TaskInjector redundantly repeats the 'static bound;
update the impl header so the generic constraint relies on the existing
TaskCell:'static supertrait instead of duplicating it. Specifically, edit the
impl declaration for TaskInjector<T> (currently declared as impl<T: TaskCell +
Send + 'static> TaskInjector<T>) to remove the extra + 'static so it becomes
impl<T: TaskCell + Send> TaskInjector<T>, leaving the TaskCell trait definition
unchanged.

In `@src/queue/priority.rs`:
- Around line 129-145: The pop() implementation currently calls .take().unwrap()
inside map which can panic if an entry was evicted concurrently; update pop()
(and the helper into_pop<T>) to handle a None from take() defensively by using
.and_then() / .map(|e| e.value().take()).and_then(...) or using .take()? so that
if take() returns None the whole operation returns None rather than panicking;
ensure the returned Option<Pop<T>> remains None on a failed take and keep
references to Pop, TaskCell, into_pop, and self.pq.pop_front() to locate the
change.
- Line 43: The explicit 'static bound on the generic T (the "T: TaskCell + Send
+ 'static" in the TaskInjector impl in priority.rs) is redundant because
TaskCell already implies 'static; remove the trailing "'static" from that
generic bound so it reads "T: TaskCell + Send" to match the outer TaskInjector
impl and avoid redundant constraints.

@v01dstar
v01dstar requested review from Connor1996 and glorv February 24, 2026 23:08

@v01dstar v01dstar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@glorv glorv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@mittalrishabh

Copy link
Copy Markdown
Member Author

@Connor1996 can you review it.

@Connor1996 Connor1996 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Connor1996
Connor1996 merged commit 9fcf102 into tikv:master Feb 26, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants