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:
zhenyi
2026-06-08 01:54:08 +08:00
parent 5c99b27421
commit 8c95eb230d
16 changed files with 518 additions and 105 deletions
+4 -3
View File
@@ -6,12 +6,13 @@ use super::{GitksService, cache, into_status};
async fn remote_archive_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
is_write: bool,
) -> Result<Option<ArchiveServiceClient<tonic::transport::Channel>>, tonic::Status> {
let header = match header {
Some(h) => h,
None => return Ok(None),
};
let Some(route) = svc.route_repository(header).await? else {
let Some(route) = svc.route_repository(header, is_write).await? else {
return Ok(None);
};
tracing::info!(storage_name = %route.storage_name, relative_path = %route.relative_path, actor_name = %route.actor_name, grpc_addr = %route.grpc_addr, "forwarding archive rpc");
@@ -38,7 +39,7 @@ impl archive_service_server::ArchiveService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_archive_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_archive_client(self, inner.repository.as_ref(), false).await? {
let resp = client.get_archive(inner).await?;
let stream = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(stream));
@@ -63,7 +64,7 @@ impl archive_service_server::ArchiveService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_archive_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_archive_client(self, inner.repository.as_ref(), false).await? {
return client.list_archive_entries(inner).await;
}
return Err(err);
+4 -3
View File
@@ -6,12 +6,13 @@ use super::{GitksService, cache, into_status, into_stream};
async fn remote_blame_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
is_write: bool,
) -> Result<Option<BlameServiceClient<tonic::transport::Channel>>, tonic::Status> {
let header = match header {
Some(h) => h,
None => return Ok(None),
};
let Some(route) = svc.route_repository(header).await? else {
let Some(route) = svc.route_repository(header, is_write).await? else {
return Ok(None);
};
tracing::info!(storage_name = %route.storage_name, relative_path = %route.relative_path, actor_name = %route.actor_name, grpc_addr = %route.grpc_addr, "forwarding blame rpc");
@@ -39,7 +40,7 @@ impl blame_service_server::BlameService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_blame_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_blame_client(self, inner.repository.as_ref(), false).await? {
return client.blame(inner).await;
}
return Err(err);
@@ -69,7 +70,7 @@ impl blame_service_server::BlameService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_blame_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_blame_client(self, inner.repository.as_ref(), false).await? {
let resp = client.stream_blame(inner).await?;
let stream = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(stream));
+15 -9
View File
@@ -6,12 +6,13 @@ use super::{GitksService, into_status};
async fn remote_branch_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
is_write: bool,
) -> Result<Option<BranchServiceClient<tonic::transport::Channel>>, tonic::Status> {
let header = match header {
Some(h) => h,
None => return Ok(None),
};
let Some(route) = svc.route_repository(header).await? else {
let Some(route) = svc.route_repository(header, is_write).await? else {
return Ok(None);
};
tracing::info!(storage_name = %route.storage_name, relative_path = %route.relative_path, actor_name = %route.actor_name, grpc_addr = %route.grpc_addr, "forwarding branch rpc");
@@ -35,7 +36,7 @@ impl branch_service_server::BranchService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref(), false).await? {
return client.list_branches(inner).await;
}
return Err(err);
@@ -59,7 +60,7 @@ impl branch_service_server::BranchService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref(), false).await? {
return client.get_branch(inner).await;
}
return Err(err);
@@ -82,7 +83,7 @@ impl branch_service_server::BranchService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref(), true).await? {
return client.create_branch(inner).await;
}
return Err(err);
@@ -91,6 +92,7 @@ impl branch_service_server::BranchService for GitksService {
};
let resp = gb.create_branch(inner).map_err(into_status)?;
tracing::info!(%repo, %name, "branch created");
self.notify_ref_update(&repo, &format!("refs/heads/{}", name), "", "");
Ok(tonic::Response::new(resp))
}
@@ -106,7 +108,7 @@ impl branch_service_server::BranchService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref(), true).await? {
return client.delete_branch(inner).await;
}
return Err(err);
@@ -115,6 +117,7 @@ impl branch_service_server::BranchService for GitksService {
};
gb.delete_branch(inner).map_err(into_status)?;
tracing::info!(%repo, %name, "branch deleted");
self.notify_ref_update(&repo, &format!("refs/heads/{}", name), "", "");
Ok(tonic::Response::new(()))
}
@@ -131,7 +134,7 @@ impl branch_service_server::BranchService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref(), true).await? {
return client.rename_branch(inner).await;
}
return Err(err);
@@ -140,6 +143,7 @@ impl branch_service_server::BranchService for GitksService {
};
let resp = gb.rename_branch(inner).map_err(into_status)?;
tracing::info!(%repo, old = %old, new = %new, "branch renamed");
self.notify_ref_update(&repo, &format!("refs/heads/{}", new), "", "");
Ok(tonic::Response::new(resp))
}
@@ -155,7 +159,7 @@ impl branch_service_server::BranchService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref(), true).await? {
return client.update_branch_target(inner).await;
}
return Err(err);
@@ -164,6 +168,7 @@ impl branch_service_server::BranchService for GitksService {
};
let resp = gb.update_branch_target(inner).map_err(into_status)?;
tracing::info!(%repo, %name, "branch target updated");
self.notify_ref_update(&repo, &format!("refs/heads/{}", name), "", "");
Ok(tonic::Response::new(resp))
}
@@ -179,7 +184,7 @@ impl branch_service_server::BranchService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref(), true).await? {
return client.set_branch_upstream(inner).await;
}
return Err(err);
@@ -188,6 +193,7 @@ impl branch_service_server::BranchService for GitksService {
};
let resp = gb.set_branch_upstream(inner).map_err(into_status)?;
tracing::info!(%repo, %name, "branch upstream set");
self.notify_ref_update(&repo, &format!("refs/heads/{}", name), "", "");
Ok(tonic::Response::new(resp))
}
@@ -204,7 +210,7 @@ impl branch_service_server::BranchService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_branch_client(self, inner.repository.as_ref(), false).await? {
return client.compare_branch(inner).await;
}
return Err(err);
+12 -8
View File
@@ -6,12 +6,13 @@ use super::{GitksService, cache, into_status};
async fn remote_commit_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
is_write: bool,
) -> Result<Option<CommitServiceClient<tonic::transport::Channel>>, tonic::Status> {
let header = match header {
Some(h) => h,
None => return Ok(None),
};
let Some(route) = svc.route_repository(header).await? else {
let Some(route) = svc.route_repository(header, is_write).await? else {
return Ok(None);
};
tracing::info!(storage_name = %route.storage_name, relative_path = %route.relative_path, actor_name = %route.actor_name, grpc_addr = %route.grpc_addr, "forwarding commit rpc");
@@ -35,7 +36,7 @@ impl commit_service_server::CommitService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_commit_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_commit_client(self, inner.repository.as_ref(), false).await? {
return client.list_commits(inner).await;
}
return Err(err);
@@ -64,7 +65,7 @@ impl commit_service_server::CommitService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_commit_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_commit_client(self, inner.repository.as_ref(), false).await? {
return client.get_commit(inner).await;
}
return Err(err);
@@ -92,7 +93,7 @@ impl commit_service_server::CommitService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_commit_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_commit_client(self, inner.repository.as_ref(), false).await? {
return client.get_commit_ancestors(inner).await;
}
return Err(err);
@@ -122,7 +123,7 @@ impl commit_service_server::CommitService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_commit_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_commit_client(self, inner.repository.as_ref(), true).await? {
return client.create_commit(inner).await;
}
return Err(err);
@@ -134,6 +135,7 @@ impl commit_service_server::CommitService for GitksService {
.and_then(|c| c.oid.as_ref().map(|o| o.hex.as_str()).or(Some("?")))
.unwrap_or("?");
tracing::info!(%repo, %branch, %commit_hex, "commit created");
self.notify_ref_update(&repo, &format!("refs/heads/{}", branch), "", "");
Ok(tonic::Response::new(resp))
}
@@ -149,7 +151,7 @@ impl commit_service_server::CommitService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_commit_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_commit_client(self, inner.repository.as_ref(), true).await? {
return client.revert_commit(inner).await;
}
return Err(err);
@@ -158,6 +160,7 @@ impl commit_service_server::CommitService for GitksService {
};
let resp = gb.revert_commit(inner).map_err(into_status)?;
tracing::info!(%repo, %branch, "commit reverted");
self.notify_ref_update(&repo, &format!("refs/heads/{}", branch), "", "");
Ok(tonic::Response::new(resp))
}
@@ -173,7 +176,7 @@ impl commit_service_server::CommitService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_commit_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_commit_client(self, inner.repository.as_ref(), true).await? {
return client.cherry_pick_commit(inner).await;
}
return Err(err);
@@ -182,6 +185,7 @@ impl commit_service_server::CommitService for GitksService {
};
let resp = gb.cherry_pick_commit(inner).map_err(into_status)?;
tracing::info!(%repo, %branch, "commit cherry-picked");
self.notify_ref_update(&repo, &format!("refs/heads/{}", branch), "", "");
Ok(tonic::Response::new(resp))
}
@@ -196,7 +200,7 @@ impl commit_service_server::CommitService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_commit_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_commit_client(self, inner.repository.as_ref(), false).await? {
return client.compare_commits(inner).await;
}
return Err(err);
+6 -5
View File
@@ -6,12 +6,13 @@ use super::{GitksService, cache, into_status, into_stream};
async fn remote_diff_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
is_write: bool,
) -> Result<Option<DiffServiceClient<tonic::transport::Channel>>, tonic::Status> {
let header = match header {
Some(h) => h,
None => return Ok(None),
};
let Some(route) = svc.route_repository(header).await? else {
let Some(route) = svc.route_repository(header, is_write).await? else {
return Ok(None);
};
tracing::info!(storage_name = %route.storage_name, relative_path = %route.relative_path, actor_name = %route.actor_name, grpc_addr = %route.grpc_addr, "forwarding diff rpc");
@@ -38,7 +39,7 @@ impl diff_service_server::DiffService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_diff_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_diff_client(self, inner.repository.as_ref(), false).await? {
return client.get_diff(inner).await;
}
return Err(err);
@@ -67,7 +68,7 @@ impl diff_service_server::DiffService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_diff_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_diff_client(self, inner.repository.as_ref(), false).await? {
return client.get_commit_diff(inner).await;
}
return Err(err);
@@ -96,7 +97,7 @@ impl diff_service_server::DiffService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_diff_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_diff_client(self, inner.repository.as_ref(), false).await? {
let resp = client.get_patch(inner).await?;
let stream = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(stream));
@@ -126,7 +127,7 @@ impl diff_service_server::DiffService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_diff_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_diff_client(self, inner.repository.as_ref(), false).await? {
return client.get_diff_stats(inner).await;
}
return Err(err);
+10 -6
View File
@@ -6,12 +6,13 @@ use super::{GitksService, into_status};
async fn remote_merge_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
is_write: bool,
) -> Result<Option<MergeServiceClient<tonic::transport::Channel>>, tonic::Status> {
let header = match header {
Some(h) => h,
None => return Ok(None),
};
let Some(route) = svc.route_repository(header).await? else {
let Some(route) = svc.route_repository(header, is_write).await? else {
return Ok(None);
};
tracing::info!(storage_name = %route.storage_name, relative_path = %route.relative_path, actor_name = %route.actor_name, grpc_addr = %route.grpc_addr, "forwarding merge rpc");
@@ -35,7 +36,7 @@ impl merge_service_server::MergeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_merge_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_merge_client(self, inner.repository.as_ref(), false).await? {
return client.check_merge(inner).await;
}
return Err(err);
@@ -59,7 +60,7 @@ impl merge_service_server::MergeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_merge_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_merge_client(self, inner.repository.as_ref(), true).await? {
return client.merge(inner).await;
}
return Err(err);
@@ -68,6 +69,7 @@ impl merge_service_server::MergeService for GitksService {
};
let resp = gb.merge(inner).map_err(into_status)?;
tracing::info!(%repo, %target, status = resp.status, "merge done");
self.notify_ref_update(&repo, &format!("refs/heads/{}", target), "", "");
Ok(tonic::Response::new(resp))
}
@@ -82,7 +84,7 @@ impl merge_service_server::MergeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_merge_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_merge_client(self, inner.repository.as_ref(), false).await? {
return client.list_merge_conflicts(inner).await;
}
return Err(err);
@@ -106,7 +108,7 @@ impl merge_service_server::MergeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_merge_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_merge_client(self, inner.repository.as_ref(), true).await? {
return client.resolve_merge_conflicts(inner).await;
}
return Err(err);
@@ -115,6 +117,7 @@ impl merge_service_server::MergeService for GitksService {
};
let resp = gb.resolve_merge_conflicts(inner).map_err(into_status)?;
tracing::info!(%repo, %target, status = resp.status, "merge conflicts resolved");
self.notify_ref_update(&repo, &format!("refs/heads/{}", target), "", "");
Ok(tonic::Response::new(resp))
}
@@ -130,7 +133,7 @@ impl merge_service_server::MergeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_merge_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_merge_client(self, inner.repository.as_ref(), true).await? {
return client.rebase(inner).await;
}
return Err(err);
@@ -139,6 +142,7 @@ impl merge_service_server::MergeService for GitksService {
};
let resp = gb.rebase(inner).map_err(into_status)?;
tracing::info!(%repo, %branch, status = resp.status, "rebase done");
self.notify_ref_update(&repo, &format!("refs/heads/{}", branch), "", "");
Ok(tonic::Response::new(resp))
}
}
+73 -7
View File
@@ -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)
}
}
+9 -8
View File
@@ -9,12 +9,13 @@ use super::{GitksService, into_status};
async fn remote_pack_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
is_write: bool,
) -> Result<Option<PackServiceClient<tonic::transport::Channel>>, tonic::Status> {
let header = match header {
Some(h) => h,
None => return Ok(None),
};
let Some(route) = svc.route_repository(header).await? else {
let Some(route) = svc.route_repository(header, is_write).await? else {
return Ok(None);
};
tracing::info!(storage_name = %route.storage_name, relative_path = %route.relative_path, actor_name = %route.actor_name, grpc_addr = %route.grpc_addr, "forwarding pack rpc");
@@ -42,7 +43,7 @@ impl pack_service_server::PackService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_pack_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_pack_client(self, inner.repository.as_ref(), false).await? {
return client.advertise_refs(inner).await;
}
return Err(err);
@@ -69,7 +70,7 @@ impl pack_service_server::PackService for GitksService {
let gb = match self.resolve(first.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_pack_client(self, first.repository.as_ref()).await? {
if let Some(mut client) = remote_pack_client(self, first.repository.as_ref(), false).await? {
let (tx, rx) = tokio::sync::mpsc::channel(16);
let _ = tx.send(first).await;
tokio::spawn(async move {
@@ -122,7 +123,7 @@ impl pack_service_server::PackService for GitksService {
let gb = match self.resolve(first.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_pack_client(self, first.repository.as_ref()).await? {
if let Some(mut client) = remote_pack_client(self, first.repository.as_ref(), false).await? {
let (tx, rx) = tokio::sync::mpsc::channel(16);
let _ = tx.send(first).await;
tokio::spawn(async move {
@@ -171,7 +172,7 @@ impl pack_service_server::PackService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_pack_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_pack_client(self, inner.repository.as_ref(), false).await? {
let resp = client.pack_objects(inner).await?;
let stream = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(stream));
@@ -200,7 +201,7 @@ impl pack_service_server::PackService for GitksService {
let gb = match self.resolve(inputs.first().and_then(|r| r.repository.as_ref())) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_pack_client(self, inputs.first().and_then(|r| r.repository.as_ref())).await? {
if let Some(mut client) = remote_pack_client(self, inputs.first().and_then(|r| r.repository.as_ref()), false).await? {
return client.index_pack(tokio_stream::iter(inputs)).await;
}
return Err(err);
@@ -223,7 +224,7 @@ impl pack_service_server::PackService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_pack_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_pack_client(self, inner.repository.as_ref(), false).await? {
return client.list_packfiles(inner).await;
}
return Err(err);
@@ -246,7 +247,7 @@ impl pack_service_server::PackService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_pack_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_pack_client(self, inner.repository.as_ref(), false).await? {
return client.fsck(inner).await;
}
return Err(err);
+19 -14
View File
@@ -6,12 +6,13 @@ use super::{GitksService, git_cmd, into_status, repository_maint, remote_endpoin
async fn remote_repository_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
is_write: bool,
) -> Result<Option<RepositoryServiceClient<tonic::transport::Channel>>, tonic::Status> {
let header = match header {
Some(h) => h,
None => return Ok(None),
};
let Some(route) = svc.route_repository(header).await? else {
let Some(route) = svc.route_repository(header, is_write).await? else {
return Ok(None);
};
tracing::info!(storage_name = %route.storage_name, relative_path = %route.relative_path, actor_name = %route.actor_name, grpc_addr = %route.grpc_addr, "forwarding repository rpc");
@@ -47,7 +48,7 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), false).await? {
return client.get_repository(inner).await;
}
return Err(err);
@@ -77,6 +78,7 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = crate::bare::GitBare::new(bare_dir);
gb.init_repository(inner.bare).map_err(into_status)?;
tracing::info!(%repo, bare = inner.bare, "repository initialized");
self.notify_ref_update(&repo, "HEAD", "", "");
Ok(tonic::Response::new(Repository {
header: inner.repository,
bare: inner.bare,
@@ -94,13 +96,14 @@ impl repository_service_server::RepositoryService for GitksService {
let _enter = span.enter();
let bare_dir = self.resolve_for_init(inner.repository.as_ref())?;
if !bare_dir.exists() {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), true).await? {
return client.delete_repository(inner).await;
}
}
tracing::warn!(%repo, path = %bare_dir.display(), "deleting repository");
std::fs::remove_dir_all(&bare_dir).map_err(|e| tonic::Status::internal(e.to_string()))?;
tracing::info!(%repo, "repository deleted");
self.notify_ref_update(&repo, "", "", "");
Ok(tonic::Response::new(()))
}
@@ -115,7 +118,7 @@ impl repository_service_server::RepositoryService for GitksService {
let bare_dir = self.resolve_for_init(inner.repository.as_ref())?;
let exists = bare_dir.exists() && bare_dir.is_dir() && bare_dir.join("HEAD").exists();
if !exists {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), false).await? {
return client.repository_exists(inner).await;
}
}
@@ -133,7 +136,7 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), false).await? {
return client.get_object_format(inner).await;
}
return Err(err);
@@ -156,7 +159,7 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), false).await? {
return client.get_default_branch(inner).await;
}
return Err(err);
@@ -180,7 +183,7 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), true).await? {
return client.set_default_branch(inner).await;
}
return Err(err);
@@ -195,6 +198,7 @@ impl repository_service_server::RepositoryService for GitksService {
));
}
tracing::info!(%repo, %name, "default branch set");
self.notify_ref_update(&repo, &refname, "", "");
Ok(tonic::Response::new(()))
}
@@ -209,7 +213,7 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), false).await? {
return client.get_repository_config(inner).await;
}
return Err(err);
@@ -266,7 +270,7 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), true).await? {
return client.set_repository_config(inner).await;
}
return Err(err);
@@ -286,6 +290,7 @@ impl repository_service_server::RepositoryService for GitksService {
}
}
}
self.notify_ref_update(&repo, "", "", "");
Ok(tonic::Response::new(()))
}
@@ -300,7 +305,7 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), false).await? {
return client.get_repository_statistics(inner).await;
}
return Err(err);
@@ -321,7 +326,7 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), false).await? {
return client.check_repository_health(inner).await;
}
return Err(err);
@@ -344,7 +349,7 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), true).await? {
return client.garbage_collect(inner).await;
}
return Err(err);
@@ -367,7 +372,7 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), true).await? {
return client.repack(inner).await;
}
return Err(err);
@@ -395,7 +400,7 @@ impl repository_service_server::RepositoryService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_repository_client(self, inner.repository.as_ref(), true).await? {
return client.write_commit_graph(inner).await;
}
return Err(err);
+9 -6
View File
@@ -6,12 +6,13 @@ use super::{GitksService, into_status};
async fn remote_tag_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
is_write: bool,
) -> Result<Option<TagServiceClient<tonic::transport::Channel>>, tonic::Status> {
let header = match header {
Some(h) => h,
None => return Ok(None),
};
let Some(route) = svc.route_repository(header).await? else {
let Some(route) = svc.route_repository(header, is_write).await? else {
return Ok(None);
};
tracing::info!(storage_name = %route.storage_name, relative_path = %route.relative_path, actor_name = %route.actor_name, grpc_addr = %route.grpc_addr, "forwarding tag rpc");
@@ -35,7 +36,7 @@ impl tag_service_server::TagService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tag_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_tag_client(self, inner.repository.as_ref(), false).await? {
return client.list_tags(inner).await;
}
return Err(err);
@@ -59,7 +60,7 @@ impl tag_service_server::TagService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tag_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_tag_client(self, inner.repository.as_ref(), false).await? {
return client.get_tag(inner).await;
}
return Err(err);
@@ -82,7 +83,7 @@ impl tag_service_server::TagService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tag_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_tag_client(self, inner.repository.as_ref(), true).await? {
return client.create_tag(inner).await;
}
return Err(err);
@@ -91,6 +92,7 @@ impl tag_service_server::TagService for GitksService {
};
let resp = gb.create_tag(inner).map_err(into_status)?;
tracing::info!(%repo, %name, "tag created");
self.notify_ref_update(&repo, &format!("refs/tags/{}", name), "", "");
Ok(tonic::Response::new(resp))
}
@@ -106,7 +108,7 @@ impl tag_service_server::TagService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tag_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_tag_client(self, inner.repository.as_ref(), true).await? {
return client.delete_tag(inner).await;
}
return Err(err);
@@ -115,6 +117,7 @@ impl tag_service_server::TagService for GitksService {
};
gb.delete_tag(inner).map_err(into_status)?;
tracing::info!(%repo, %name, "tag deleted");
self.notify_ref_update(&repo, &format!("refs/tags/{}", name), "", "");
Ok(tonic::Response::new(()))
}
@@ -130,7 +133,7 @@ impl tag_service_server::TagService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tag_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_tag_client(self, inner.repository.as_ref(), false).await? {
return client.verify_tag(inner).await;
}
return Err(err);
+8 -7
View File
@@ -6,12 +6,13 @@ use super::{GitksService, cache, into_status, into_stream};
async fn remote_tree_client(
svc: &GitksService,
header: Option<&RepositoryHeader>,
is_write: bool,
) -> Result<Option<TreeServiceClient<tonic::transport::Channel>>, tonic::Status> {
let header = match header {
Some(h) => h,
None => return Ok(None),
};
let Some(route) = svc.route_repository(header).await? else {
let Some(route) = svc.route_repository(header, is_write).await? else {
return Ok(None);
};
tracing::info!(storage_name = %route.storage_name, relative_path = %route.relative_path, actor_name = %route.actor_name, grpc_addr = %route.grpc_addr, "forwarding tree rpc");
@@ -38,7 +39,7 @@ impl tree_service_server::TreeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tree_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_tree_client(self, inner.repository.as_ref(), false).await? {
return client.list_tree(inner).await;
}
return Err(err);
@@ -67,7 +68,7 @@ impl tree_service_server::TreeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tree_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_tree_client(self, inner.repository.as_ref(), false).await? {
return client.get_tree(inner).await;
}
return Err(err);
@@ -96,7 +97,7 @@ impl tree_service_server::TreeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tree_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_tree_client(self, inner.repository.as_ref(), false).await? {
return client.get_blob(inner).await;
}
return Err(err);
@@ -124,7 +125,7 @@ impl tree_service_server::TreeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tree_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_tree_client(self, inner.repository.as_ref(), false).await? {
let resp = client.get_raw_blob(inner).await?;
let stream = super::bridge_server_stream(resp.into_inner());
return Ok(tonic::Response::new(stream));
@@ -158,7 +159,7 @@ impl tree_service_server::TreeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tree_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_tree_client(self, inner.repository.as_ref(), false).await? {
return client.get_file_metadata(inner).await;
}
return Err(err);
@@ -186,7 +187,7 @@ impl tree_service_server::TreeService for GitksService {
let gb = match self.resolve(inner.repository.as_ref()) {
Ok(gb) => gb,
Err(err) if err.code() == tonic::Code::NotFound => {
if let Some(mut client) = remote_tree_client(self, inner.repository.as_ref()).await? {
if let Some(mut client) = remote_tree_client(self, inner.repository.as_ref(), false).await? {
return client.find_files(inner).await;
}
return Err(err);