iomgr: dedicated sync-IO reactor for blocking drive I/O (iomgr::sync_wait)#126
Merged
Conversation
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## dev/v13.x #126 +/- ##
============================================
Coverage ? 45.42%
============================================
Files ? 35
Lines ? 1409
Branches ? 628
============================================
Hits ? 640
Misses ? 336
Partials ? 433 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
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.
Why
v13 removed fibers (
88e6f8d,d4f8aab) and, with them, the nativeDriveInterface::sync_write/sync_readthat let a reactor issue a blocking drive op by yielding the calling fiber while the reactor kept reaping
completions. The public drive surface is now async-only (
async_* -> io_op,co_await).Cold-path callers (superblock / chunk-info / metadata reads & writes during boot, checkpoint, recovery) still
need a synchronous drive op. Building that on a
std::futurecannot work on a reactor thread: the reactorblocks in
fut.get()and can never reap the io_uring completion that would unblock it → deadlock. (homestorepapered over this with a
drive_bridge::sync_waitthatHS_REL_ASSERT(!am_i_io_reactor())— which just movedthe problem onto callers and aborted whenever a blocking op was reached from a reactor.)
What
A single, internal sync-IO reactor plus a blocking primitive that is safe to call from any thread:
iomgr::sync_wait(io_op) -> io_result(io_op.hpp,drive.cpp) — runs the op on the dedicated syncreactor and blocks the caller until completion. Because the op is issued and reaped on a reactor that is
never itself a
sync_waitcaller, the caller blocking can never deadlock the reactor driving the completion.Safe both off-reactor and on-reactor. (Debug-asserts it is not called on the sync reactor.)
IOManager::start_sync_reactor()(iomgr.cpp) — creates theiomgr_sync_ioreactor at the end ofstart();IOManager::sync_io_reactor()accessor +m_sync_reactormember (iomgr.hpp). It is a userreactor, drive-capable like any other (the drive interface has
all_ioscope), kept off the worker pool.match_regex()excludes the sync reactor from everyreactor_regex(iomgr.cpp). Bothmessage broadcasts (
run_on_*) and scoped iodev/timer distribution (generic_interface::add_io_device) routethrough
match_regex, so this keeps user closures and timer fds off it: noall_user/all_iobroadcast canrun a closure there (which could itself call
sync_wait→ self-deadlock), and broadcast counts (e.g.all_user) stay correct. It is addressed only directly by pointer — thesync_waitdispatch and the shutdownstop.
m_yet_to_stop_nreactorsat start but is hidden from theall_iostop broadcast, so
IOManager::stop()stops it explicitly by pointer before the broadcast (otherwise shutdownwould hang waiting on it).
Teardown robustness fix (surfaced by the above)
IOReactorEPoll::remove_iodev_implnow toleratesEPOLL_CTL_DEL -> ENOENT(reactor_epoll.cpp). Areactor does not track which iodevs it actually
ADDed;IOInterface::on_reactor_stopwalks the whole iodevmap and removes each regardless of scope, so it can
DELa scoped fd (e.g. anall_user/all_workerglobaltimer) it never added, or one a prior cancel already removed.
ENOENTmeans "already absent" — the desiredpost-state of a remove — so it returns success instead of
LOGDFATAL/abort. (Pre-existing asymmetry, onlyreachable once blocking I/O stopped aborting first; reasonable to split into its own commit.)
Tests
test_sync_io(TestSyncIo-Epoll):off_reactor,on_reactor(the defining case — a blocking syncop issued from a worker reactor completes, no deadlock),
concurrent_from_reactors.ctestsuite green;test_sync_io+test_driveclean under TSAN (Sanitized-thread).Reviewer notes
drive_bridge::sync_waitbecomes athin delegate to
iomgr::sync_wait(dropping its!am_i_io_reactor()assert), and homestore's interimBlockingIoExecutoris deleted.io_op.hpp(+9),iomgr.hpp(+11),drive.cpp(+28),iomgr.cpp(+33),reactor_epoll.cpp(+11),test/CMakeLists.txt(+5),test/test_sync_io.cpp(+141).