use std::sync::Arc;
use aion_core::{ActivityId, Payload};
use beamr::native::ProcessContext;
use beamr::term::Term;
use crate::durability::{Command, CorrelationKey, Resolution, ResolveOutcome};
use crate::error::EngineError;
use crate::runtime::nif_activity::{
activity_error, activity_id_from_correlation, error_result_term, ok_result_term,
};
use crate::runtime::nif_activity_dispatch::FIRST_DELIVERY_ATTEMPT;
use crate::runtime::nif_context::NifContext;
pub(super) fn await_activity_result_with_context(
state: &crate::runtime::EngineNifState,
mut context: NifContext,
runtime: &Arc<crate::RuntimeHandle>,
process_context: &mut ProcessContext,
correlation: &str,
) -> Result<Term, Term> {
if let Err(error) = crate::runtime::nif_query_pump::ensure_not_servicing_query(
state,
context.pid(),
"await_activity_result",
) {
return Ok(error_result_term(process_context, &error).unwrap_or(Term::NIL));
}
if let Some(sentinel) =
crate::runtime::nif_query_pump::take_pending_query_sentinel(state, context.pid())
{
return Ok(error_result_term(process_context, &sentinel).unwrap_or(Term::NIL));
}
let activity_id = activity_id_from_correlation(process_context, correlation)?;
let step = await_activity_step(state, &mut context, runtime, &activity_id, || {
super::nif_wake::consume_wake_marker(process_context, runtime);
});
match step {
Ok(ActivityAwaitStep::Completed(bytes)) => {
Ok(ok_result_term(process_context, &bytes).unwrap_or(Term::NIL))
}
Ok(ActivityAwaitStep::Failed(message)) => {
Ok(error_result_term(process_context, &message).unwrap_or(Term::NIL))
}
Ok(ActivityAwaitStep::Suspend) => {
process_context.request_suspend(None);
Ok(Term::NIL)
}
Err(error) => {
Err(error_result_term(process_context, &error.to_string()).unwrap_or(Term::NIL))
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub(super) enum ActivityAwaitStep {
Completed(Vec<u8>),
Failed(String),
Suspend,
}
pub(super) fn await_activity_step(
state: &crate::runtime::EngineNifState,
context: &mut NifContext,
runtime: &crate::RuntimeHandle,
activity_id: &ActivityId,
consume_wake_marker: impl FnOnce(),
) -> Result<ActivityAwaitStep, EngineError> {
if let Some(recorded) = recorded_resolution_for(context, activity_id)? {
return Ok(recorded_step(recorded));
}
consume_wake_marker();
if let Some(step) = take_runtime_completion(context, runtime, activity_id.clone())? {
return Ok(step);
}
if crate::runtime::nif_timeout::expired_scope_deadline(state, context.pid(), context.history())
.is_some()
{
let message = crate::runtime::nif_timeout::SCOPE_EXPIRED_MESSAGE.to_owned();
let attempt =
super::nif_activity_retry::latest_recorded_attempt(context.history(), activity_id)
.unwrap_or(FIRST_DELIVERY_ATTEMPT)
.max(FIRST_DELIVERY_ATTEMPT);
context
.record_activity_failed(
chrono::Utc::now(),
activity_id.clone(),
activity_error(message.clone()),
attempt,
)
.map_err(|error| EngineError::Runtime {
reason: error.error_reason(),
})?;
return Ok(ActivityAwaitStep::Failed(message));
}
Ok(ActivityAwaitStep::Suspend)
}
fn recorded_resolution_for(
context: &mut NifContext,
activity_id: &ActivityId,
) -> Result<Option<Resolution>, EngineError> {
let ordinal = activity_id.sequence_position();
let input =
Payload::from_json(&serde_json::Value::Null).map_err(|error| EngineError::Runtime {
reason: format!("await_activity_result replay input: {error}"),
})?;
match context
.resolve_command(Command::RunActivity {
key: CorrelationKey::Activity(ordinal),
activity_type: "await_activity_result".to_owned(),
input,
})
.map_err(|error| EngineError::Runtime {
reason: error.error_reason(),
})? {
ResolveOutcome::Recorded(resolution) => Ok(Some(resolution)),
ResolveOutcome::ResumeLive => Ok(None),
}
}
fn recorded_step(resolution: Resolution) -> ActivityAwaitStep {
match resolution {
Resolution::ActivityCompleted(payload) => {
ActivityAwaitStep::Completed(payload.bytes().to_vec())
}
Resolution::ActivityFailedTerminal(error) => ActivityAwaitStep::Failed(error.message),
other => ActivityAwaitStep::Failed(format!(
"await_activity_result: recorded non-activity resolution {other:?}"
)),
}
}
fn take_runtime_completion(
context: &NifContext,
runtime: &crate::RuntimeHandle,
activity_id: ActivityId,
) -> Result<Option<ActivityAwaitStep>, EngineError> {
let ordinal = activity_id.sequence_position();
if let Some((payload, attempt)) = runtime.take_activity_result(context.pid(), ordinal)? {
let attempt = attempt.unwrap_or(FIRST_DELIVERY_ATTEMPT);
context
.record_activity_completed(chrono::Utc::now(), activity_id, payload.clone(), attempt)
.map_err(|error| EngineError::Runtime {
reason: error.error_reason(),
})?;
return Ok(Some(ActivityAwaitStep::Completed(payload.bytes().to_vec())));
}
if let Some((error, attempt)) = runtime.take_activity_error(context.pid(), ordinal)? {
let attempt = attempt.unwrap_or(FIRST_DELIVERY_ATTEMPT);
context
.record_activity_failed(
chrono::Utc::now(),
activity_id,
activity_error(error.message.clone()),
attempt,
)
.map_err(|record_error| EngineError::Runtime {
reason: record_error.error_reason(),
})?;
return Ok(Some(ActivityAwaitStep::Failed(error.message)));
}
Ok(None)
}