1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
use super::SystemHttpClient;
use crate::Error;
use std::{
	cell::UnsafeCell,
	sync::atomic::{self, AtomicU8},
	time::Duration,
};

const PENDING: u8 = 0;
const BUSY: u8 = 1;
const READY: u8 = 2;

struct GlobalResolvedSystemHttpClient {
	state: AtomicU8,
	value: UnsafeCell<Option<SystemHttpClient>>,
}
impl GlobalResolvedSystemHttpClient {
	fn get() -> Option<SystemHttpClient> {
		static HTTP_CLIENT: GlobalResolvedSystemHttpClient = GlobalResolvedSystemHttpClient {
			state: AtomicU8::new(PENDING),
			value: UnsafeCell::new(None),
		};

		let mut spin_wait = Duration::from_millis(50);
		loop {
			match HTTP_CLIENT.state.fetch_max(BUSY, atomic::Ordering::SeqCst) {
				BUSY => (),
				PENDING => break,
				READY => return unsafe { *HTTP_CLIENT.value.get() },
				_ => unreachable!(),
			}
			std::thread::sleep(spin_wait);
			spin_wait = Duration::max(spin_wait * 2, Duration::from_secs(2));
		}

		let resolved = SystemHttpClient::resolve();

		unsafe { *HTTP_CLIENT.value.get() = resolved };

		HTTP_CLIENT.state.store(READY, atomic::Ordering::Release);

		resolved
	}
}
unsafe impl Sync for GlobalResolvedSystemHttpClient {}

pub(crate) fn resolve() -> Result<SystemHttpClient, Error> {
	match GlobalResolvedSystemHttpClient::get() {
		Some(client) => Ok(client),
		None => Err(Error::SystemHTTPClientNotFound),
	}
}

#[must_use]
/// Returns whether the system has a compatible HTTP client installed
pub fn installed() -> bool {
	GlobalResolvedSystemHttpClient::get().is_some()
}

#[must_use]
/// Returns the system's compatible HTTP client used for requests, if one is installed.
pub fn http_client() -> Option<SystemHttpClient> {
	GlobalResolvedSystemHttpClient::get()
}