1mod carrier;
13mod catalog;
14mod connection;
15mod host;
16mod identity;
17mod instance;
18mod local;
19mod observer;
20mod observer_host;
21mod plugin;
22mod publisher;
23mod task;
24
25pub mod __private {
26 pub use super::observer::{Declarations as ObserverDeclarations, Inventory as ObserverInventory, Outputs as ObserverOutputs};
27 pub use super::task::spawn as spawn_observer;
28 pub use crate as sidecar;
29 pub use harmos_domain::{qualified, qualified_len, qualify};
30 pub use harmos_sdk::__private::*;
31}
32
33pub use harmos_sdk::{ExecutionContext, Operation, Service};
36
37pub use catalog::{CATALOG_EXPORT_FLAG, CATALOG_EXPORT_LIMIT};
38pub use host::{CallError, PublicationError, Supervisor};
39pub use identity::ActivationId;
40pub use instance::OperationInstance;
41pub use observer::{Activation, Activity, DemandKey, Instance, Observer, ObserverAttachment, ObserverContext, ObserverError, Tick};
42pub use observer_host::{ObserverCaller, ObserverHost, ObserverPublisher, ObserverStreamPublisher};
43pub use plugin::{PluginNative, run_plugin, serve_plugin_channels};
44pub use publisher::{Batch, PublishError, Publisher};
45
46use std::collections::BTreeMap;
50use std::future::Future;
51use std::pin::Pin;
52use std::sync::{Arc, Mutex};
53use tokio::sync::{mpsc, oneshot};
55use crate::Frame;
57use carrier::{Failure, Input, Sender};
58use harmos_domain::{Refusal, UNKNOWN, UNREADABLE, reserved};
59use host::Outgoing;
60
61const INCOMING_REQUESTS: usize = 128;
64
65type Prepare = Box<dyn Fn(&str) -> Result<Serving, Refusal> + Send + Sync>;
70
71type Cancellations = Arc<Mutex<BTreeMap<u64, Option<oneshot::Sender<()>>>>>;
72type Table = Arc<Mutex<harmos_sdk::__private::WireTable>>;
73type SharedOpening = Arc<tokio::sync::Mutex<Opening>>;
74type Retirement = Box<dyn Fn() -> Pin<Box<dyn Future<Output = Result<(), Refusal>> + Send>> + Send + Sync>;
75
76struct Incoming {
79 frame: Frame,
80 cancelled: Option<oneshot::Receiver<()>>,
81 consumption: Option<crate::transport::Consumption>,
82}
83
84struct ConnectionGuard(Supervisor);
85
86struct Worker(tokio::task::JoinHandle<Result<(), Refusal>>);
87
88pub(crate) struct Opening {
89 serving: Option<Serving>,
90 prepare: Prepare,
91 catalog: crate::PluginCatalog,
92}
93
94struct Serving {
95 owner: String,
96 operations: Table,
97 retirement: Retirement,
98 quiescence: Retirement,
99}
100
101enum Answered {
104 Nothing,
105 Frame(Frame),
106 Deferred(Pin<Box<dyn Future<Output = Frame> + Send>>),
107}
108
109impl Drop for ConnectionGuard {
114 fn drop(&mut self) {
115 self.0.disconnected();
116 }
117}
118
119impl Drop for Worker {
120 fn drop(&mut self) {
121 self.0.abort();
122 }
123}
124
125impl Opening {
126 async fn cleanup(&mut self) -> Result<(), Refusal> {
127 let Some(serving) = &self.serving else {
128 return Ok(());
129 };
130 let settlement = serving.operations.lock().map_err(Refusal::of)?.close();
131 settlement.await.map_err(Refusal::of)?;
132 (serving.retirement)().await?;
133 let retired = serving.operations.lock().map_err(Refusal::of)?.release().map_err(Refusal::of)?;
134 drop(retired);
135 Ok(())
136 }
137}
138
139async fn exchange(
144 opening: SharedOpening, inbound: &mut Input, outgoing: &Sender, completed: oneshot::Sender<()>,
145 mut stopping: tokio::sync::watch::Receiver<bool>, clock: fn() -> Option<i64>,
146) -> Result<(), Refusal> {
147 let (to_host, mut outbound) = mpsc::channel(1024);
148 let host = Supervisor::new(to_host.clone(), clock);
149 let _connection = ConnectionGuard(host.clone());
150 let (requests_tx, requests_rx) = mpsc::channel(INCOMING_REQUESTS);
151 let cancellations: Cancellations = Arc::new(Mutex::new(BTreeMap::new()));
152 let (shutdown_tx, shutdown_rx) = oneshot::channel();
153 let mut worker =
154 Worker(tokio::spawn(dispatch_requests(opening, host.clone(), requests_rx, cancellations.clone(), to_host.clone(), shutdown_rx)));
155 let mut abandoned = false;
156 let mut active_requests = std::collections::BTreeSet::new();
157 let mut exit_after_request = None;
158 let mut abandon_after_request = false;
159 let mut worker_finished = false;
160 let mut outcome = Ok(());
161
162 let serving = async {
163 loop {
164 let sending = {
165 let worker_done = async {
166 if worker_finished {
167 std::future::pending::<Result<Result<(), Refusal>, tokio::task::JoinError>>().await
168 } else {
169 (&mut worker.0).await
170 }
171 };
172 tokio::select! {
173 arrival = inbound.receive() => match arrival {
174 Ok(Some(carrier::Arrival { frame, cancelled: early_cancelled, consumption })) => match frame {
175 Frame::Answer { id, result } => {
176 host.receive_answer(id, result);
177 if let Some(receipt) = consumption { let _ = receipt.consumed(); }
178 continue;
179 }
180 Frame::Refusal { id, refusal } => {
181 host.receive_refusal(id, refusal);
182 if let Some(receipt) = consumption { let _ = receipt.consumed(); }
183 continue;
184 }
185 Frame::Cancel { id } => {
186 if let Some(cancel) = cancellations.lock().expect("request cancellation lock")
187 .get_mut(&id).and_then(Option::take)
188 {
189 let _ = cancel.send(());
190 }
191 continue;
192 }
193 frame @ (Frame::Call { .. } | Frame::Push { .. }) => {
194 let cancelled = if let Frame::Call { id, .. } = &frame {
195 let mut pending = cancellations.lock().expect("request cancellation lock");
196 if pending.contains_key(id) {
197 outgoing.fail(Failure::Invalid("duplicate live call id".to_owned()));
198 break;
199 }
200 let (cancel, cancelled) = oneshot::channel();
201 if early_cancelled {
202 let _ = cancel.send(());
203 pending.insert(*id, None);
204 } else {
205 pending.insert(*id, Some(cancel));
206 }
207 Some(cancelled)
208 } else {
209 None
210 };
211 match requests_tx.try_send(Incoming { frame, cancelled, consumption }) {
212 Ok(()) => continue,
213 Err(mpsc::error::TrySendError::Full(request)) => {
214 let id = match &request.frame { Frame::Call { id, .. } => Some(*id), _ => None };
215 drop(request.frame);
216 if let Some(receipt) = request.consumption { let _ = receipt.consumed(); }
217 if let Some(id) = id {
218 cancellations.lock().expect("request cancellation lock").remove(&id);
219 let refusal = Refusal::of("the sidecar request queue is full");
220 if to_host.try_send(Outgoing::Frame(Frame::Refusal {
221 id,
222 refusal,
223 })).is_err() {
224 break;
225 }
226 continue;
227 }
228 outgoing.fail(Failure::Capacity);
229 break;
230 }
231 Err(mpsc::error::TrySendError::Closed(_)) => break,
232 }
233 }
234 },
235 Ok(None) => break,
236 Err(error) => { outgoing.fail(Failure::Invalid(error.to_string())); break; },
237 },
238 asked = outbound.recv() => match asked {
239 None => break,
240 Some(asked) => asked,
241 },
242 result = worker_done => {
243 worker_finished = true;
244 match result {
245 Err(error) => {
246 outcome = Err(Refusal::of(error));
247 outgoing.fail(Failure::Panicked);
248 break;
249 }
250 Ok(result) => {
251 outcome = result;
252 match outbound.try_recv() {
253 Ok(asked) => asked,
254 Err(_) => break,
255 }
256 },
257 }
258 }
259 }
260 };
261
262 match sending {
263 Outgoing::Frame(frame) => {
264 let answered = match &frame {
265 Frame::Answer { id, .. } | Frame::Refusal { id, .. } => Some(*id),
266 _ => None,
267 };
268 if send(outgoing, frame).await.is_err() {
269 break;
270 }
271 if answered.is_some_and(|id| active_requests.remove(&id)) && active_requests.is_empty() {
272 if abandon_after_request {
273 abandoned = true;
274 break;
275 }
276 if let Some(reason) = exit_after_request.take() {
277 let _ = send(outgoing, Frame::exiting(reason)).await;
278 break;
279 }
280 }
281 }
282 Outgoing::Started(id) => {
283 active_requests.insert(id);
284 }
285 Outgoing::Finished(id) => {
286 if active_requests.remove(&id) && active_requests.is_empty() {
287 if abandon_after_request {
288 abandoned = true;
289 break;
290 }
291 if let Some(reason) = exit_after_request.take() {
292 let _ = send(outgoing, Frame::exiting(reason)).await;
293 break;
294 }
295 }
296 }
297 Outgoing::Exiting(reason) => {
298 if !active_requests.is_empty() {
299 exit_after_request.get_or_insert(reason);
300 } else {
301 let _ = send(outgoing, Frame::exiting(reason)).await;
302 break;
303 }
304 }
305 Outgoing::Abandon => {
306 if !active_requests.is_empty() {
307 abandon_after_request = true;
308 } else {
309 abandoned = true;
310 break;
311 }
312 }
313 }
314 }
315 };
316 tokio::select! {
317 biased;
318 _ = stopping.wait_for(|closing| *closing) => {}
319 _ = serving => {}
320 }
321
322 host.disconnected();
323 outbound.close();
324 drop(requests_tx);
325 let _ = shutdown_tx.send(abandoned);
326 if !worker_finished {
327 outcome = (&mut worker.0).await.map_err(Refusal::of).and_then(|result| result);
328 }
329 let _ = completed.send(());
330 outcome
331}
332
333async fn dispatch_requests(
334 opening: SharedOpening, host: Supervisor, mut requests: mpsc::Receiver<Incoming>, cancellations: Cancellations,
335 outgoing: mpsc::Sender<Outgoing>, mut shutdown: oneshot::Receiver<bool>,
336) -> Result<(), Refusal> {
337 let mut abandoned = false;
338 let mut replies = tokio::task::JoinSet::new();
339 loop {
340 let request = tokio::select! {
341 biased;
342 ended = &mut shutdown => {
343 abandoned = ended.unwrap_or(false);
344 break;
345 }
346 completed = replies.join_next(), if !replies.is_empty() => {
347 if let Some(Err(error)) = completed {
348 tracing::warn!(%error, "carrier reply task failed");
349 break;
350 }
351 continue;
352 }
353 request = requests.recv() => request,
354 };
355 let Some(Incoming {
356 frame: request,
357 mut cancelled,
358 consumption,
359 }) = request
360 else {
361 break;
362 };
363 let id = match &request {
364 Frame::Call { id, .. } => Some(*id),
365 _ => None,
366 };
367
368 if let Frame::Call { id, .. } = &request
369 && outgoing.send(Outgoing::Started(*id)).await.is_err()
370 {
371 break;
372 }
373
374 if replies.len() >= INCOMING_REQUESTS {
375 drop(request);
376 if let Some(receipt) = consumption {
377 let _ = receipt.consumed();
378 }
379 if let Some(id) = id {
380 cancellations.lock().expect("request cancellation lock").remove(&id);
381 let _ = outgoing
382 .send(Outgoing::Frame(Frame::Refusal {
383 id,
384 refusal: Refusal::new(UNKNOWN, "carrier reply capacity exhausted".to_owned()),
385 }))
386 .await;
387 }
388 continue;
389 }
390
391 let mut was_cancelled = false;
392 let answered = tokio::select! {
393 biased;
394 ended = &mut shutdown => {
395 abandoned = ended.unwrap_or(false);
396 None
397 }
398 _ = async {
399 match cancelled.as_mut() {
400 Some(cancelled) => { let _ = cancelled.await; }
401 None => std::future::pending::<()>().await,
402 }
403 } => {
404 was_cancelled = true;
405 None
406 }
407 answered = async {
408 let mut opening = opening.lock().await;
409 answer(&mut opening, &host, request).await
410 } => Some(answered),
411 };
412 if let Some(receipt) = consumption {
413 let _ = receipt.consumed();
414 }
415 if let Some(id) = id {
416 if !matches!(&answered, Some(Answered::Deferred(_))) {
417 cancellations.lock().expect("request cancellation lock").remove(&id);
418 }
419 if was_cancelled {
420 if outgoing.send(Outgoing::Finished(id)).await.is_err() {
421 break;
422 }
423 continue;
424 }
425 }
426 let Some(answered) = answered else {
427 break;
428 };
429
430 match answered {
431 Answered::Deferred(reply) => {
432 let outgoing = outgoing.clone();
433 let cancellations = Arc::clone(&cancellations);
434 replies.spawn(async move {
435 let response = tokio::select! {
436 biased;
437 _ = async {
438 match cancelled.as_mut() {
439 Some(cancelled) => { let _ = cancelled.await; }
440 None => std::future::pending::<()>().await,
441 }
442 } => id.map(Outgoing::Finished),
443 frame = reply => Some(Outgoing::Frame(frame)),
444 };
445 if let Some(response) = response {
446 let _ = outgoing.send(response).await;
447 }
448 if let Some(id) = id {
449 cancellations.lock().expect("request cancellation lock").remove(&id);
450 }
451 });
452 }
453 Answered::Nothing => continue,
454 Answered::Frame(frame) => {
455 if outgoing.send(Outgoing::Frame(frame)).await.is_err() {
456 break;
457 }
458 }
459 }
460 }
461
462 replies.abort_all();
463 while replies.join_next().await.is_some() {}
464 cancellations.lock().expect("request cancellation lock").clear();
465
466 let mut opening = opening.lock().await;
470 if abandoned {
471 if let Some(serving) = &opening.serving {
474 (serving.quiescence)().await?;
475 }
476 } else {
477 opening.cleanup().await?;
478 }
479 Ok(())
480}
481
482async fn answer(opening: &mut Opening, host: &Supervisor, frame: Frame) -> Answered {
483 let (id, method, params) = match frame {
484 Frame::Call { id, method, params } => (id, method, params),
485 _ => return Answered::Nothing,
486 };
487 if method == Frame::REGISTER {
488 let owner = match <String as crate::Message>::decode(¶ms) {
489 Ok(owner) => owner,
490 Err(error) => {
491 return Answered::Frame(Frame::Refusal {
492 id,
493 refusal: Refusal::new(UNREADABLE, format!("invalid owner assignment: {error}")),
494 });
495 }
496 };
497 if let Some(serving) = &opening.serving {
498 if serving.owner != owner {
499 return Answered::Frame(Frame::Refusal {
500 id,
501 refusal: Refusal::of("carrier owner assignment cannot change"),
502 });
503 }
504 } else {
505 match (opening.prepare)(&owner) {
506 Ok(serving) => opening.serving = Some(serving),
507 Err(error) => return Answered::Frame(Frame::Refusal { id, refusal: error }),
508 }
509 }
510 return match serde_json::to_vec(&opening.catalog) {
511 Ok(result) => Answered::Frame(Frame::Answer { id, result: result.into() }),
512 Err(error) => Answered::Frame(Frame::Refusal {
513 id,
514 refusal: Refusal::new(UNREADABLE, error.to_string()),
515 }),
516 };
517 }
518 if method == crate::operations::WIRE_METHOD {
519 let command = match crate::operations::WireCommand::decode(¶ms) {
520 Ok(command) => command,
521 Err(error) => {
522 return Answered::Frame(Frame::Refusal {
523 id,
524 refusal: Refusal::new(UNREADABLE, error.to_string()),
525 });
526 }
527 };
528 let Some(serving) = &opening.serving else {
529 return Answered::Frame(Frame::Refusal {
530 id,
531 refusal: Refusal::of("carrier owner has not been assigned"),
532 });
533 };
534 let table = &serving.operations;
535 let operation_host = match &command {
536 crate::operations::WireCommand::Initialize { context, .. } => Ok(host.for_operation(*context)),
537 crate::operations::WireCommand::Attempt { context, values, .. }
538 | crate::operations::WireCommand::StreamAttempt { context, values, .. } => host.for_attempt(*context, values.clone()),
539 _ => Ok(host.clone()),
540 };
541 let operation_host = match operation_host {
542 Ok(operation_host) => operation_host,
543 Err(error) => {
544 return Answered::Frame(Frame::Refusal {
545 id,
546 refusal: Refusal::new(UNREADABLE, error.to_string()),
547 });
548 }
549 };
550 let phase = match table.lock() {
551 Ok(mut table) => {
552 let call = operation_host.context().clone();
553 let mut dependencies = harmos_sdk::__private::ResourceSet::new();
554 let caller = Arc::new(harmos_sdk::operations::Calls(Arc::new(operation_host.clone())));
555 if let Err(error) = dependencies.insert(caller).and_then(|()| dependencies.insert(Arc::new(operation_host))) {
556 return Answered::Frame(Frame::Refusal { id, refusal: Refusal::of(error) });
557 }
558 let context = crate::ExecutionContext::new(dependencies, call);
559 table.begin(command, Arc::new(context))
560 }
561 Err(error) => {
562 return Answered::Frame(Frame::Refusal {
563 id,
564 refusal: Refusal::new(UNREADABLE, error.to_string()),
565 });
566 }
567 };
568 return Answered::Deferred(Box::pin(async move {
569 let answer = phase.await;
570 let minimum = match &answer {
573 crate::operations::WireAnswer::Ready { payload } => payload.len(),
574 crate::operations::WireAnswer::Stream { payload, .. } => payload.as_ref().map_or(0, Vec::len),
575 crate::operations::WireAnswer::Failed { failure } => failure.cause().len(),
576 };
577 if minimum >= Frame::LIMIT {
578 return oversized_reply(id);
579 }
580 match answer.encode() {
581 Ok(result) => Frame::Answer { id, result: result.into() },
582 Err(harmos_sdk::operations::WireCodecError::Oversized) => oversized_reply(id),
583 Err(error) => Frame::Refusal {
584 id,
585 refusal: Refusal::new(UNREADABLE, error.to_string()),
586 },
587 }
588 }));
589 }
590 if reserved(&method) {
591 return Answered::Frame(Frame::Refusal {
592 id,
593 refusal: Refusal::new(UNKNOWN, format!("this sidecar does not serve the reserved `{method}`")),
594 });
595 }
596
597 Answered::Frame(Frame::Refusal {
598 id,
599 refusal: Refusal::new(
600 UNKNOWN,
601 format!("operation work must use the `{}` phase contract; received `{method}`", crate::operations::WIRE_METHOD),
602 ),
603 })
604}
605
606fn oversized_reply(id: u64) -> Frame {
607 Frame::Refusal {
608 id,
609 refusal: Refusal::new(Frame::OVERSIZED, format!("the route reply exceeds the {}-byte sidecar frame limit", Frame::LIMIT)),
610 }
611}
612
613async fn send(outgoing: &Sender, frame: Frame) -> std::io::Result<()> {
614 let call = match &frame {
615 Frame::Answer { id, .. } | Frame::Refusal { id, .. } => Some(*id),
616 _ => None,
617 };
618 let frame = match call {
619 Some(id) if !frame.fits() => oversized_reply(id),
620 _ => frame,
621 };
622 outgoing.send(frame).await
623}
624
625