1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
//! Synchronous bridge for driving durable-store futures to completion.
//!
//! # Why a hand-rolled executor and why it cannot deadlock
//!
//! The runtime channel API ([`crate::channel::ChannelHandle::publish`] and
//! [`crate::channel::ChannelHandle::flush`]) is synchronous: it is called from a
//! beamr scheduler worker thread that drives a connection process. The durable
//! storage surface ([`crate::durability::DurableStore`]) is `async`. This helper
//! polls a single store future to completion on the *calling* thread without
//! spawning a runtime or a thread.
//!
//! It is deadlock-free **because the current store backend is synchronous
//! underneath**: the vendored haematite [`crate::durability::HaematiteStore`]
//! completes every `append`/`cas`/`read`/`flush` under an in-memory lock and
//! returns `Poll::Ready` on the first poll — there is no external I/O await, no
//! cross-task channel, and nothing that hands control to another task that would
//! need *this* thread to make progress. Concretely:
//!
//! * No runtime is created, so no other task contends for an executor.
//! * No thread is spawned and no lock is held across the poll by this helper, so
//! it cannot form a wait cycle with another thread.
//! * The future returns immediately, so the calling scheduler worker is not
//! blocked for any meaningful time.
//!
//! If a future-yielding, real-I/O store backend is ever introduced (the
//! downstream mock -> on-disk-haematite swap), this synchronous bridge must be
//! replaced with a real executor on a dedicated I/O thread.
//!
//! # Bounded, loud failure on a suspending future
//!
//! A synchronous-underneath future returns `Poll::Ready` on the FIRST poll. To
//! make the unreachable suspending-backend case fail loudly instead of spinning
//! forever, the poll loop is **bounded** to [`MAX_POLLS`] (a tiny margin above
//! the single poll a correct future needs). If the future is still `Pending`
//! after that bound, [`block_on`] returns [`BridgeError::DidNotComplete`] rather
//! than busy-yielding indefinitely: a still-pending future here is a contract
//! violation (a suspending `DurableStore` backend was wired without swapping in
//! a real executor), and a loud, bounded error is far safer than a silent
//! worker-starving hang. Production callers surface that error up their normal
//! error path (no panic in production code). The happy path is unchanged:
//! `Ready` on the first poll returns immediately with zero extra overhead.
use Future;
use pin;
use Arc;
use ;
/// Maximum number of polls [`block_on`] performs before failing loudly.
///
/// A synchronous-underneath store future completes in exactly one poll; the
/// margin tolerates a trivially-chained adapter future while still bounding the
/// loop so a genuinely-suspending future fails fast instead of spinning.
const MAX_POLLS: usize = 8;
/// Failure raised when a future driven by [`block_on`] does not complete
/// synchronously within the bounded poll budget.
/// Drives `future` to completion on the calling thread.
///
/// See the module documentation for the deadlock-freedom contract: this is only
/// sound for store backends whose futures complete synchronously without
/// awaiting external I/O.
///
/// # Errors
///
/// Returns [`BridgeError::DidNotComplete`] if `future` is still `Poll::Pending`
/// after [`MAX_POLLS`] polls. This never happens for a synchronous-underneath
/// store backend (which completes on the first poll); it signals that a
/// suspending backend was introduced and requires a real executor on a
/// dedicated I/O thread (see the module docs).
;