diff --git a/backend/crates/gateway-store/src/postgres/proxies.rs b/backend/crates/gateway-store/src/postgres/proxies.rs index f92ef58e9..76c5280d7 100644 --- a/backend/crates/gateway-store/src/postgres/proxies.rs +++ b/backend/crates/gateway-store/src/postgres/proxies.rs @@ -441,10 +441,18 @@ impl ProxyStore for PgProxyRepository { if !acquired { return Err(store_error(conflict(id))); } - let record = self.get(id).await?; - if !record.last_test.is_some_and(|test| test.success) { - return Err(store_error(conflict(id))); - } + let record = match self.get(id).await { + Ok(record) if record.last_test.as_ref().is_some_and(|test| test.success) => record, + result => { + // 拒绝预留时先等待数据库释放锁,避免连接关闭尚未生效就误挡后续代理操作。 + sqlx::query("select pg_advisory_unlock_shared(hashtextextended($1, 739219))") + .bind(id) + .execute(&mut connection) + .await + .map_err(|_| store_error(unavailable()))?; + return Err(result.err().unwrap_or_else(|| store_error(conflict(id)))); + } + }; Ok(ProxyImportReservation { binding: ImportProxyBinding { id: record.id, diff --git a/backend/crates/gateway-store/tests/postgres/proxies.rs b/backend/crates/gateway-store/tests/postgres/proxies.rs index c81b87d64..901e0a30f 100644 --- a/backend/crates/gateway-store/tests/postgres/proxies.rs +++ b/backend/crates/gateway-store/tests/postgres/proxies.rs @@ -473,6 +473,47 @@ async fn proxy_accounts_paginate_thousands_of_accounts_and_search_without_loadin database.close().await; } +#[tokio::test] +async fn rejected_import_reservations_release_proxy_lock_before_returning() { + let Some(database) = TestDatabase::create("rejected_proxy_import").await else { + return; + }; + let store = PgProxyRepository::new(database.pool.clone()); + let other_process = PgProxyRepository::new(database.pool.clone()); + let context = context(); + let saved = store + .create( + NewProxy { + location: None, + name: "未通过检测的出口".to_owned(), + proxy: OutboundProxy::parse("http://127.0.0.1:8080").unwrap(), + }, + &context, + ) + .await + .unwrap() + .record; + + // 每次失败后立即写入检测结果,不等待连接关闭或重试锁冲突。 + for _ in 0..64 { + let error = store.reserve_import(&saved.id).await.err().unwrap(); + assert_eq!(error.kind(), AdminStoreErrorKind::Conflict); + other_process + .record_test( + &saved.id, + saved.revision, + ProxyTestResult { + success: false, + ..success() + }, + &context, + ) + .await + .unwrap(); + } + database.close().await; +} + #[tokio::test] async fn import_reservation_blocks_proxy_mutations_until_rotated_credentials_are_committed() { use gateway_store::postgres::{