From 73bea06ee1df895f75098116896bf5b02ab836b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C5=ABdolfs=20O=C5=A1i=C5=86=C5=A1?= Date: Tue, 29 Sep 2026 17:24:48 +0200 Subject: [PATCH] Serialize writes in the test HTTP API Parallel e2e tests hit the repo's sigrefs lock when creating COBs concurrently, which the Tauri avoids by running these commands on the main thread serially. --- crates/test-http-api/Cargo.toml | 2 +- crates/test-http-api/src/api.rs | 42 +++++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/crates/test-http-api/Cargo.toml b/crates/test-http-api/Cargo.toml index 7c15b6d90..f8b5b84d4 100644 --- a/crates/test-http-api/Cargo.toml +++ b/crates/test-http-api/Cargo.toml @@ -19,5 +19,5 @@ radicle-types = { path = "../radicle-types" } serde = { version = "1.0.0", features = ["derive"] } serde_json = { version = "1.0.0", features = ["preserve_order"] } thiserror = { version = "2.0.12" } -tokio = { version = "1.45", default-features = false, features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.45", default-features = false, features = ["macros", "rt-multi-thread", "sync"] } tower-http = { version = "0.6.8", default-features = false, features = ["cors", "set-header"] } diff --git a/crates/test-http-api/src/api.rs b/crates/test-http-api/src/api.rs index baf0ba703..e831e855d 100644 --- a/crates/test-http-api/src/api.rs +++ b/crates/test-http-api/src/api.rs @@ -3,12 +3,14 @@ use std::path::PathBuf; use std::sync::Arc; use axum::Router; -use axum::extract::State; -use axum::response::{IntoResponse, Json}; +use axum::extract::{Request, State}; +use axum::middleware::{self, Next}; +use axum::response::{IntoResponse, Json, Response}; use axum::routing::post; use hyper::Method; use hyper::header::CONTENT_TYPE; use serde::{Deserialize, Serialize}; +use tokio::sync::Mutex; use tower_http::cors::{self, CorsLayer}; use radicle::node::NodeId; @@ -73,6 +75,22 @@ impl Context { } pub fn router(ctx: Context) -> Router { + let writes = Router::new() + .route("/seed", post(seed_handler)) + .route("/unseed", post(unseed_handler)) + .route("/clean", post(clean_handler)) + .route("/create_issue", post(create_issue_handler)) + .route("/create_issue_comment", post(create_issue_comment_handler)) + .route("/edit_issue", post(edit_issue_handler)) + .route("/delete_issue", post(delete_issue_handler)) + .route("/edit_patch", post(edit_patch_handler)) + .route("/create_patch_review", post(create_patch_review_handler)) + .route("/delete_patch", post(delete_patch_handler)) + .route_layer(middleware::from_fn_with_state( + Arc::new(Mutex::new(())), + serialize_writes, + )); + Router::new() .route("/config", post(config_handler)) .route("/authenticate", post(auth_handler)) @@ -83,9 +101,6 @@ pub fn router(ctx: Context) -> Router { "/seeded_not_replicated", post(seeded_not_replicated_handler), ) - .route("/seed", post(seed_handler)) - .route("/unseed", post(unseed_handler)) - .route("/clean", post(clean_handler)) .route("/repo_by_id", post(repo_handler)) .route("/list_repo_refs", post(list_repo_refs_handler)) .route("/identity_by_repo", post(identity_handler)) @@ -111,18 +126,11 @@ pub fn router(ctx: Context) -> Router { .route("/repo_commit_count", post(repo_commit_count_handler)) .route("/repo_commit", post(repo_commit_handler)) .route("/list_issues", post(issues_handler)) - .route("/create_issue", post(create_issue_handler)) - .route("/create_issue_comment", post(create_issue_comment_handler)) - .route("/edit_issue", post(edit_issue_handler)) .route("/issue_by_id", post(issue_handler)) .route("/comment_threads_by_issue_id", post(issue_threads_handler)) - .route("/delete_issue", post(delete_issue_handler)) .route("/list_patches", post(patches_handler)) .route("/patch_by_id", post(patch_handler)) .route("/revisions_by_patch", post(revision_handler)) - .route("/edit_patch", post(edit_patch_handler)) - .route("/create_patch_review", post(create_patch_review_handler)) - .route("/delete_patch", post(delete_patch_handler)) .route("/get_embed", post(get_embeds_handler)) .route("/save_embed_by_path", post(save_embed_handler)) .route("/save_embed_by_clipboard", post(save_embed_handler)) @@ -132,6 +140,7 @@ pub fn router(ctx: Context) -> Router { .route("/list_notifications", post(list_notifications_handler)) .route("/notification_count", post(notification_count_handler)) .route("/clear_notifications", post(clear_notifications_handler)) + .merge(writes) .layer( CorsLayer::new() .allow_origin(cors::Any) @@ -141,6 +150,15 @@ pub fn router(ctx: Context) -> Router { .with_state(ctx) } +async fn serialize_writes( + State(lock): State>>, + request: Request, + next: Next, +) -> Response { + let _guard = lock.lock().await; + next.run(request).await +} + async fn config_handler(State(ctx): State) -> impl IntoResponse { let config = ctx.config();