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
//! XPC echo — an end-to-end demonstration of Apple XPC using an anonymous channel.
//!
//! Two tokio tasks run in-process: a **server** task that echoes every incoming message
//! back to the sender, and a **client** task that exercises three XPC patterns:
//!
//! 1. Fire-and-forget send (`XpcConnection::send`)
//! 2. Request-reply (`XpcConnection::send_request` / `ReceivedXpcMessage::reply`)
//! 3. Shutdown via connection cancel
//!
//! The channel is created with `XpcEndpoint::anonymous_channel`, which requires no
//! launchd registration and no installed plist — making this example fully self-contained.
//!
//! > **Apple-only.** XPC is available exclusively on macOS/iOS/tvOS/watchOS. On other
//! > platforms the binary prints a notice and exits immediately.
//!
//! # Run the example
//!
//! ```sh
//! cargo run --example xpc_echo --features=net-apple-xpc
//! ```
//!
//! # Expected output
//!
//! ```text
//! [server] ready, waiting for events
//! [client] sending ping (fire-and-forget)
//! [server] received fire-and-forget: {"kind": "ping"}
//! [client] sending echo request
//! [server] received request, replying: {"text": "hello xpc"}
//! [client] got reply: {"text": "hello xpc"}
//! [client] done, cancelling connection
//! [server] connection closed (Interrupted), shutting down
//! ```
#![cfg_attr(
target_vendor = "apple",
expect(
clippy::expect_used,
reason = "example: panic-on-error is the standard pattern for demos"
)
)]
#[cfg(not(target_vendor = "apple"))]
fn main() {
eprintln!("xpc_echo: XPC is only available on Apple platforms.");
}
#[cfg(target_vendor = "apple")]
#[tokio::main]
async fn main() {
use std::{collections::BTreeMap, convert::Infallible, time::Duration};
use tokio::sync::oneshot;
use rama::{
graceful::Shutdown,
net::apple::xpc::{XpcEndpoint, XpcMessage, XpcServer},
rt::Executor,
service::service_fn,
telemetry::tracing::{
self, error, info,
level_filters::LevelFilter,
subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt},
},
};
tracing::subscriber::registry()
.with(fmt::layer())
.with(
EnvFilter::builder()
.with_default_directive(LevelFilter::DEBUG.into())
.from_env_lossy(),
)
.init();
let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
let graceful = Shutdown::new(async { drop(shutdown_rx.await) });
// Create anonymous channel: server connection + endpoint for the client.
let (server_conn, endpoint) = XpcEndpoint::anonymous_channel(None).expect("anonymous_channel");
let server = XpcServer::new(service_fn(async |message: XpcMessage| {
let reply = match message {
XpcMessage::Dictionary(values) if values.contains_key("text") => {
Some(XpcMessage::Dictionary(values))
}
_ => None,
};
Ok::<_, Infallible>(reply)
}));
// Server task: serve the anonymous listener through the higher-level XpcServer
// adapter, which accepts the peer connection and dispatches message handling to
// the Rama service above.
let server_task = graceful.spawn_task_fn(async move |guard| {
info!(target: "xpc_echo::server", "ready, waiting for peer connection");
if let Err(err) = server
.serve_connection(server_conn, Executor::graceful(guard))
.await
{
error!(target: "xpc_echo::server", %err, "server error");
}
info!(target: "xpc_echo::server", "server stopped");
});
// Client task: connect via endpoint, send fire-and-forget then a request.
let client_task = graceful.spawn_task(async move {
let client_conn = endpoint.into_connection().expect("into_connection");
// 1. Fire-and-forget ping.
info!(target: "xpc_echo::client", "sending ping (fire-and-forget)");
let mut ping = BTreeMap::new();
ping.insert("kind".to_owned(), XpcMessage::String("ping".to_owned()));
client_conn
.send(XpcMessage::Dictionary(ping))
.expect("send ping");
// Small yield so the server gets a chance to print the fire-and-forget before
// we start the request — not required for correctness.
tokio::task::yield_now().await;
// 2. Request-reply echo.
info!(target: "xpc_echo::client", "sending echo request");
let mut req = BTreeMap::new();
req.insert(
"text".to_owned(),
XpcMessage::String("hello xpc".to_owned()),
);
match client_conn.send_request(XpcMessage::Dictionary(req)).await {
Ok(reply) => info!(target: "xpc_echo::client", ?reply, "got reply"),
Err(err) => error!(target: "xpc_echo::client", %err, "request error"),
}
// 3. Cancel to signal the server we're done.
info!(target: "xpc_echo::client", "done, cancelling connection");
client_conn.cancel();
_ = shutdown_tx.send(());
});
_ = tokio::join!(server_task, client_task);
graceful
.shutdown_with_limit(Duration::from_secs(5))
.await
.expect("graceful shutdown");
}