use std::sync::Arc;
use std::time::Duration;
use rocketmq_runtime::ChildServiceContext;
use rocketmq_runtime::MetadataDeadline;
use rocketmq_runtime::MetadataIoActor;
use rocketmq_runtime::ShutdownDeadline;
use tokio::sync::Mutex;
use crate::provider_owner::ProviderOwner;
use crate::AuthFailureKind;
use crate::AuthOperation;
use crate::AuthServiceError;
use crate::AuthServiceResult;
pub struct AuthStartupFailure {
primary: AuthServiceError,
cleanup_error: Option<AuthServiceError>,
cleanup: StartupCleanup,
}
impl AuthStartupFailure {
pub fn primary(&self) -> &AuthServiceError {
&self.primary
}
pub fn cleanup_error(&self) -> Option<&AuthServiceError> {
self.cleanup_error.as_ref()
}
pub async fn retry_cleanup_until(&self, deadline: ShutdownDeadline) -> AuthServiceResult<()> {
self.cleanup.run(deadline).await
}
}
impl std::fmt::Debug for AuthStartupFailure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AuthStartupFailure")
.field("primary", &self.primary)
.field("cleanup_error", &self.cleanup_error)
.finish_non_exhaustive()
}
}
impl std::fmt::Display for AuthStartupFailure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("authentication runtime startup failed")
}
}
impl std::error::Error for AuthStartupFailure {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.primary)
}
}
struct StartupCleanup {
provider: Option<Arc<ProviderOwner>>,
metadata_io: Option<MetadataIoActor>,
context: ChildServiceContext,
completed: Mutex<bool>,
}
impl StartupCleanup {
async fn run(&self, deadline: ShutdownDeadline) -> AuthServiceResult<()> {
let at = tokio::time::Instant::from_std(deadline.instant());
let timeout = || AuthServiceError::new(AuthOperation::MaintainService, AuthFailureKind::Timeout);
let mut completed = tokio::time::timeout_at(at, self.completed.lock())
.await
.map_err(|_| timeout())?;
if *completed {
return Ok(());
}
if let Some(provider) = &self.provider {
provider.shutdown_until(deadline, false).await?;
}
if let Some(metadata_io) = &self.metadata_io {
let report = metadata_io.shutdown_until(MetadataDeadline::at(at)).await;
if report.timed_out || report.pending_operations != 0 {
return Err(timeout());
}
}
let report = self.context.task_group().shutdown(deadline.remaining()).await;
if !report.is_healthy() {
return Err(timeout());
}
*completed = true;
Ok(())
}
}
pub(super) async fn rollback(
primary: AuthServiceError,
provider: Option<Arc<ProviderOwner>>,
metadata_io: Option<MetadataIoActor>,
context: ChildServiceContext,
) -> AuthServiceError {
let cleanup = StartupCleanup {
provider,
metadata_io,
context,
completed: Mutex::new(false),
};
let cleanup_error = cleanup.run(ShutdownDeadline::after(Duration::from_secs(5))).await.err();
AuthServiceError::with_source(
primary.operation(),
primary.kind(),
AuthStartupFailure {
primary,
cleanup_error,
cleanup,
},
)
}