Skip to content

Commit

Permalink
Experimental use of constructor functions to call init
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sagebind committed May 24, 2020
1 parent 33b7a0b commit 02174e6
Showing 1 changed file with 43 additions and 21 deletions.
64 changes: 43 additions & 21 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ extern crate schannel;

use std::ffi::CStr;
use std::str;
use std::sync::Once;
use std::sync::{atomic::{AtomicBool, Ordering}, Once};

pub use error::{Error, FormError, MultiError, ShareError};
mod error;
Expand All @@ -81,31 +81,53 @@ mod panic;
/// It's not required to call this before the library is used, but it's
/// recommended to do so as soon as the program starts.
pub fn init() {
/// An exported constructor function. On supported platforms, this will be
/// invoked automatically before the program's `main` is called.
#[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".init_array")]
#[cfg_attr(target_os = "freebsd", link_section = ".init_array")]
#[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
#[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
static INIT_CTOR: extern "C" fn() = init_inner;

/// Used to prevent concurrent calls when initializing after `main` starts.
static INIT: Once = Once::new();
INIT.call_once(|| {
platform_init();
unsafe {
assert_eq!(curl_sys::curl_global_init(curl_sys::CURL_GLOBAL_ALL), 0);
}

// Note that we explicitly don't schedule a call to
// `curl_global_cleanup`. The documentation for that function says
//
// > You must not call it when any other thread in the program (i.e. a
// > thread sharing the same memory) is running. This doesn't just mean
// > no other thread that is using libcurl.
// We invoke our init function through our static to ensure the symbol isn't
// optimized away due to a rustc bug:
// https://github.com/rust-lang/rust/issues/47384
INIT.call_once(|| INIT_CTOR());

#[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".text.startup")]
extern "C" fn init_inner() {
// We can't rely on just a `Once` for initialization, because
// constructor functions are called before the Rust runtime starts, and
// `Once` relies on facilities provided by the Rust runtime. So we
// additionally protect against multiple initialize calls with this
// atomic.
//
// We can't ever be sure of that, so unfortunately we can't call the
// function.
});
// If the current platform does not support constructor functions, then
// we will rely on the `Once` to call initialization.
static IS_INIT: AtomicBool = AtomicBool::new(false);

#[cfg(need_openssl_init)]
fn platform_init() {
openssl_sys::init();
}
if !IS_INIT.compare_and_swap(false, true, Ordering::Acquire) {
#[cfg(need_openssl_init)]
openssl_sys::init();

unsafe {
assert_eq!(curl_sys::curl_global_init(curl_sys::CURL_GLOBAL_ALL), 0);
}

#[cfg(not(need_openssl_init))]
fn platform_init() {}
// Note that we explicitly don't schedule a call to
// `curl_global_cleanup`. The documentation for that function says
//
// > You must not call it when any other thread in the program (i.e.
// > a thread sharing the same memory) is running. This doesn't just
// > mean no other thread that is using libcurl.
//
// We can't ever be sure of that, so unfortunately we can't call the
// function.
}
}
}

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

0 comments on commit 02174e6

Please sign in to comment.