Skip to content

Commit ee68a61

Browse files
authored
Experimental use of constructor functions to call init (#334)
* Experimental use of constructor functions to call init As mentioned in #333, this is a potentially helpful addition that ensures that curl is initialized on the main thread by using constructor functions that get called by the OS before the current program's `main` is called. This has the advantage that, assuming you are on one of the supported platforms, `init()` will be called safely, correctly, and automatically without concerning the user about the gotchas. This does have some disadvantages: - Constructor functions are always invoked, which means that simply including curl can slow down the startup of a user's program even if curl is only conditionally used, or used later in program execution. - On platforms without a constructor implementation, the user still needs to initialize curl on the main thread. All the common platforms are covered though, so maybe this is a niche scenario. There's no additional runtime cost to this implementation except on platforms without a constructor, where we do an extra atomic swap that isn't necessary. This is probably fixable with additional conditional compilation. * Micro-optimization Ensure subsequent init calls are compiled down to a single atomic compare-and-swap. * Consolidate ELF attribute * Reducing to just a Once is acceptable Since it is not possible for the `Once` to have contention in a constructor, it is safe to use it. (Under contention, `Once` requires `std::thread` machinery to work, which is not yet available before `main`.) Also update documentation on initialization.
1 parent 4f5c728 commit ee68a61

File tree

1 file changed

+59
-24
lines changed

1 file changed

+59
-24
lines changed

src/lib.rs

+59-24
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@
4646
//! There is a large number of releases for libcurl, all with different sets of
4747
//! capabilities. Robust programs may wish to inspect `Version::get()` to test
4848
//! what features are implemented in the linked build of libcurl at runtime.
49+
//!
50+
//! # Initialization
51+
//!
52+
//! The underlying libcurl library must be initialized before use and has
53+
//! certain requirements on how this is done. Check the documentation for
54+
//! [`init`] for more details.
4955
5056
#![deny(missing_docs, missing_debug_implementations)]
5157
#![doc(html_root_url = "https://docs.rs/curl/0.4")]
@@ -78,34 +84,63 @@ mod panic;
7884

7985
/// Initializes the underlying libcurl library.
8086
///
81-
/// It's not required to call this before the library is used, but it's
82-
/// recommended to do so as soon as the program starts.
87+
/// The underlying libcurl library must be initialized before use, and must be
88+
/// done so on the main thread before any other threads are created by the
89+
/// program. This crate will do this for you automatically in the following
90+
/// scenarios:
91+
///
92+
/// - Creating a new [`Easy`][easy::Easy] or [`Multi`][multi::Multi] handle
93+
/// - At program startup on Windows, macOS, Linux, Android, or FreeBSD systems
94+
///
95+
/// This should be sufficient for most applications and scenarios, but in any
96+
/// other case, it is strongly recommended that you call this function manually
97+
/// as soon as your program starts.
98+
///
99+
/// Calling this function more than once is harmless and has no effect.
100+
#[inline]
83101
pub fn init() {
102+
/// Used to prevent concurrent or duplicate initialization.
84103
static INIT: Once = Once::new();
85-
INIT.call_once(|| {
86-
platform_init();
87-
unsafe {
88-
assert_eq!(curl_sys::curl_global_init(curl_sys::CURL_GLOBAL_ALL), 0);
89-
}
90-
91-
// Note that we explicitly don't schedule a call to
92-
// `curl_global_cleanup`. The documentation for that function says
93-
//
94-
// > You must not call it when any other thread in the program (i.e. a
95-
// > thread sharing the same memory) is running. This doesn't just mean
96-
// > no other thread that is using libcurl.
97-
//
98-
// We can't ever be sure of that, so unfortunately we can't call the
99-
// function.
100-
});
101-
102-
#[cfg(need_openssl_init)]
103-
fn platform_init() {
104-
openssl_sys::init();
104+
105+
/// An exported constructor function. On supported platforms, this will be
106+
/// invoked automatically before the program's `main` is called.
107+
#[cfg_attr(
108+
any(target_os = "linux", target_os = "freebsd", target_os = "android"),
109+
link_section = ".init_array"
110+
)]
111+
#[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
112+
#[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
113+
static INIT_CTOR: extern "C" fn() = init_inner;
114+
115+
/// This is the body of our constructor function.
116+
#[cfg_attr(
117+
any(target_os = "linux", target_os = "android"),
118+
link_section = ".text.startup"
119+
)]
120+
extern "C" fn init_inner() {
121+
INIT.call_once(|| {
122+
#[cfg(need_openssl_init)]
123+
openssl_sys::init();
124+
125+
unsafe {
126+
assert_eq!(curl_sys::curl_global_init(curl_sys::CURL_GLOBAL_ALL), 0);
127+
}
128+
129+
// Note that we explicitly don't schedule a call to
130+
// `curl_global_cleanup`. The documentation for that function says
131+
//
132+
// > You must not call it when any other thread in the program (i.e.
133+
// > a thread sharing the same memory) is running. This doesn't just
134+
// > mean no other thread that is using libcurl.
135+
//
136+
// We can't ever be sure of that, so unfortunately we can't call the
137+
// function.
138+
});
105139
}
106140

107-
#[cfg(not(need_openssl_init))]
108-
fn platform_init() {}
141+
// We invoke our init function through our static to ensure the symbol isn't
142+
// optimized away by a bug: https://github.com/rust-lang/rust/issues/47384
143+
INIT_CTOR();
109144
}
110145

111146
unsafe fn opt_str<'a>(ptr: *const libc::c_char) -> Option<&'a str> {

0 commit comments

Comments
 (0)