1use std::{
2 any::Any,
3 cell::{Cell, RefCell},
4 collections::VecDeque,
5 fmt,
6 marker::PhantomData,
7 panic::AssertUnwindSafe,
8 rc::{Rc, Weak},
9};
10
11use futures::{FutureExt, future::LocalBoxFuture};
12
13use super::{
14 CancellationToken, DiagnosticAdmission, DiagnosticEvent, DiagnosticSource, EventAdmissionPlan,
15 InvocationContext, NativeAppRuntime, RuntimeFailure, diagnostics::diagnostic_operation,
16 ensure_context_active, schedule_plugin_supervision_after_failure,
17};
18
19pub trait EventCapability: 'static {
21 type Event: Clone + 'static;
23 const ID: &'static str;
25 const DESCRIPTOR_VERSION: &'static str;
27}
28
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31pub enum EventAdmission {
32 Accepted,
34 Unavailable,
36 Exhausted,
38}
39
40pub type EventPublishStatus = EventAdmission;
42
43#[derive(Clone, Debug, Eq, PartialEq)]
45pub struct EventPublishResult {
46 subscriber_instance: String,
47 admission: EventAdmission,
48}
49
50impl EventPublishResult {
51 pub(crate) fn new(subscriber_instance: String, admission: EventAdmission) -> Self {
52 Self {
53 subscriber_instance,
54 admission,
55 }
56 }
57
58 pub fn subscriber_instance(&self) -> &str {
60 &self.subscriber_instance
61 }
62
63 pub fn provider_instance(&self) -> &str {
65 self.subscriber_instance()
66 }
67
68 pub const fn admission(&self) -> EventAdmission {
70 self.admission
71 }
72
73 pub const fn status(&self) -> EventPublishStatus {
75 self.admission
76 }
77}
78
79pub trait NativeEventEndpoint: fmt::Debug {
81 fn capability_id(&self) -> &'static str;
83 fn descriptor_version(&self) -> &'static str;
85 fn operations(&self) -> &'static [&'static str];
87 fn owns_event_admission(&self) -> bool {
93 false
94 }
95 fn publish(
101 &self,
102 operation: &str,
103 event: Box<dyn Any>,
104 context: InvocationContext,
105 ) -> LocalBoxFuture<'static, Result<(), RuntimeFailure>>;
106}
107
108struct QueuedEvent {
109 operation: String,
110 event: Box<dyn Any>,
111 context: InvocationContext,
112 snapshot: NativeEventEndpointSnapshot,
113}
114
115impl fmt::Debug for QueuedEvent {
116 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
117 formatter
118 .debug_struct("QueuedEvent")
119 .field("operation", &self.operation)
120 .field("request_id", &self.context.request_id())
121 .field("generation", &self.snapshot.generation)
122 .finish_non_exhaustive()
123 }
124}
125
126#[derive(Debug, Default)]
127struct NativeEventQueueState {
128 pending: VecDeque<QueuedEvent>,
129 admitted: usize,
130 draining: bool,
131}
132
133#[derive(Debug)]
135pub(crate) struct NativeEventQueue {
136 capacity: usize,
137 state: RefCell<NativeEventQueueState>,
138}
139
140impl NativeEventQueue {
141 pub(crate) fn new(admission: EventAdmissionPlan) -> Rc<Self> {
142 Self {
143 capacity: admission.capacity(),
144 state: RefCell::new(NativeEventQueueState::default()),
145 }
146 .into()
147 }
148
149 fn try_enqueue(&self, event: QueuedEvent) -> Option<bool> {
150 let mut state = self.state.borrow_mut();
151 if state.admitted >= self.capacity {
152 return None;
153 }
154 state.admitted += 1;
155 state.pending.push_back(event);
156 if state.draining {
157 Some(false)
158 } else {
159 state.draining = true;
160 Some(true)
161 }
162 }
163
164 fn pop(&self) -> Option<QueuedEvent> {
165 self.state.borrow_mut().pending.pop_front()
166 }
167
168 fn complete(&self) {
169 let mut state = self.state.borrow_mut();
170 state.admitted = state.admitted.saturating_sub(1);
171 if state.pending.is_empty() {
172 state.draining = false;
173 }
174 }
175
176 fn abort(&self) {
177 let mut state = self.state.borrow_mut();
178 state.pending.clear();
179 state.admitted = 0;
180 state.draining = false;
181 }
182}
183
184#[derive(Clone, Debug)]
185pub(crate) struct NativeEventEndpointSnapshot {
186 pub(crate) endpoint: Rc<dyn NativeEventEndpoint>,
187 pub(crate) generation: u64,
188 pub(crate) cancellation: CancellationToken,
189}
190
191#[derive(Debug)]
192pub(crate) struct NativeEventEndpointState {
193 pub(crate) capability_id: &'static str,
194 pub(crate) descriptor_version: &'static str,
195 pub(crate) operations: &'static [&'static str],
196 endpoint: RefCell<Option<Rc<dyn NativeEventEndpoint>>>,
197 generation: Cell<u64>,
198 cancellation: RefCell<CancellationToken>,
199 queues: RefCell<Vec<Weak<NativeEventQueue>>>,
200}
201
202impl NativeEventEndpointState {
203 pub(crate) fn new(endpoint: Rc<dyn NativeEventEndpoint>, generation: u64) -> Self {
204 Self {
205 capability_id: endpoint.capability_id(),
206 descriptor_version: endpoint.descriptor_version(),
207 operations: endpoint.operations(),
208 endpoint: RefCell::new(Some(endpoint)),
209 generation: Cell::new(generation),
210 cancellation: RefCell::new(CancellationToken::new()),
211 queues: RefCell::new(Vec::new()),
212 }
213 }
214
215 pub(crate) fn snapshot(&self) -> Option<NativeEventEndpointSnapshot> {
216 self.endpoint
217 .borrow()
218 .clone()
219 .map(|endpoint| NativeEventEndpointSnapshot {
220 endpoint,
221 generation: self.generation.get(),
222 cancellation: self.cancellation.borrow().clone(),
223 })
224 }
225
226 pub(crate) fn mark_unavailable(&self) {
227 self.cancellation.borrow().cancel();
228 self.endpoint.borrow_mut().take();
229 self.reset_queues();
230 }
231
232 pub(crate) fn cancel(&self) {
233 self.cancellation.borrow().cancel();
234 }
235
236 pub(crate) fn install(&self, endpoint: Rc<dyn NativeEventEndpoint>, generation: u64) {
237 self.generation.set(generation);
238 self.cancellation.replace(CancellationToken::new());
239 self.endpoint.replace(Some(endpoint));
240 }
241
242 pub(crate) fn is_current(&self, generation: u64) -> bool {
243 self.generation.get() == generation && self.endpoint.borrow().is_some()
244 }
245
246 pub(crate) fn register_queue(&self, queue: &Rc<NativeEventQueue>) {
247 self.queues.borrow_mut().push(Rc::downgrade(queue));
248 }
249
250 fn reset_queues(&self) {
251 self.queues.borrow_mut().retain(|queue| {
252 let Some(queue) = queue.upgrade() else {
253 return false;
254 };
255 queue.abort();
256 true
257 });
258 }
259}
260
261#[derive(Clone, Debug)]
262pub(crate) struct NativeEventEndpointBinding {
263 pub(super) requirement_id: String,
264 pub(crate) plugin_instance: String,
265 pub(crate) state: Rc<NativeEventEndpointState>,
266 pub(crate) queue: Rc<NativeEventQueue>,
267}
268
269#[derive(Clone, Debug)]
271pub struct PluginEventDependencyHandle {
272 pub(crate) binding: NativeEventEndpointBinding,
273 pub(crate) caller_instance: String,
274 pub(crate) runtime: Rc<RefCell<std::rc::Weak<NativeAppRuntime>>>,
275}
276
277impl PluginEventDependencyHandle {
278 pub fn capability_id(&self) -> &'static str {
280 self.binding.state.capability_id
281 }
282
283 pub fn descriptor_version(&self) -> &'static str {
285 self.binding.state.descriptor_version
286 }
287
288 pub fn operations(&self) -> &'static [&'static str] {
290 self.binding.state.operations
291 }
292
293 pub fn typed<C: EventCapability>(&self) -> Result<NativeEventHandle<C>, RuntimeFailure> {
295 if self.capability_id() != C::ID || self.descriptor_version() != C::DESCRIPTOR_VERSION {
296 return Err(RuntimeFailure::ProtocolViolation { capability: C::ID });
297 }
298 let runtime = self
299 .runtime
300 .borrow()
301 .upgrade()
302 .ok_or(RuntimeFailure::AdmissionClosed)?;
303 Ok(NativeEventHandle::from_endpoints(
304 std::slice::from_ref(&self.binding),
305 runtime,
306 &self.caller_instance,
307 true,
308 ))
309 }
310}
311
312#[derive(Debug)]
314pub struct NativeEventHandle<C: EventCapability> {
315 endpoints: Vec<NativeEventEndpointBinding>,
316 runtime: Rc<NativeAppRuntime>,
317 caller_instance: String,
318 allow_before_ready: bool,
319 capability: PhantomData<fn() -> C>,
320}
321
322impl<C: EventCapability> Clone for NativeEventHandle<C> {
323 fn clone(&self) -> Self {
324 Self {
325 endpoints: self.endpoints.clone(),
326 runtime: self.runtime.clone(),
327 caller_instance: self.caller_instance.clone(),
328 allow_before_ready: self.allow_before_ready,
329 capability: PhantomData,
330 }
331 }
332}
333
334impl<C: EventCapability> NativeEventHandle<C> {
335 pub(crate) fn from_endpoints(
336 endpoints: &[NativeEventEndpointBinding],
337 runtime: Rc<NativeAppRuntime>,
338 caller_instance: &str,
339 allow_before_ready: bool,
340 ) -> Self {
341 Self {
342 endpoints: endpoints.to_vec(),
343 runtime,
344 caller_instance: caller_instance.to_owned(),
345 allow_before_ready,
346 capability: PhantomData,
347 }
348 }
349
350 pub fn binding_count(&self) -> usize {
352 self.endpoints.len()
353 }
354
355 pub async fn publish(&self, operation: &str, event: C::Event) -> Vec<EventPublishResult> {
361 self.publish_with_context(operation, self.next_context(), event)
362 .await
363 }
364
365 pub async fn publish_with_context(
367 &self,
368 operation: &str,
369 context: InvocationContext,
370 event: C::Event,
371 ) -> Vec<EventPublishResult> {
372 let context = context
373 .for_caller(&self.caller_instance)
374 .for_target(C::ID, operation);
375 futures::future::join_all(self.endpoints.iter().map(|endpoint| {
376 self.publish_to_endpoint(endpoint, operation, context.clone(), event.clone())
377 }))
378 .await
379 }
380
381 async fn publish_to_endpoint(
382 &self,
383 endpoint: &NativeEventEndpointBinding,
384 operation: &str,
385 context: InvocationContext,
386 event: C::Event,
387 ) -> EventPublishResult {
388 let operation_name = diagnostic_operation(endpoint.state.operations, operation);
389 let was_closed = self.runtime.shutdown_started.get()
390 || (!self.allow_before_ready && self.runtime.admission.is_closed());
391 let result = self
392 .publish_to_endpoint_inner(endpoint, operation, context.clone(), event)
393 .await;
394 let outcome = match result.admission() {
395 EventAdmission::Accepted => DiagnosticAdmission::Accepted,
396 EventAdmission::Unavailable if was_closed => DiagnosticAdmission::Closed,
397 EventAdmission::Unavailable => DiagnosticAdmission::Unavailable,
398 EventAdmission::Exhausted => DiagnosticAdmission::Exhausted,
399 };
400 self.runtime.diagnostics.emit(
401 DiagnosticSource::Admission,
402 (self.runtime.driver.now)(),
403 |_| DiagnosticEvent::EventAdmission {
404 requirement_id: Some(endpoint.requirement_id.clone()),
405 request_id: context.request_id(),
406 publisher_instance: self.caller_instance.clone(),
407 subscriber_instance: endpoint.plugin_instance.clone(),
408 capability: C::ID,
409 operation: operation_name,
410 outcome,
411 },
412 );
413 result
414 }
415
416 #[allow(
417 clippy::too_many_lines,
418 reason = "event admission keeps legacy commit acknowledgement and v2 settlement explicit"
419 )]
420 async fn publish_to_endpoint_inner(
421 &self,
422 endpoint: &NativeEventEndpointBinding,
423 operation: &str,
424 context: InvocationContext,
425 event: C::Event,
426 ) -> EventPublishResult {
427 let subscriber = endpoint.plugin_instance.clone();
428 let unavailable =
429 || EventPublishResult::new(subscriber.clone(), EventAdmission::Unavailable);
430 if self.runtime.shutdown_started.get()
431 || (!self.allow_before_ready && self.runtime.admission.is_closed())
432 {
433 return unavailable();
434 }
435 let Some(snapshot) = endpoint.state.snapshot() else {
436 return unavailable();
437 };
438 if !endpoint.state.operations.contains(&operation) {
439 return unavailable();
440 }
441 let queue = &endpoint.queue;
442 if !endpoint.state.is_current(snapshot.generation)
443 || ensure_context_active(&self.runtime.driver, &context).is_err()
444 {
445 return unavailable();
446 }
447 if snapshot.endpoint.owns_event_admission() {
448 let result = if self
453 .runtime
454 .plan
455 .plugin_instance(&endpoint.plugin_instance)
456 .is_some_and(|instance| instance.authoring_version() == 2)
457 {
458 let endpoint_impl = snapshot.endpoint.clone();
459 let operation_name = operation.to_owned();
460 super::settlement::operation(
461 &self.runtime,
462 &endpoint.plugin_instance,
463 &context,
464 snapshot.cancellation,
465 C::ID,
466 move |execution_context| {
467 endpoint_impl.publish(&operation_name, Box::new(event), execution_context)
468 },
469 )
470 .await
471 .and_then(|result| result)
472 } else {
473 snapshot
474 .endpoint
475 .publish(operation, Box::new(event), context.clone())
476 .await
477 };
478 return match result {
479 Ok(()) => EventPublishResult::new(subscriber, EventAdmission::Accepted),
480 Err(error) => {
481 let error = schedule_plugin_supervision_after_failure(
482 &self.runtime,
483 &endpoint.plugin_instance,
484 error,
485 );
486 self.runtime.diagnostics.emit_runtime_failure(
487 (self.runtime.driver.now)(),
488 Some(&endpoint.plugin_instance),
489 &error,
490 );
491 let admission = if matches!(error, RuntimeFailure::ResourceExhausted { .. }) {
492 EventAdmission::Exhausted
493 } else {
494 EventAdmission::Unavailable
495 };
496 EventPublishResult::new(subscriber, admission)
497 }
498 };
499 }
500 let queued = QueuedEvent {
501 operation: operation.to_owned(),
502 event: Box::new(event),
503 context,
504 snapshot,
505 };
506 let Some(should_start) = queue.try_enqueue(queued) else {
507 return EventPublishResult::new(subscriber, EventAdmission::Exhausted);
508 };
509 if should_start {
510 let Some(tasks) = self
511 .runtime
512 .plugins
513 .get(&endpoint.plugin_instance)
514 .and_then(|plugin| plugin.generation_parts().map(|(_, tasks, _)| tasks))
515 else {
516 queue.abort();
517 return unavailable();
518 };
519 let drain = drain_event_queue(
520 queue.clone(),
521 self.runtime.clone(),
522 endpoint.plugin_instance.clone(),
523 C::ID,
524 );
525 if tasks.spawn_local(Box::pin(drain)).is_err() {
526 queue.abort();
527 return unavailable();
528 }
529 }
530 EventPublishResult::new(subscriber, EventAdmission::Accepted)
531 }
532
533 fn next_context(&self) -> InvocationContext {
534 InvocationContext::new(self.next_request_id(), None, CancellationToken::new())
535 .with_caller_instance(self.caller_instance.clone())
536 }
537
538 fn next_request_id(&self) -> super::RequestId {
539 let request_id = self.runtime.request_ids.get();
540 self.runtime.request_ids.set(request_id.saturating_add(1));
541 request_id
542 }
543}
544
545async fn drain_event_queue(
546 queue: Rc<NativeEventQueue>,
547 runtime: Rc<NativeAppRuntime>,
548 plugin_instance: String,
549 capability: &'static str,
550) {
551 while let Some(queued) = queue.pop() {
552 let endpoint = queued.snapshot.endpoint.clone();
553 let operation = queued.operation;
554 let event = queued.event;
555 let result = AssertUnwindSafe(super::settlement::operation(
556 &runtime,
557 &plugin_instance,
558 &queued.context,
559 queued.snapshot.cancellation,
560 capability,
561 move |execution_context| endpoint.publish(&operation, event, execution_context),
562 ))
563 .catch_unwind()
564 .await;
565 match result {
566 Ok(Ok(Ok(()))) => {}
567 Ok(Ok(Err(error)) | Err(error)) => {
568 runtime.diagnostics.emit_runtime_failure(
569 (runtime.driver.now)(),
570 Some(&plugin_instance),
571 &error,
572 );
573 let _ =
574 schedule_plugin_supervision_after_failure(&runtime, &plugin_instance, error);
575 }
576 Err(_) => {
577 let error = RuntimeFailure::PluginFailure {
578 detail: format!("native Event subscriber `{plugin_instance}` panicked"),
579 };
580 runtime.diagnostics.emit_runtime_failure(
581 (runtime.driver.now)(),
582 Some(&plugin_instance),
583 &error,
584 );
585 let _ =
586 schedule_plugin_supervision_after_failure(&runtime, &plugin_instance, error);
587 }
588 }
589 queue.complete();
590 }
591}