1use crate::{
4 changed, closed, config::RegistrationPolicy, lock, millis, operation, timeout, Connection,
5 Core, Gate, OwnerPhase,
6};
7use nadisc::managed::{
8 DiscoveryError, ErrorCode, RegistrationEvidence, RegistrationPlan, Result, WriteAdmission,
9};
10use std::{
11 sync::{
12 atomic::{AtomicBool, AtomicU64, Ordering},
13 Arc, Mutex,
14 },
15 time::Duration,
16};
17use tokio::{sync::watch, time::Instant};
18
19#[derive(Clone)]
21pub struct Eligibility {
22 inner: Arc<Mutex<bool>>,
23 changes: watch::Sender<bool>,
24 check: Option<Arc<dyn Fn() -> bool + Send + Sync>>,
25 valid_until: Arc<Mutex<Option<Instant>>>,
26 source: Option<std::sync::Weak<()>>,
27}
28
29pub struct EligibilityController {
31 signal: Eligibility,
32 _lifetime: Arc<()>,
33}
34impl EligibilityController {
35 pub fn update(&self, ready: bool, valid_for: Duration) -> Result<()> {
39 if valid_for < Duration::from_millis(1) || valid_for > Duration::from_secs(30) {
40 self.signal.set(false);
41 return Err(DiscoveryError::new(
42 ErrorCode::InvalidConfig,
43 "eligibility validity is out of bounds",
44 ));
45 }
46 let mut value = lock(&self.signal.inner);
47 *value = ready;
48 *lock(&self.signal.valid_until) = ready.then(|| Instant::now() + valid_for);
49 self.signal.changes.send_replace(ready);
50 Ok(())
51 }
52}
53impl Drop for EligibilityController {
54 fn drop(&mut self) {
58 self.signal.set(false);
59 }
60}
61
62impl Eligibility {
63 pub fn new() -> Self {
67 Self {
68 inner: Arc::new(Mutex::new(false)),
69 changes: watch::channel(false).0,
70 check: None,
71 valid_until: Arc::new(Mutex::new(None)),
72 source: None,
73 }
74 }
75
76 pub fn channel() -> (EligibilityController, Self) {
80 let lifetime = Arc::new(());
81 let mut signal = Self::new();
82 signal.source = Some(Arc::downgrade(&lifetime));
83 (
84 EligibilityController {
85 signal: signal.clone(),
86 _lifetime: lifetime,
87 },
88 signal,
89 )
90 }
91
92 pub fn set(&self, ready: bool) {
96 let mut value = lock(&self.inner);
97 *value = ready;
98 *lock(&self.valid_until) = ready.then(|| Instant::now() + Duration::from_secs(30));
99 self.changes.send_replace(ready);
100 }
101
102 pub fn is_ready(&self) -> bool {
106 self.current(*lock(&self.inner))
107 }
108
109 pub fn from_check(check: impl Fn() -> bool + Send + Sync + 'static) -> Self {
113 Self {
114 inner: Arc::new(Mutex::new(true)),
115 changes: watch::channel(true).0,
116 check: Some(Arc::new(check)),
117 valid_until: Arc::new(Mutex::new(None)),
118 source: None,
119 }
120 }
121
122 fn current(&self, ready: bool) -> bool {
126 ready
127 && self
128 .source
129 .as_ref()
130 .is_none_or(|source| source.strong_count() > 0)
131 && self.check.as_ref().map_or_else(
132 || lock(&self.valid_until).is_some_and(|until| Instant::now() < until),
133 |check| check(),
134 )
135 }
136}
137
138impl Default for Eligibility {
139 fn default() -> Self {
143 Self::new()
144 }
145}
146
147pub struct RegistrationSpec {
149 pub connection_ref: String,
151 pub plan: RegistrationPlan,
153 pub policy: RegistrationPolicy,
155 pub eligibility: Eligibility,
157}
158
159#[derive(Clone, Copy, Debug, Eq, PartialEq)]
161pub enum RegistrationState {
162 Planned,
164 Registering,
166 OutcomeUnknown,
168 PendingHealth,
170 Active,
172 Suspended,
174 Withdrawing,
176 Residual,
178 Removed,
180}
181
182#[derive(Clone, Debug)]
184pub struct RegistrationStatus {
185 pub id: String,
187 pub connection: String,
189 pub state: RegistrationState,
191 pub startup_confirmed: bool,
193 pub readiness_critical: bool,
195 pub observed_at: Option<Instant>,
197 pub valid_until: Option<Instant>,
199 pub error: Option<DiscoveryError>,
201}
202
203impl RegistrationStatus {
204 pub fn is_ready(&self) -> bool {
208 self.state == RegistrationState::Active
209 && self
210 .valid_until
211 .is_some_and(|deadline| Instant::now() < deadline)
212 }
213}
214
215pub(crate) struct Record {
216 pub spec: RegistrationSpec,
217 pub connection: Arc<Connection>,
218 pub status: Mutex<RegistrationStatus>,
219 pub started: AtomicBool,
220 pub confirmed: AtomicBool,
221 health_revocation: AtomicU64,
222 attempted: AtomicBool,
223 uncertain: AtomicBool,
224 unsettled_renewal: AtomicBool,
225}
226
227impl Record {
228 pub(crate) fn new(spec: RegistrationSpec, connection: Arc<Connection>) -> Self {
232 let health_revocation = connection.revocation.load(Ordering::Acquire);
233 Self {
234 status: Mutex::new(RegistrationStatus {
235 id: spec.plan.id.clone(),
236 connection: spec.connection_ref.clone(),
237 state: RegistrationState::Planned,
238 startup_confirmed: false,
239 readiness_critical: spec.policy.readiness_critical,
240 observed_at: None,
241 valid_until: None,
242 error: None,
243 }),
244 spec,
245 connection,
246 started: AtomicBool::new(false),
247 confirmed: AtomicBool::new(false),
248 health_revocation: AtomicU64::new(health_revocation),
249 attempted: AtomicBool::new(false),
250 uncertain: AtomicBool::new(false),
251 unsettled_renewal: AtomicBool::new(false),
252 }
253 }
254
255 pub(crate) fn current_status(&self, gate: &Gate) -> RegistrationStatus {
259 let mut status = lock(&self.status).clone();
260 if self.health_revocation.load(Ordering::Acquire)
262 != self.connection.revocation.load(Ordering::Acquire)
263 {
264 status.valid_until = None;
265 if matches!(
266 status.state,
267 RegistrationState::Active | RegistrationState::PendingHealth
268 ) {
269 status.state = RegistrationState::Suspended;
270 status.error = Some(DiscoveryError::new(
271 ErrorCode::Unauthenticated,
272 "registration evidence belongs to a revoked identity",
273 ));
274 }
275 }
276 if !self.spec.eligibility.is_ready() || gate.failed || gate.close_started {
277 status.valid_until = None;
278 }
279 status
280 }
281}
282
283struct Admission<'a> {
284 core: &'a Core,
285 record: &'a Record,
286}
287impl WriteAdmission for Admission<'_> {
288 fn submit(&self, submit: &mut dyn FnMut()) -> Result<()> {
292 let (core, record) = (self.core, self.record);
293 let gate = lock(&core.gate);
294 let eligible = lock(&record.spec.eligibility.inner);
295 if gate.failed
296 || gate.close_started
297 || !matches!(gate.phase, OwnerPhase::Starting | OwnerPhase::Serving)
298 || !record.spec.eligibility.current(*eligible)
299 || core.registrations_stop.is_cancelled()
300 {
301 return Err(closed());
302 }
303 record.attempted.store(true, Ordering::Release);
305 record.uncertain.store(true, Ordering::Release);
306 submit();
307 Ok(())
308 }
309}
310
311pub(crate) async fn run(core: Arc<Core>, record: Arc<Record>) {
315 let mut eligibility = record.spec.eligibility.changes.subscribe();
316 let mut prepared = record.connection.prepared.subscribe();
317 let admission = Admission {
318 core: &core,
319 record: &record,
320 };
321 let monitor = millis(record.spec.policy.monitor_interval_ms);
322 let interval = record
323 .connection
324 .provider
325 .renewal_interval(&record.spec.plan)
326 .unwrap_or(monitor)
327 .min(monitor);
328 let idempotent_renewal = record
329 .connection
330 .provider
331 .supports_idempotent_renewal(&record.spec.plan);
332 let mut registered = false;
333 let mut renewal_allowed = false;
334 let mut registration_blocked = false;
335 let mut backoff = record.connection.policy.initial_backoff_ms;
336 let mut attempt = 0u64;
337 loop {
338 let mut renewal_failed = false;
339 if core.registrations_stop.is_cancelled() {
340 return;
341 }
342 if !record.spec.eligibility.is_ready() || !*prepared.borrow() {
343 if registered {
344 set_state(&core, &record, RegistrationState::Suspended, None);
346 withdraw(
347 record.clone(),
348 Instant::now() + millis(record.spec.policy.deregister_timeout_ms),
349 millis(record.spec.policy.deregister_timeout_ms),
350 )
351 .await;
352 registered = false;
353 registration_blocked |= lock(&record.status).state != RegistrationState::Removed;
354 changed(&core);
355 }
356 tokio::select! {
357 _ = core.registrations_stop.cancelled() => return,
358 _ = eligibility.changed() => {},
359 _ = prepared.changed() => {},
360 _ = tokio::time::sleep(interval) => {},
361 }
362 continue;
363 }
364 if !registered && !registration_blocked {
365 set_state(&core, &record, RegistrationState::Registering, None);
366 let result = operation(
367 &core,
368 Instant::now() + millis(record.connection.policy.request_timeout_ms),
369 record
370 .connection
371 .provider
372 .register(&record.spec.plan, &admission),
373 )
374 .await;
375 match result {
376 Ok(()) => {
377 record.uncertain.store(false, Ordering::Release);
378 registered = true;
379 renewal_allowed = true;
380 set_state(&core, &record, RegistrationState::PendingHealth, None);
381 }
382 Err(error) => {
383 {
384 let gate = lock(&core.gate);
385 revoke_identity(&core, &gate, &record.connection, &error);
386 }
387 if definite_rejection(&error) {
388 record.uncertain.store(false, Ordering::Release);
389 }
390 registration_blocked = record.uncertain.load(Ordering::Acquire)
391 || matches!(
392 error.code,
393 ErrorCode::InvalidConfig | ErrorCode::Unsupported
394 );
395 set_state(
397 &core,
398 &record,
399 if record.uncertain.load(Ordering::Acquire) {
400 RegistrationState::OutcomeUnknown
401 } else {
402 RegistrationState::Suspended
403 },
404 Some(error),
405 );
406 }
407 }
408 }
409 if registered
410 && renewal_allowed
411 && (!record.uncertain.load(Ordering::Acquire) || idempotent_renewal)
413 && record
414 .connection
415 .provider
416 .renewal_interval(&record.spec.plan)
417 .is_some()
418 {
419 let result = operation(
420 &core,
421 Instant::now() + millis(record.connection.policy.request_timeout_ms),
422 record
423 .connection
424 .provider
425 .renew(&record.spec.plan, true, &admission),
426 )
427 .await;
428 match result {
429 Ok(()) => record.uncertain.store(false, Ordering::Release),
431 Err(error) => {
432 renewal_failed = true;
433 {
434 let gate = lock(&core.gate);
435 revoke_identity(&core, &gate, &record.connection, &error);
436 }
437 if definite_rejection(&error) {
438 record.uncertain.store(false, Ordering::Release);
439 }
440 if record.uncertain.load(Ordering::Acquire) {
441 record.unsettled_renewal.store(true, Ordering::Release);
443 renewal_allowed = idempotent_renewal && !error.revokes_evidence();
444 set_state(
445 &core,
446 &record,
447 RegistrationState::OutcomeUnknown,
448 Some(error),
449 );
450 } else {
451 renewal_allowed = false;
452 let mut status = lock(&record.status);
453 if error.revokes_evidence() {
454 status.valid_until = None;
455 status.state = RegistrationState::Suspended;
456 }
457 status.error = Some(error);
458 }
459 }
460 }
461 }
462 if registered && !record.uncertain.load(Ordering::Acquire) {
463 let revocation = record.connection.revocation.load(Ordering::Acquire);
464 let result = operation(
465 &core,
466 Instant::now() + millis(record.connection.policy.request_timeout_ms),
467 record.connection.provider.inspect(&record.spec.plan),
468 )
469 .await;
470 let gate = lock(&core.gate);
471 if core.registrations_stop.is_cancelled() || gate.close_started {
472 return;
473 }
474 if let Err(error) = &result {
475 if error.revokes_evidence() {
476 renewal_allowed = false;
477 }
478 revoke_identity(&core, &gate, &record.connection, error);
479 }
480 if revocation == record.connection.revocation.load(Ordering::Acquire) {
482 let mut status = lock(&record.status);
483 match result {
484 Ok(RegistrationEvidence::Healthy) if record.spec.eligibility.is_ready() => {
485 registration_blocked = false;
486 renewal_allowed = true;
487 let now = Instant::now();
488 status.state = RegistrationState::Active;
489 status.observed_at = Some(now);
490 status.valid_until =
491 Some(now + millis(record.spec.policy.observation_grace_ms));
492 status.error = None;
493 status.startup_confirmed = true;
494 record.confirmed.store(true, Ordering::Release);
495 record
496 .health_revocation
497 .store(revocation, Ordering::Release);
498 }
499 Ok(RegistrationEvidence::Disabled | RegistrationEvidence::Missing) => {
500 renewal_allowed = false;
502 registration_blocked = true;
503 status.state = RegistrationState::Suspended;
504 status.valid_until = None;
505 status.error = Some(DiscoveryError::new(
506 ErrorCode::NotReady,
507 "registration was disabled or removed",
508 ));
509 }
510 Ok(_) => {
511 renewal_allowed = true;
512 status.state = RegistrationState::PendingHealth;
513 status.valid_until = None;
514 status.error = None;
515 }
516 Err(error) => {
517 if error.revokes_evidence() {
519 status.state = RegistrationState::Suspended;
520 status.valid_until = None;
521 renewal_allowed = false;
522 }
523 status.error = Some(error);
524 }
525 }
526 }
527 }
528 changed(&core);
529 let delay = if registered && !renewal_failed {
530 backoff = record.connection.policy.initial_backoff_ms;
531 attempt = 0;
532 interval
533 } else {
534 let delay = crate::retry_delay(&core, &record.connection, backoff, attempt);
535 attempt = attempt.wrapping_add(1);
536 backoff = backoff
537 .saturating_mul(2)
538 .min(record.connection.policy.max_backoff_ms);
539 delay.max(interval)
541 };
542 let next_attempt = Instant::now() + delay;
543 loop {
544 tokio::select! {
545 _ = core.registrations_stop.cancelled() => return,
546 _ = eligibility.changed() => if !record.spec.eligibility.is_ready() { break; },
548 _ = tokio::time::sleep_until(next_attempt) => break,
549 }
550 }
551 }
552}
553
554fn revoke_identity(core: &Core, gate: &Gate, connection: &Connection, error: &DiscoveryError) {
558 if matches!(
559 error.code,
560 ErrorCode::Unauthenticated | ErrorCode::PermissionDenied | ErrorCode::PermissionFiltered
561 ) {
562 crate::revoke_connection(core, gate, connection);
563 }
564}
565
566fn definite_rejection(error: &DiscoveryError) -> bool {
570 matches!(
571 error.code,
572 ErrorCode::InvalidConfig
573 | ErrorCode::Unauthenticated
574 | ErrorCode::PermissionDenied
575 | ErrorCode::Conflict
576 | ErrorCode::Unsupported
577 )
578}
579
580fn set_state(
584 core: &Core,
585 record: &Record,
586 state: RegistrationState,
587 error: Option<DiscoveryError>,
588) {
589 let mut status = lock(&record.status);
590 status.state = state;
591 status.error = error;
592 if state != RegistrationState::Active {
593 status.valid_until = None;
594 }
595 drop(status);
596 changed(core);
597}
598
599pub(crate) async fn withdraw(record: Arc<Record>, deadline: Instant, share: Duration) {
603 if !record.attempted.load(Ordering::Acquire) {
604 lock(&record.status).state = RegistrationState::Removed;
605 return;
606 }
607 if lock(&record.status).state == RegistrationState::Removed {
608 return;
609 }
610 let uncertain = record.uncertain.load(Ordering::Acquire)
612 || record.unsettled_renewal.load(Ordering::Acquire);
613 lock(&record.status).state = RegistrationState::Withdrawing;
614 let until =
615 deadline.min(Instant::now() + share.min(millis(record.spec.policy.deregister_timeout_ms)));
616 let result = tokio::time::timeout_at(
617 until,
618 record.connection.provider.deregister(&record.spec.plan),
619 )
620 .await
621 .unwrap_or_else(|_| Err(timeout()));
622 let mut status = lock(&record.status);
623 status.valid_until = None;
624 match result {
625 Ok(()) if !uncertain => {
626 status.state = RegistrationState::Removed;
627 status.error = None;
628 record.attempted.store(false, Ordering::Release);
629 }
630 Ok(()) => {
631 status.state = RegistrationState::Residual;
632 status.error = Some(DiscoveryError::new(
633 ErrorCode::OutcomeUnknown,
634 "an earlier write may still complete after cleanup",
635 ));
636 }
637 Err(error) => {
638 record.uncertain.store(true, Ordering::Release);
639 status.state = RegistrationState::Residual;
640 status.error = Some(error);
641 }
642 }
643}
644
645pub(crate) fn finish_withdrawal(record: &Record) {
649 let mut status = lock(&record.status);
650 status.valid_until = None;
651 if !record.attempted.load(Ordering::Acquire) {
652 status.state = RegistrationState::Removed;
653 } else if !matches!(
654 status.state,
655 RegistrationState::Removed | RegistrationState::Residual
656 ) {
657 status.state = RegistrationState::Residual;
658 status.error = Some(DiscoveryError::new(
659 ErrorCode::OutcomeUnknown,
660 "registration cleanup did not complete",
661 ));
662 }
663}