blob: 661c34b9ce97364ad38e039bc4bdb1fe28a392c9 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//! The shell vocabulary.
//!
//! The shard/metadata type aliases the dispatch layer is generic over, the
//! [`ShellBus`] bound, the [`ShellHandlers`] slot struct, and the
//! `[cluster]` timer-to-tick translation every consensus group boots with.
//! Everything here is type- and config-level; construction (wiring the
//! handlers against a live bus) stays in [`crate::boot`].
use crate::session_manager::SessionManager;
use configs::server::ServerConfig;
use consensus::{ConsensusTimers, VsrConsensus};
use iggy_common::variadic;
use journal::prepare_journal::PrepareJournal;
use journal::superblock::PingPongSuperblock;
use message_bus::client_listener::RequestHandler;
use message_bus::replica::listener::MessageHandler;
use message_bus::{ConnectionInstaller, IggyMessageBus, MessageBus};
use metadata::IggyMetadata;
use metadata::MuxStateMachine;
use metadata::impls::metadata::IggySnapshot;
use metadata::stm::mux::WithFactory;
use metadata::stm::stream::Streams;
use metadata::stm::user::Users;
use shard::shards_table::PapayaShardsTable;
use shard::{IggyShard, ListClientsHandler, MetadataSubmitHandler, PartitionReadHandler};
use std::cell::RefCell;
use std::rc::{Rc, Weak};
use std::time::Duration;
pub(crate) type ServerMuxStateMachine = MuxStateMachine<variadic!(Users, Streams)>;
/// Cross-thread bundle carrying one `ReadHandleFactory` per metadata
/// state. Shard 0 mints one after `recover()` and broadcasts a clone to
/// every peer shard; each peer rebuilds a reader-mode
/// [`ServerMuxStateMachine`] on its own runtime, skipping the WAL.
pub(crate) type ServerMetadataBundle = <variadic!(Users, Streams) as WithFactory>::Bundle;
pub(crate) type ServerMetadata = IggyMetadata<
VsrConsensus<Rc<IggyMessageBus>>,
PrepareJournal,
IggySnapshot,
ServerMuxStateMachine,
>;
/// The shard type the dispatch layer is generic over.
///
/// `B`/`MJ`/`S`/`SB` are free; the metadata state machine (`M`) and shards
/// table (`T`) are pinned, being identical in production and the simulator.
/// Production instantiates it as [`ServerShard`], defaulting `SB` to the
/// on-disk [`PingPongSuperblock`]; the simulator supplies its own
/// `B`/`MJ`/`S`/`SB`.
pub type ShellShard<B, MJ, S, SB = PingPongSuperblock> =
IggyShard<B, MJ, S, ServerMuxStateMachine, PapayaShardsTable, SB>;
/// Late-bound self-reference the deferred dispatch handlers upgrade per frame.
pub type ShellShardHandle<B, MJ, S, SB = PingPongSuperblock> =
Rc<RefCell<Option<Weak<ShellShard<B, MJ, S, SB>>>>>;
/// Bus bounds the dispatch/pump path needs (matches `run_message_pump`).
/// Blanket-impl'd, so it is only shorthand for the four underlying bounds.
pub trait ShellBus: MessageBus + ConnectionInstaller + Clone + 'static {}
impl<B: MessageBus + ConnectionInstaller + Clone + 'static> ShellBus for B {}
/// The five dispatch handlers a shard is built with, plus the
/// [`SessionManager`] the request-plane pair shares.
///
/// Both production (`build_shard_for_thread`) and the simulator's shell
/// mode construct these through [`crate::boot::wire_shell_handlers`],
/// so the request plane is wired one way. The simulator's shell-off fast
/// path uses [`ShellHandlers::noop`] instead.
pub struct ShellHandlers {
pub on_replica_message: MessageHandler,
pub on_client_request: RequestHandler,
pub on_metadata_submit: MetadataSubmitHandler,
pub on_list_clients: ListClientsHandler,
pub on_partition_read: PartitionReadHandler,
/// Bound by the client-request handler, read by the get-clients
/// handler; the caller keeps it to reach locally-homed sessions.
pub sessions: Rc<RefCell<SessionManager>>,
}
impl ShellHandlers {
/// Inert handlers for the shell-off fast path: every callback is a
/// no-op over an empty [`SessionManager`]. Behaviorally identical to
/// hand-written no-op closures, so a caller can keep one destructure
/// site across both toggle states.
#[must_use]
pub fn noop() -> Self {
Self {
on_replica_message: Rc::new(|_, _| {}),
on_client_request: Rc::new(|_, _| {}),
on_metadata_submit: Rc::new(|_| {}),
on_list_clients: Rc::new(|_| {}),
on_partition_read: Rc::new(|_, _, _| {}),
sessions: Rc::new(RefCell::new(SessionManager::new())),
}
}
}
pub type ServerShard = ShellShard<Rc<IggyMessageBus>, PrepareJournal, IggySnapshot>;
/// Convert a consensus-timer interval to whole ticks, floored at one tick so a
/// sub-tick value still fires and saturated on overflow.
fn duration_to_ticks(interval: Duration) -> u64 {
let ticks = interval.as_millis() / shard::CONSENSUS_TICK_INTERVAL.as_millis();
u64::try_from(ticks.max(1)).unwrap_or(u64::MAX)
}
/// `[cluster] heartbeat_timeout` in consensus ticks. Every consensus group
/// (metadata and per-partition planes alike) gets the same window: the failure
/// it guards against - a primary that stopped heartbeating - is host-level, not
/// per-plane.
pub(crate) fn cluster_heartbeat_ticks(config: &ServerConfig) -> u64 {
duration_to_ticks(config.cluster.heartbeat_timeout.get_duration())
}
/// `[cluster] commit_broadcast_interval` in consensus ticks: how often the
/// primary broadcasts its commit point, the cluster's liveness feed. Applied
/// to every consensus group, matching `cluster_heartbeat_ticks`.
pub(crate) fn commit_broadcast_ticks(config: &ServerConfig) -> u64 {
duration_to_ticks(config.cluster.commit_broadcast_interval.get_duration())
}
/// `[cluster] prepare_retransmit_interval` in consensus ticks: how often the
/// primary retransmits un-acked prepares. Applied to every consensus group,
/// matching `cluster_heartbeat_ticks`.
pub(crate) fn prepare_retransmit_ticks(config: &ServerConfig) -> u64 {
duration_to_ticks(config.cluster.prepare_retransmit_interval.get_duration())
}
/// `[cluster] view_change_retransmit_interval` in consensus ticks: how often a
/// replica retransmits its `StartViewChange` / `DoViewChange` during a view
/// change. Applied to every consensus group, matching `cluster_heartbeat_ticks`.
pub(crate) fn view_change_retransmit_ticks(config: &ServerConfig) -> u64 {
duration_to_ticks(
config
.cluster
.view_change_retransmit_interval
.get_duration(),
)
}
/// `[cluster] view_change_status_timeout` in consensus ticks: the stalled
/// view-change backstop before escalating to a fresh election. Applied to every
/// consensus group, matching `cluster_heartbeat_ticks`.
pub(crate) fn view_change_status_ticks(config: &ServerConfig) -> u64 {
duration_to_ticks(config.cluster.view_change_status_timeout.get_duration())
}
/// `[cluster] request_start_view_retransmit_interval` in consensus ticks: how
/// often a recovering or view-change backup re-requests the current `StartView`.
/// Applied to every consensus group, matching `cluster_heartbeat_ticks`.
pub(crate) fn request_start_view_ticks(config: &ServerConfig) -> u64 {
duration_to_ticks(
config
.cluster
.request_start_view_retransmit_interval
.get_duration(),
)
}
/// The full `[cluster]` timer set every consensus group boots with, built
/// once so the planes cannot diverge in what they apply.
pub(crate) fn consensus_timers(config: &ServerConfig) -> ConsensusTimers {
ConsensusTimers {
normal_heartbeat_ticks: cluster_heartbeat_ticks(config),
commit_message_ticks: commit_broadcast_ticks(config),
prepare_ticks: prepare_retransmit_ticks(config),
view_change_retransmit_ticks: view_change_retransmit_ticks(config),
view_change_status_ticks: view_change_status_ticks(config),
request_start_view_ticks: request_start_view_ticks(config),
probe_attempts_max: config.cluster.view_probe_attempts_max,
}
}
/// `[cluster] repair_retry_interval` in consensus ticks: how long a stalled
/// journal-repair stream waits before re-requesting its window. Both planes'
/// repair loops share it, so it is applied once per shard (not per consensus
/// group). Clamped to `u32`, the width of the session idle-tick counter.
pub(crate) fn repair_retry_ticks(config: &ServerConfig) -> u32 {
u32::try_from(duration_to_ticks(
config.cluster.repair_retry_interval.get_duration(),
))
.unwrap_or(u32::MAX)
}