diff options
Diffstat (limited to 'crates/shirabe')
| -rw-r--r-- | crates/shirabe/src/command/diagnose_command.rs | 9 | ||||
| -rw-r--r-- | crates/shirabe/src/util/http/curl_downloader.rs | 2 | ||||
| -rw-r--r-- | crates/shirabe/src/util/http/proxy_manager.rs | 25 | ||||
| -rw-r--r-- | crates/shirabe/src/util/remote_filesystem.rs | 2 | ||||
| -rw-r--r-- | crates/shirabe/src/util/stream_context_factory.rs | 2 | ||||
| -rw-r--r-- | crates/shirabe/tests/util/http/proxy_manager_test.rs | 27 |
6 files changed, 34 insertions, 33 deletions
diff --git a/crates/shirabe/src/command/diagnose_command.rs b/crates/shirabe/src/command/diagnose_command.rs index e7fb9c74..e457fbb2 100644 --- a/crates/shirabe/src/command/diagnose_command.rs +++ b/crates/shirabe/src/command/diagnose_command.rs @@ -338,7 +338,6 @@ impl Command for DiagnoseCommand { } } - let proxy_manager = ProxyManager::get_instance(); let protos: Vec<&str> = if config.borrow_mut().get("disable-tls").as_bool() == Some(true) { vec!["http"] } else { @@ -346,9 +345,11 @@ impl Command for DiagnoseCommand { }; let proxy_check_result: anyhow::Result<(), anyhow::Error> = (|| -> anyhow::Result<()> { for proto in &protos { - let proxy = proxy_manager - .lock() - .unwrap() + // Compute the proxy under a short-lived lock: `check_http_proxy` below transitively + // re-enters `ProxyManager::get_instance()` (via HttpDownloader -> CurlDownloader / + // RemoteFilesystem), and `std::sync::Mutex` is not reentrant, so the guard must not + // still be held when that call happens. + let proxy = ProxyManager::get_instance() .as_ref() .unwrap() .get_proxy_for_request(&format!("{}://repo.packagist.org", proto)) diff --git a/crates/shirabe/src/util/http/curl_downloader.rs b/crates/shirabe/src/util/http/curl_downloader.rs index 6a6a1d66..f698fc25 100644 --- a/crates/shirabe/src/util/http/curl_downloader.rs +++ b/crates/shirabe/src/util/http/curl_downloader.rs @@ -222,8 +222,6 @@ impl CurlDownloader { // PHP logs the proxy in the "Downloading" line; resolving it here keeps that message // faithful even though reqwest does not yet apply the proxy (see send_once TODO). let using_proxy = ProxyManager::get_instance() - .lock() - .unwrap() .as_ref() .map(|pm| pm.get_proxy_for_request(url)) .transpose() diff --git a/crates/shirabe/src/util/http/proxy_manager.rs b/crates/shirabe/src/util/http/proxy_manager.rs index 3bc0888d..82e8ebc5 100644 --- a/crates/shirabe/src/util/http/proxy_manager.rs +++ b/crates/shirabe/src/util/http/proxy_manager.rs @@ -4,10 +4,10 @@ use crate::downloader::TransportException; use crate::util::NoProxyPattern; use crate::util::http::ProxyItem; use crate::util::http::RequestProxy; +use std::sync::Mutex; use std::sync::atomic::{AtomicU64, Ordering}; -use std::sync::{Mutex, OnceLock}; -static INSTANCE: OnceLock<Mutex<Option<ProxyManager>>> = OnceLock::new(); +static INSTANCE: Mutex<Option<ProxyManager>> = Mutex::new(None); // Distinguishes ProxyManager instances so tests can mirror PHP `===` identity of the singleton, // which the Rust value-based singleton does not otherwise expose. @@ -37,14 +37,25 @@ impl ProxyManager { instance } - pub fn get_instance() -> &'static Mutex<Option<ProxyManager>> { - INSTANCE.get_or_init(|| Mutex::new(Some(ProxyManager::new()))) + // Returns the singleton already locked, rather than the bare `Mutex`, so that ensuring the + // instance is constructed and reading it happen under a single lock. Returning the `Mutex` + // itself would let a caller's own separate `.lock()` race a concurrent `reset()` in the gap + // between the two locks. + pub fn get_instance() -> std::sync::MutexGuard<'static, Option<ProxyManager>> { + // Mirrors PHP `getInstance`'s `if (self::$instance === null) { self::$instance = new self(); }`: + // construction is lazy, so it observes the environment at first use after a `reset` + // rather than at `reset` time. + let mut guard = INSTANCE.lock().unwrap(); + if guard.is_none() { + *guard = Some(ProxyManager::new()); + } + guard } + /// Clears the persistent instance (mirrors PHP `ProxyManager::reset`, which sets + /// `self::$instance = null` rather than eagerly reconstructing it). pub fn reset() { - if let Some(mutex) = INSTANCE.get() { - *mutex.lock().unwrap() = Some(ProxyManager::new()); - } + *INSTANCE.lock().unwrap() = None; } /// For testing only: a unique id per constructed instance, used to mirror PHP `===` identity diff --git a/crates/shirabe/src/util/remote_filesystem.rs b/crates/shirabe/src/util/remote_filesystem.rs index fc61b16f..9b851007 100644 --- a/crates/shirabe/src/util/remote_filesystem.rs +++ b/crates/shirabe/src/util/remote_filesystem.rs @@ -256,7 +256,7 @@ impl RemoteFilesystem { .map_err(|e| anyhow::anyhow!(e))?; let using_proxy = { - let proxy_manager_guard = ProxyManager::get_instance().lock().unwrap(); + let proxy_manager_guard = ProxyManager::get_instance(); let proxy = proxy_manager_guard .as_ref() .expect("ProxyManager instance") diff --git a/crates/shirabe/src/util/stream_context_factory.rs b/crates/shirabe/src/util/stream_context_factory.rs index 43357772..cae00c08 100644 --- a/crates/shirabe/src/util/stream_context_factory.rs +++ b/crates/shirabe/src/util/stream_context_factory.rs @@ -96,7 +96,7 @@ impl StreamContextFactory { // Add stream proxy options if there is a proxy if !for_curl { - let proxy_manager = ProxyManager::get_instance().lock().unwrap(); + let proxy_manager = ProxyManager::get_instance(); let proxy_manager = proxy_manager.as_ref().unwrap(); let proxy = proxy_manager.get_proxy_for_request(url)?; let proxy_options = proxy.get_context_options(); diff --git a/crates/shirabe/tests/util/http/proxy_manager_test.rs b/crates/shirabe/tests/util/http/proxy_manager_test.rs index 7beafe19..d37a1fd8 100644 --- a/crates/shirabe/tests/util/http/proxy_manager_test.rs +++ b/crates/shirabe/tests/util/http/proxy_manager_test.rs @@ -46,21 +46,18 @@ fn test_instantiation() { // PHP compares object identity (===); the value-based Rust singleton exposes a per-instance // generation id instead, which changes only when a new ProxyManager is constructed. let original_instance = { - let mutex = ProxyManager::get_instance(); - let guard = mutex.lock().unwrap(); + let guard = ProxyManager::get_instance(); guard.as_ref().unwrap().__generation() }; let same_instance = { - let mutex = ProxyManager::get_instance(); - let guard = mutex.lock().unwrap(); + let guard = ProxyManager::get_instance(); guard.as_ref().unwrap().__generation() }; assert_eq!(original_instance, same_instance); ProxyManager::reset(); let new_instance = { - let mutex = ProxyManager::get_instance(); - let guard = mutex.lock().unwrap(); + let guard = ProxyManager::get_instance(); guard.as_ref().unwrap().__generation() }; assert_ne!(same_instance, new_instance); @@ -74,8 +71,7 @@ fn test_get_proxy_for_request_throws_on_bad_proxy_url() { Platform::put_env("http_proxy", "localhost"); ProxyManager::reset(); - let mutex = ProxyManager::get_instance(); - let guard = mutex.lock().unwrap(); + let guard = ProxyManager::get_instance(); let proxy_manager = guard.as_ref().unwrap(); assert!( @@ -126,8 +122,7 @@ fn test_lowercase_overrides_uppercase() { } ProxyManager::reset(); - let mutex = ProxyManager::get_instance(); - let guard = mutex.lock().unwrap(); + let guard = ProxyManager::get_instance(); let proxy = guard.as_ref().unwrap().get_proxy_for_request(url).unwrap(); assert_eq!(expected_url, proxy.get_status(None).unwrap()); } @@ -161,8 +156,7 @@ fn test_cgi_proxy_is_only_used_when_no_http_proxy() { } ProxyManager::reset(); - let mutex = ProxyManager::get_instance(); - let guard = mutex.lock().unwrap(); + let guard = ProxyManager::get_instance(); let proxy = guard .as_ref() .unwrap() @@ -180,8 +174,7 @@ fn test_no_http_proxy_does_not_use_https_proxy() { Platform::put_env("https_proxy", "https://proxy.com:443"); ProxyManager::reset(); - let mutex = ProxyManager::get_instance(); - let guard = mutex.lock().unwrap(); + let guard = ProxyManager::get_instance(); let proxy = guard .as_ref() .unwrap() @@ -198,8 +191,7 @@ fn test_no_https_proxy_does_not_use_http_proxy() { Platform::put_env("http_proxy", "http://proxy.com:80"); ProxyManager::reset(); - let mutex = ProxyManager::get_instance(); - let guard = mutex.lock().unwrap(); + let guard = ProxyManager::get_instance(); let proxy = guard .as_ref() .unwrap() @@ -280,8 +272,7 @@ fn test_get_proxy_for_request() { } ProxyManager::reset(); - let mutex = ProxyManager::get_instance(); - let guard = mutex.lock().unwrap(); + let guard = ProxyManager::get_instance(); let proxy = guard.as_ref().unwrap().get_proxy_for_request(url).unwrap(); assert_eq!(options.as_ref(), proxy.get_context_options()); |
