Skip to content

kqueue/epoll: make cancel() reliable when ops complete inline#132

Merged
sgerbino merged 1 commit intocppalliance:developfrom
mvandeberg:bug/cancel-race
Feb 12, 2026
Merged

kqueue/epoll: make cancel() reliable when ops complete inline#132
sgerbino merged 1 commit intocppalliance:developfrom
mvandeberg:bug/cancel-race

Conversation

@mvandeberg
Copy link
Contributor

@mvandeberg mvandeberg commented Feb 11, 2026

Add cancel_pending flags to descriptor_state so cancellation intent persists across speculative I/O completions, matching IOCP's CancelIoEx semantics. Removes the shutdown(SHUT_WR) workaround from fc73eee.

Summary by CodeRabbit

  • Bug Fixes
    • Cancellation requests now reliably take effect even when the target I/O operation isn't currently active.
    • Added pending-cancellation tracking so deferred cancels are applied when the next operation parks.
    • Read and write paths detect and apply pending cancels during retries and when parking.
    • Pending-cancellation flags are cleared on close to ensure a clean socket lifecycle.

@mvandeberg mvandeberg requested a review from sgerbino February 11, 2026 20:56
@coderabbitai
Copy link

coderabbitai bot commented Feb 11, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds per-descriptor deferred-cancellation flags and integrates them into epoll and kqueue read/write and cancel paths so cancellations requested when an operation isn't currently parked are recorded and applied when the next operation parks or advances.

Changes

Cohort / File(s) Summary
Descriptor state (epoll & kqueue)
src/corosio/src/detail/epoll/op.hpp, src/corosio/src/detail/kqueue/op.hpp
Adds bool read_cancel_pending = false; and bool write_cancel_pending = false; to descriptor_state for deferred cancellation tracking; documents IOCP-like semantics.
Epoll socket logic
src/corosio/src/detail/epoll/sockets.cpp
When registering/parking ops, checks and clears read_cancel_pending/write_cancel_pending and marks op cancelled if set. cancel()/cancel_single_op() set pending flags when target op isn't claimed. close_socket() clears flags.
Kqueue socket logic
src/corosio/src/detail/kqueue/sockets.cpp
Adds pending-cancel checks in do_read_io/do_write_io; cancel()/cancel_single_op() set flags when appropriate; flags cleared on close or when applied.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Descriptor as descriptor_state
  participant Op
  participant Scheduler

  Caller->>Descriptor: cancel(read/write)  -- op not parked
  Descriptor-->>Descriptor: set read_cancel_pending / write_cancel_pending
  Note right of Descriptor: pending flag recorded
  Caller->>Op: submit read/write op
  Op->>Descriptor: park (desc_state_.read_op / write_op = &op)
  Descriptor-->>Op: detect pending flag, clear flag
  Op->>Op: mark cancelled
  Op->>Scheduler: post cancelled op / signal completion
  Scheduler->>Caller: resume with cancelled result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

I twitch my whiskers, flags set neat,
A pending cancel held on repeat,
When ops next park I hop and clear,
Post the note, then bound with cheer,
A rabbit's wink — async made sweet 🐇✨

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding cancel_pending flags to make cancel() reliable when operations complete inline, which is the core purpose of this changeset across both kqueue and epoll implementations.

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

✨ Finishing touches
🧪 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.

@codecov
Copy link

codecov bot commented Feb 11, 2026

Codecov Report

❌ Patch coverage is 85.71429% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.21%. Comparing base (08b2663) to head (ca7de00).
⚠️ Report is 3 commits behind head on develop.

Files with missing lines Patch % Lines
src/corosio/src/detail/epoll/sockets.cpp 85.71% 3 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop     #132      +/-   ##
===========================================
+ Coverage    80.81%   81.21%   +0.39%     
===========================================
  Files           64       64              
  Lines         5557     5685     +128     
===========================================
+ Hits          4491     4617     +126     
- Misses        1066     1068       +2     
Files with missing lines Coverage Δ
src/corosio/src/detail/epoll/op.hpp 81.44% <ø> (ø)
src/corosio/src/detail/epoll/sockets.hpp 91.66% <ø> (ø)
src/corosio/src/detail/epoll/sockets.cpp 83.29% <85.71%> (+0.72%) ⬆️

... and 11 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 08b2663...ca7de00. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@cppalliance-bot
Copy link

cppalliance-bot commented Feb 11, 2026

An automated preview of the documentation is available at https://132.corosio.prtest3.cppalliance.org/index.html

If more commits are pushed to the pull request, the docs will rebuild at the same URL.

2026-02-12 17:16:10 UTC

@cppalliance-bot
Copy link

cppalliance-bot commented Feb 11, 2026

GCOVR code coverage report https://132.corosio.prtest3.cppalliance.org/gcovr/index.html
LCOV code coverage report https://132.corosio.prtest3.cppalliance.org/genhtml/index.html
Coverage Diff Report https://132.corosio.prtest3.cppalliance.org/diff-report/index.html

Build time: 2026-02-11 23:16:25 UTC

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/corosio/src/detail/epoll/sockets.cpp`:
- Around line 270-276: The epoll cancel_pending code paths clear the pending
flag and post the op without marking it cancelled, causing EOF instead of
cancelled for a subsequent operation; update each cancel_pending consumption
site that uses desc_state_.read_cancel_pending /
desc_state_.write_cancel_pending (the four occurrences around the read/write
cancel handling) to call op.request_cancel() (or set op.cancelled.store(true,
...)) before svc_.post(&op) and svc_.work_finished() so the posted operation is
correctly marked cancelled (mirror the kqueue do_read_io behavior).
- Around line 705-712: The cancel path currently sets
desc_state_.read_cancel_pending/write_cancel_pending unconditionally (e.g., in
cancel() where it checks read_op != &rd_ and write_op != &wr_), causing stale
pending flags to leak to later operations; update the logic to only set the
pending flag when the operation is actually parked (guard with a check that
desc_state_.read_op != nullptr / desc_state_.write_op != nullptr before setting
read_cancel_pending/write_cancel_pending) and also clear any pending flags at
the start of new operations in read_some and write_some (after acquiring the
same mutex) so a previously set flag cannot spuriously cancel an unrelated
subsequent op; apply the same fix to cancel_single_op() and ensure the rd_/wr_
claim paths still exchange and preserve behavior.

In `@src/corosio/src/detail/kqueue/sockets.cpp`:
- Around line 713-720: The else branches unconditionally set
desc_state_.read_cancel_pending / desc_state_.write_cancel_pending whenever
read_op != &rd_ or write_op != &wr_, which can mark a cancel for a future
unrelated operation; instead only set those cancel_pending flags if there is an
active operation to cancel (i.e. when the operation slot actually holds a
different op), or clear/reset the flags at the start of a new operation; locate
the logic around desc_state_, compare read_op and write_op against rd_ and wr_
(and the rd_claimed/wr_claimed handling) and change the else paths to check that
an operation is present before setting read_cancel_pending/write_cancel_pending,
or ensure do_read_io/do_write_io clear the pending flags at operation start to
avoid consuming a stale flag.

@mvandeberg mvandeberg force-pushed the bug/cancel-race branch 4 times, most recently from 389e374 to c28a986 Compare February 11, 2026 23:00
Add cancel_pending flags to descriptor_state so cancellation intent
persists across speculative I/O completions, matching IOCP's CancelIoEx
semantics.

Symmetry
@sgerbino sgerbino merged commit 477e92f into cppalliance:develop Feb 12, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants