• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mosquito / caio / 30864274438

04 Aug 2026 12:02AM UTC coverage: 90.308% (-1.0%) from 91.304%
30864274438

Pull #71

github

web-flow
Merge 483f3e458 into 825b3ef0a
Pull Request #71: Add SQPOLL support (on by default when available) + deferred submission batching

6 of 46 new or added lines in 3 files covered. (13.04%)

50 existing lines in 4 files now uncovered.

410 of 454 relevant lines covered (90.31%)

10.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.41
/caio/linux_uring_asyncio.py
1
from .asyncio_base import AsyncioContextBase
14✔
2
from .linux_uring import SQPOLL_ALLOWED, Context, Operation
14✔
3

4

UNCOV
5
class AsyncioContext(AsyncioContextBase):
5✔
UNCOV
6
    OPERATION_CLASS = Operation
5✔
UNCOV
7
    CONTEXT_CLASS = Context
5✔
8

NEW
9
    def _create_context(self, max_requests, **kwargs):
5✔
10
        # SQPOLL_ALLOWED reflects a real kernel/capability probe done once
11
        # at import time (see linux_uring.c) - default to it rather than
12
        # to sqpoll=False, but let an explicit caller kwarg win either way.
NEW
13
        kwargs.setdefault("sqpoll", SQPOLL_ALLOWED)
5✔
NEW
14
        context = super()._create_context(max_requests, **kwargs)
5✔
UNCOV
15
        self.loop.add_reader(context.fileno, self._on_read_event)
5✔
NEW
16
        self._flush_scheduled = False
5✔
UNCOV
17
        return context
5✔
18

UNCOV
19
    def _on_done(self, future, result):
5✔
UNCOV
20
        if future.done():
5✔
UNCOV
21
            return
5✔
UNCOV
22
        future.set_result(True)
5✔
23

UNCOV
24
    def _destroy_context(self):
5✔
UNCOV
25
        self.loop.remove_reader(self.context.fileno)
5✔
26

UNCOV
27
    def _on_submitted(self):
5✔
28
        # Non-SQPOLL flush() completes page-cache ops inline (drain_cq fires
29
        # futures before `await future` even suspends); SQPOLL just wakes
30
        # the kernel thread if it's asleep - and only when it's actually
31
        # gone idle, so eager per-op flush() is already close to syscall-
32
        # free there. Batching doesn't reduce a cost that's mostly already
33
        # gone - measured no meaningful difference either way - so ignore
34
        # deferred whenever the kernel actually negotiated SQPOLL (not
35
        # just what was requested - EPERM/EINVAL can silently fall back to
36
        # a plain ring, see linux_uring.c's flag_table_sqpoll).
NEW
37
        if not self.deferred or self.context.sqpoll:
5✔
NEW
38
            self.context.flush()
5✔
NEW
39
            return
5✔
40
        # Nothing between submit()'s isinstance check and here ever awaits,
41
        # so N concurrently-scheduled submits used to each flush their own
42
        # single SQE back to back - no batching despite N being ready at
43
        # once. call_soon() defers to the next _run_once() pass, after all
44
        # of them have written their SQE, so one flush() covers the whole
45
        # batch - at the cost of one extra event-loop round-trip for a lone
46
        # unbatched op.
NEW
47
        if not self._flush_scheduled:
×
NEW
48
            self._flush_scheduled = True
×
NEW
49
            self.loop.call_soon(self._deferred_flush)
×
50

NEW
51
    def _deferred_flush(self):
5✔
NEW
52
        self._flush_scheduled = False
×
UNCOV
53
        self.context.flush()
×
54

UNCOV
55
    def _on_read_event(self):
5✔
56
        """Handle completions signalled via the eventfd."""
57
        # poll() raises BlockingIOError whenever the eventfd's counter
58
        # reads as zero - possible even right after epoll reported this fd
59
        # readable (e.g. the kernel coalesces multiple completions into one
60
        # counter increment, and the ones beyond the first can still be
61
        # landing in the CQ ring at the moment poll() runs). Letting that
62
        # exception escape used to skip process_events() entirely for this
63
        # wakeup - draining is independent of the eventfd count (it reads
64
        # the CQ ring directly), so a stale/absent counter is never a
65
        # reason to skip it, only silently stranding whatever was already
66
        # completed with no other event left to wake this Context up
67
        # again. Confirmed to actually hang a real (non-tmpfs) benchmark
68
        # run this way - inline tmpfs completions never exercise this path
69
        # at all, since they never reach the eventfd in the first place.
UNCOV
70
        try:
5✔
UNCOV
71
            self.context.poll()
5✔
72
        except BlockingIOError:
×
73
            pass
×
UNCOV
74
        while self.context.process_events():
5✔
UNCOV
75
            pass
5✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc