refactor(actor): implement replica sync and ref update notification system
- Add is_write parameter to remote clients for read/write routing distinction - Introduce RepoEntry struct with role tracking (primary/replica) for repositories - Replace HashSet with HashMap for repository storage with role metadata - Add ROLE_PRIMARY and ROLE_REPLICA constants for node role identification - Implement FindPrimary and FindReplica RPC methods for role-based routing - Add RefUpdateEvent message type for propagating reference updates - Create sync module with BundleApplicator for handling replica synchronization - Implement notify_ref_update calls after branch/tag/commit operations - Add broadcast_ref_update function to propagate events across cluster nodes - Modify route_repository to prioritize primary for writes and replicas for reads - Update actor message handling to support role-based repository discovery - Implement sync_from_primary function using pack protocol for incremental updates
This commit is contained in:
+73
-7
@@ -29,11 +29,12 @@ use crate::pb::{
|
||||
pub struct GitksService {
|
||||
pub repo_prefix: PathBuf,
|
||||
pub node_actor: Option<ActorRef<GitNodeMessage>>,
|
||||
pub grpc_addr: String,
|
||||
}
|
||||
|
||||
impl GitksService {
|
||||
pub fn new(repo_prefix: PathBuf) -> Self {
|
||||
Self { repo_prefix, node_actor: None }
|
||||
Self { repo_prefix, node_actor: None, grpc_addr: String::new() }
|
||||
}
|
||||
|
||||
pub fn with_actor(mut self, node_actor: ActorRef<GitNodeMessage>) -> Self {
|
||||
@@ -41,6 +42,11 @@ impl GitksService {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_grpc_addr(mut self, grpc_addr: String) -> Self {
|
||||
self.grpc_addr = grpc_addr;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn scan_all_repo(&self) -> GitResult<Vec<String>> {
|
||||
let root = self.repo_prefix.as_ref();
|
||||
let mut repos = Vec::new();
|
||||
@@ -57,19 +63,45 @@ impl GitksService {
|
||||
pub async fn route_repository(
|
||||
&self,
|
||||
header: &crate::pb::RepositoryHeader,
|
||||
is_write: bool,
|
||||
) -> Result<Option<RouteDecision>, tonic::Status> {
|
||||
use crate::actor::message::{ROLE_PRIMARY, ROLE_REPLICA};
|
||||
let members = ractor::pg::get_members(&"gitks_nodes".to_string());
|
||||
let local = self.node_actor.as_ref().map(|actor| actor.get_cell());
|
||||
let mut primary: Option<RouteDecision> = None;
|
||||
let mut replica: Option<RouteDecision> = None;
|
||||
for member in members {
|
||||
if local.as_ref().is_some_and(|actor| actor == &member) {
|
||||
continue;
|
||||
}
|
||||
if let Some(decision) = query_route(member, header.clone()).await? {
|
||||
if let Some(decision) = query_find_primary(member.clone(), header.clone()).await? {
|
||||
if decision.found && !decision.grpc_addr.is_empty() {
|
||||
return Ok(Some(decision));
|
||||
primary = Some(decision);
|
||||
if is_write {
|
||||
return Ok(primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
if !is_write && replica.is_none() {
|
||||
if let Some(decision) = query_find_replica(member.clone(), header.clone()).await? {
|
||||
if decision.found && !decision.grpc_addr.is_empty() && decision.role == ROLE_REPLICA {
|
||||
replica = Some(decision);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(p) = primary {
|
||||
return Ok(Some(p));
|
||||
}
|
||||
if let Some(r) = replica {
|
||||
tracing::info!(
|
||||
storage_name = %r.storage_name,
|
||||
relative_path = %r.relative_path,
|
||||
"read request routed to replica"
|
||||
);
|
||||
return Ok(Some(r));
|
||||
}
|
||||
let _ = ROLE_PRIMARY;
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
@@ -127,6 +159,26 @@ impl GitksService {
|
||||
Ok(canonical)
|
||||
}
|
||||
|
||||
pub fn notify_ref_update(
|
||||
&self,
|
||||
relative_path: &str,
|
||||
ref_name: &str,
|
||||
old_oid: &str,
|
||||
new_oid: &str,
|
||||
) {
|
||||
if let Some(ref actor) = self.node_actor {
|
||||
let event = crate::actor::message::RefUpdateEvent {
|
||||
relative_path: relative_path.to_string(),
|
||||
ref_name: ref_name.to_string(),
|
||||
old_oid: old_oid.to_string(),
|
||||
new_oid: new_oid.to_string(),
|
||||
primary_grpc_addr: self.grpc_addr.clone(),
|
||||
primary_storage_name: String::new(),
|
||||
};
|
||||
crate::actor::handler::broadcast_ref_update(actor, event);
|
||||
}
|
||||
}
|
||||
|
||||
/// Inject repo_prefix as storage_path into the client-provided header
|
||||
fn prefixed_header(&self, header: &crate::pb::RepositoryHeader) -> crate::pb::RepositoryHeader {
|
||||
crate::pb::RepositoryHeader {
|
||||
@@ -139,7 +191,7 @@ impl GitksService {
|
||||
|
||||
|
||||
|
||||
pub(super) async fn remote_endpoint(addr: &str) -> Result<tonic::transport::Endpoint, tonic::Status> {
|
||||
pub async fn remote_endpoint(addr: &str) -> Result<tonic::transport::Endpoint, tonic::Status> {
|
||||
let uri: tonic::codegen::http::Uri = addr
|
||||
.parse()
|
||||
.map_err(|e| tonic::Status::invalid_argument(format!("invalid URI: {e}")))?;
|
||||
@@ -162,15 +214,29 @@ pub(super) fn bridge_server_stream<T: Send + 'static>(
|
||||
tokio_stream::wrappers::ReceiverStream::new(rx)
|
||||
}
|
||||
|
||||
async fn query_route(
|
||||
async fn query_find_primary(
|
||||
member: ActorCell,
|
||||
header: crate::pb::RepositoryHeader,
|
||||
) -> Result<Option<RouteDecision>, tonic::Status> {
|
||||
let actor_ref: ActorRef<GitNodeMessage> = member.into();
|
||||
match ractor::call_t!(actor_ref, GitNodeMessage::RouteRepository, 500, header) {
|
||||
match ractor::call_t!(actor_ref, GitNodeMessage::FindPrimary, 500, header) {
|
||||
Ok(decision) => Ok(Some(decision)),
|
||||
Err(err) => {
|
||||
tracing::warn!(error = %err, "repository route query failed");
|
||||
tracing::warn!(error = %err, "find primary query failed");
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn query_find_replica(
|
||||
member: ActorCell,
|
||||
header: crate::pb::RepositoryHeader,
|
||||
) -> Result<Option<RouteDecision>, tonic::Status> {
|
||||
let actor_ref: ActorRef<GitNodeMessage> = member.into();
|
||||
match ractor::call_t!(actor_ref, GitNodeMessage::FindReplica, 500, header) {
|
||||
Ok(decision) => Ok(Some(decision)),
|
||||
Err(err) => {
|
||||
tracing::warn!(error = %err, "find replica query failed");
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user