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

mosquito / caio / 30896930505

04 Aug 2026 09:34AM UTC coverage: 92.657% (+0.4%) from 92.291%
30896930505

Pull #72

github

web-flow
Merge fa9d465c9 into 6d03e42c8
Pull Request #72: Make all four backends safe under free-threaded CPython

20 of 20 new or added lines in 1 file covered. (100.0%)

75 existing lines in 3 files now uncovered.

429 of 463 relevant lines covered (92.66%)

12.61 hits per line

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

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

4

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

UNCOV
9
    def _create_context(self, max_requests, **kwargs):
7✔
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.
UNCOV
13
        kwargs.setdefault("sqpoll", SQPOLL_ALLOWED)
7✔
UNCOV
14
        context = super()._create_context(max_requests, **kwargs)
7✔
UNCOV
15
        self.loop.add_reader(context.fileno, self._on_read_event)
7✔
UNCOV
16
        self._flush_scheduled = False
7✔
UNCOV
17
        return context
7✔
18

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

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

UNCOV
27
    def _on_submitted(self):
7✔
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).
UNCOV
37
        if not self.deferred or self.context.sqpoll:
7✔
UNCOV
38
            self.context.flush()
7✔
UNCOV
39
            return
7✔
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.
UNCOV
47
        if not self._flush_scheduled:
7✔
UNCOV
48
            self._flush_scheduled = True
7✔
UNCOV
49
            self.loop.call_soon(self._deferred_flush)
7✔
50

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

UNCOV
55
    def _on_read_event(self):
7✔
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:
7✔
UNCOV
71
            self.context.poll()
7✔
72
        except BlockingIOError:
×
73
            pass
×
UNCOV
74
        while self.context.process_events():
7✔
UNCOV
75
            pass
7✔
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