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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
//! # myelin
//!
//! Define async service APIs as traits, communicate over channels.
//!
//! The trait definition is the single source of truth. A proc macro generates
//! the channel plumbing: request/response enums, client stubs, and server
//! dispatch. Transport and serialization are pluggable — local in-process
//! channels just move `Send` structs; cross-boundary transports serialize
//! transparently.
//!
//! ## Transports
//!
//! - [`ClientTransport`] — client side: make a request, get a response.
//! - [`ServerTransport`] — server side: receive requests, send responses.
//!
//! Implementations:
//! - `transport_tokio` (feature `tokio`) — tokio mpsc + oneshot, local, no serialization.
//! - `transport_smol` (feature `smol`) — async-channel mpsc + bounded(1) reply
//! channel, local, no serialization.
//! - `transport_embassy` (feature `embassy`) — embassy static channels + signals.
//! - `transport_postcard` (feature `postcard`) — postcard serialization over any
//! sync `Read + Write` stream, length-prefix framing. Internally wraps
//! the sync I/O in [`io::BlockingIo`] so the async transport stack runs
//! each read/write inline — suitable for stdio-style binaries driven
//! by a trivial [`BlockOn`].
//!
//! ## Async I/O
//!
//! The stream transport ([`stream::StreamTransport`]) operates on
//! [`io::AsyncBytesRead`] / [`io::AsyncBytesWrite`] — myelin's own
//! runtime-neutral async byte-stream traits (just `read_exact`,
//! `write_all`, `flush`). Bring your own reader/writer by implementing
//! those traits, or use one of the feature-gated adapters:
//!
//! - [`io::BlockingIo`] — wrap any sync `std::io::Read`/`Write`. The
//! async methods resolve inline; used by `transport_postcard` for
//! stdio transport.
//! - [`io::futures_io`] (feature `futures-io`) — adapt any type bounded
//! by `futures_io::AsyncRead` / `AsyncWrite`. Covers smol's
//! `smol::Async<T>`, async-std, and anything in the futures-io
//! ecosystem.
//! - [`io::tokio_io`] (feature `tokio-io`) — adapt `tokio::io::AsyncRead`
//! / `AsyncWrite`. Independent of the `tokio` feature (channels-only
//! transport).
//!
//! Inside myelin core, shared access to a reader/writer between
//! concurrent async tasks is mediated by a small single-waiter async
//! mutex, [`io::LocalLock`]. It replaces the old `RefCell`-based
//! scheme (unsound across `.await`) and is cancel-safe.
//!
//! ## Duplex transport
//!
//! For peers that both call *and* serve over one byte stream, use
//! [`stream::DuplexStreamTransport`]. It owns one `(reader, writer)`
//! pair and vends:
//!
//! - [`stream::DuplexClientHalf`] — `impl ClientTransport` per API
//! that this peer calls into.
//! - [`stream::DuplexServerHalf`] — `impl ServerTransport` per API
//! that this peer serves.
//! - [`stream::DuplexPump`] — a `run()` future the user's runtime
//! spawns; it drives the reader and demultiplexes frames into
//! either the slot router (responses) or a per-api_id server inbox
//! (requests).
//!
//! Wire format inside each framed payload: `[u8 kind][u16 api_id LE]
//! [u8 slot_id][codec bytes]`. `kind` is `0` (request) or `1`
//! (response); `api_id` identifies the API; `slot_id` is the caller's
//! echo-back value. See [`stream::duplex`] for details.
//!
//! Example (sketch, full example in `tests/duplex_smol_integration.rs`):
//!
//! ```ignore
//! use myelin::io::futures_io::{FuturesIoReader, FuturesIoWriter};
//! use myelin::stream::{DuplexStreamTransport, LengthPrefixed, PostcardCodec};
//!
//! type Dx<R, W> = DuplexStreamTransport<R, W, LengthPrefixed, PostcardCodec, 8, 512>;
//!
//! # fn main() {
//! # let (r, w): (FuturesIoReader<smol::io::Empty>, FuturesIoWriter<smol::io::Sink>) = panic!();
//! let dx: Dx<_, _> = Dx::new(r, w);
//! let server = dx.server_half::<u32, u32>(0x0001);
//! let client = dx.client_half::<u32, u32>(0x0002);
//! let (pump, _handle) = dx.split();
//! smol::block_on(async {
//! // Run pump concurrently with your server loop and client calls.
//! let _ = futures_lite::future::zip(pump.run(), async { /* ... */ }).await;
//! });
//! # }
//! ```
//!
//! ## Cancel Safety
//!
//! All myelin transports provide the following cancel safety guarantees:
//!
//! ### Cancelling a client call is always safe
//!
//! A client's `call()` future can be dropped at any `.await` point without
//! corrupting the transport or leaking state.
//!
//! - **Dropped before the request is sent:** No effect. The request was never
//! enqueued and no server-side resources are consumed.
//!
//! - **Dropped after the request is sent, before the reply is received:** The
//! server will still process the request and produce a response, but that
//! response is discarded. This means the server does *wasted work*, but
//! there is no protocol corruption, no leaked memory, and no poisoned state.
//! The client can immediately make another call.
//!
//! ### Cancelling a server task affects in-flight clients
//!
//! If the server task/thread is cancelled or shut down, clients with in-flight
//! requests will observe a transport-specific outcome:
//!
//! - **Tokio:** The client receives a `ChannelClosed` error (the mpsc/oneshot
//! senders are dropped).
//! - **Embassy:** The client's `Signal::wait()` will never complete — the client
//! hangs. Avoid cancelling embassy server tasks while clients are in-flight.
//! - **PostcardStream:** The underlying I/O stream is closed, producing an I/O
//! error on the client side.
//!
//! ### Summary
//!
//! | Scenario | Result |
//! |---|---|
//! | Client cancelled before send | Clean, no effect |
//! | Client cancelled after send | Server does wasted work, reply discarded |
//! | Server cancelled | In-flight clients get an error (tokio/postcard) or hang (embassy) |
pub use BlockOn;
pub use ;
pub use ;
/// Generate channel-API plumbing from an async trait definition.
///
/// See the [`myelin_macros::service`] documentation for the full input
/// contract and emitted items.
pub use service;
// Re-export paste for use by compose_service! and #[myelin::service] macros.
//
// These two lines re-export, under the name `paste`:
// - the `paste!` *macro* (in the macro namespace), so that existing
// `$crate::paste!` invocations in `compose_service!` continue to work.
// - the `paste` *crate* (in the type/module namespace), so that code emitted
// by `#[myelin::service]` can reference `::myelin::paste::paste!`.
// Macros and items live in separate namespaces, so the two `paste` names
// coexist without conflict.
pub use paste;
pub use paste;
// Re-export static_cell under the `embassy` feature so that code emitted by
// `#[myelin::service]` can refer to `::myelin::static_cell::StaticCell`
// without the consumer having to depend on (or re-export) `static_cell`
// themselves. The absolute path is part of the emitted `macro_rules!` body
// and matches the "no crate-override" policy of v1.
pub use static_cell;
/// Compose multiple service APIs into a single multiplexed service.
///
/// Generates combined request/response enums, a client that provides typed
/// accessors for each sub-service, dispatch serve functions, and convenience
/// type aliases for tokio/embassy transports.
///
/// Each component service must provide:
/// - `{Service}Request` / `{Service}Response` enums
/// - `{Service}Service` async trait and `{Service}ServiceSync` sync trait
/// - `{Service}Client` / `{Service}ClientSync` structs
/// - `{service}_dispatch` async function: `(&S, {Service}Request) -> {Service}Response`
/// - `{service}_dispatch_sync` sync function: `(&S, {Service}Request) -> {Service}Response`
///
/// The dispatch functions handle a single request→response, unlike the serve
/// functions which run an infinite loop.
///
/// # Usage
///
/// ```ignore
/// myelin::compose_service!(
/// Combined,
/// [Greeter, greeter_dispatch, greeter_dispatch_sync],
/// [Math, math_dispatch, math_dispatch_sync],
/// );
/// ```
///
/// This generates:
/// - `CombinedRequest` / `CombinedResponse` wrapper enums
/// - `CombinedClient<T>` with `.greeter()` and `.math()` accessors
/// - `CombinedClientSync<T, B>` with `.greeter()` and `.math()` accessors
/// - `combined_serve()` and `combined_serve_sync()` dispatch loop functions
/// - `CombinedTokioService` type alias (behind `tokio` feature)
/// - Embassy types (behind `embassy` feature)