#[cfg(test)]
use std::collections::HashMap;
use std::sync::atomic::AtomicU64;
use std::sync::Arc;
use ractor::{Actor, ActorProcessingErr, ActorRef};
use super::fragment_state::FragmentState;
use super::kv_worker::handle_worker_message;
use super::pool::release_pending;
use super::protocol::{ApplyError, KvMessage};
pub(super) struct PoolWorker;
pub(super) struct PoolWorkerArgs {
pub(super) shared: Arc<FragmentState>,
pub(super) pool_pending: Arc<AtomicU64>,
pub(super) slot_pending: Arc<AtomicU64>,
#[cfg(test)]
pub(super) startup_gate: Option<WorkerTestGate>,
}
#[cfg(test)]
#[derive(Clone, Default)]
pub(super) struct WorkerTestGate {
pub(super) entered: Arc<tokio::sync::Notify>,
pub(super) release: Arc<tokio::sync::Notify>,
}
#[cfg(test)]
static WORKER_STARTUP_GATES: std::sync::LazyLock<
std::sync::Mutex<HashMap<String, WorkerTestGate>>,
> = std::sync::LazyLock::new(|| std::sync::Mutex::new(HashMap::new()));
#[cfg(test)]
static WORKER_GET_GATES: std::sync::LazyLock<std::sync::Mutex<HashMap<String, WorkerTestGate>>> =
std::sync::LazyLock::new(|| std::sync::Mutex::new(HashMap::new()));
#[cfg(test)]
pub(super) fn install_worker_startup_gate(name: String) -> WorkerTestGate {
let gate = WorkerTestGate::default();
let replaced = WORKER_STARTUP_GATES
.lock()
.expect("worker startup gate registry poisoned")
.insert(name, gate.clone());
assert!(replaced.is_none(), "worker startup gate already installed");
gate
}
#[cfg(test)]
pub(super) fn take_worker_startup_gate(name: &str) -> Option<WorkerTestGate> {
WORKER_STARTUP_GATES
.lock()
.expect("worker startup gate registry poisoned")
.remove(name)
}
#[cfg(test)]
pub(super) fn install_worker_get_gate(key: String) -> WorkerTestGate {
let gate = WorkerTestGate::default();
let replaced = WORKER_GET_GATES
.lock()
.expect("worker Get gate registry poisoned")
.insert(key, gate.clone());
assert!(replaced.is_none(), "worker Get gate already installed");
gate
}
#[cfg(test)]
pub(super) fn take_worker_get_gate(key: &str) -> Option<WorkerTestGate> {
WORKER_GET_GATES
.lock()
.expect("worker Get gate registry poisoned")
.remove(key)
}
impl Actor for PoolWorker {
type Msg = KvMessage;
type State = PoolWorkerArgs;
type Arguments = PoolWorkerArgs;
async fn pre_start(
&self,
_myself: ActorRef<Self::Msg>,
args: Self::Arguments,
) -> Result<Self::State, ActorProcessingErr> {
#[cfg(test)]
if let Some(gate) = &args.startup_gate {
gate.entered.notify_one();
gate.release.notified().await;
}
Ok(args)
}
async fn handle(
&self,
_myself: ActorRef<Self::Msg>,
message: Self::Msg,
state: &mut Self::State,
) -> Result<(), ActorProcessingErr> {
match message {
KvMessage::ApplyOp {
op,
priority: _,
is_replication: _,
reply,
} => {
let result = state.shared.apply(&op).await;
release_pool_reservation(state);
match result {
Ok(outcome) => {
if let Some(reply) = reply {
let _ = reply.send(Ok(outcome));
}
Ok(())
}
Err(error) => {
let fatal = match &error {
ApplyError::Storage(message) => {
Some(ActorProcessingErr::from(message.clone()))
}
ApplyError::Backpressure | ApplyError::InvalidInput(_) => None,
};
if let Some(reply) = reply {
let _ = reply.send(Err(error));
}
match fatal {
Some(error) => Err(error),
None => Ok(()),
}
}
}
}
KvMessage::Get {
key,
reply,
priority: _,
} => {
#[cfg(test)]
if let Some(gate) = take_worker_get_gate(&key) {
gate.entered.notify_one();
gate.release.notified().await;
}
let value = state.shared.get(&key).await;
release_pool_reservation(state);
let _ = reply.send(Ok(value));
Ok(())
}
KvMessage::ContinueBatchRead(mut work) => {
let Some(key) = work.current_key() else {
release_pool_reservation(state);
work.finish();
return Ok(());
};
#[cfg(test)]
if let Some(gate) = take_worker_get_gate(key) {
gate.entered.notify_one();
gate.release.notified().await;
}
let value = state.shared.get(key).await;
release_pool_reservation(state);
work.push(Ok(value));
work.return_to_dispatcher();
Ok(())
}
message => {
let result = handle_worker_message(&state.shared, message).await;
release_pool_reservation(state);
result
}
}
}
}
fn release_pool_reservation(state: &PoolWorkerArgs) {
release_pending(&state.slot_pending, 1);
release_pending(&state.pool_pending, 1);
}