Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for curl_easy_upkeep #378

Merged
merged 1 commit into from
Mar 3, 2021
Merged

Add support for curl_easy_upkeep #378

merged 1 commit into from
Mar 3, 2021

Conversation

cmeister2
Copy link
Contributor

Add feature-gated support for curl_easy_upkeep, introduced in 7.62.0


Happy to discuss feature naming, etc! Would be nice to have a model for adding in features that have been introduced within the last 9 years.

@alexcrichton
Copy link
Owner

Thanks for this! I wonder, though, would it be possible to do some quick-and-dirty version detection of the libcurl found at build time? That way we could change this function to always be available and simply return an error unconditionally if the linked version doesn't have support.

@sagebind
Copy link
Collaborator

Even that won't always work though, because if we are linking to libcurl dynamically then the build machine might have a newer version of libcurl than the user's machine. Depending on the system you'll probably get undefined symbol errors when the OS tries to run your program and the only way to opt-out would be to downgrade libcurl to an old enough version on your build machine.

@cmeister2
Copy link
Contributor Author

cmeister2 commented Feb 25, 2021 via email

@cmeister2
Copy link
Contributor Author

I also realised I missed a few files off - let me push those before pushing anything else.

@cmeister2
Copy link
Contributor Author

cmeister2 commented Feb 25, 2021

The code I've written for dynamic loading is this:

    /// Some protocols have "connection upkeep" mechanisms. These mechanisms
    /// usually send some traffic on existing connections in order to keep them
    /// alive; this can prevent connections from being closed due to overzealous
    /// firewalls, for example.
    ///
    /// Currently the only protocol with a connection upkeep mechanism is
    /// HTTP/2: when the connection upkeep interval is exceeded and upkeep() is
    /// called, an HTTP/2 PING frame is sent on the connection.
    #[cfg(not(feature = "static-curl"))]
    pub fn upkeep(&self) -> Result<(), Error> {
        // Derive the curl binary name
        let libname = libloading::library_filename("curl");

        let ret = unsafe {
            let lib = libloading::Library::new(&libname).map_err(|_| {
                let mut e = Error::new(curl_sys::CURLE_FAILED_INIT);
                e.set_extra(format!("Failed to load library: {:?}", libname));
                e
            })?;

            let func = lib
                .get::<unsafe extern "C" fn(*mut curl_sys::CURL) -> curl_sys::CURLcode>(
                    b"curl_easy_upkeep",
                )
                .map_err(|_| {
                    let mut e = Error::new(curl_sys::CURLE_FAILED_INIT);
                    e.set_extra("Failed to find function: curl_easy_upkeep".to_string());
                    e
                })?;

            self.cvt(func(self.inner.handle))
        };

        panic::propagate();
        return ret;
    }

The test_upkeep test fails with

---- test_upkeep stdout ----
thread 'test_upkeep' panicked at 'handle.upkeep() failed with Error { description: "Failed initialization", code: 2, extra: Some("Failed to find function: curl_easy_upkeep") }', tests/easy.rs:849:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

with dynamic libcurl 7.60.0, and passes with dynamic libcurl 7.76.0. "static-curl" curl works as well regardless.

@alexcrichton - thoughts on the above? If you want me to explore using libc::dlsym instead of libloading I can (to reduce the number of dependencies) but afaict libloading makes it ergonomic to use those functions.

Add feature-gated support for curl_easy_upkeep, introduced in 7.62.0
@sagebind
Copy link
Collaborator

sagebind commented Feb 25, 2021

Interesting approach, I'll have to think about this. Some initial thoughts:

  • We should probably return some sort of "your curl does not support this method" error if the symbol cannot be found.
  • We should probably avoid loading libraries on every method call and instead do it just once lazily as needed. Otherwise it could be expensive.
  • There's a potential safety risk here if the libcurl that dynamic loading finds gets a different binary than the one the application is linked to, which could happen if the user has more than one libcurl installed. We'd be calling methods from one libcurl version on a handle created by a different libcurl version.

@cmeister2
Copy link
Contributor Author

cmeister2 commented Feb 25, 2021 via email

@cmeister2
Copy link
Contributor Author

cmeister2 commented Feb 25, 2021 via email

@cmeister2
Copy link
Contributor Author

Seems as if you can do this with libloading, though for some reason this is not exposed on the top level Library in libloading.

    #[cfg(not(feature = "static-curl"))]
    pub fn upkeep(&self) -> Result<(), Error> {
        let ret = unsafe {
            let lib = libloading::os::unix::Library::this();
            let func = lib
                .get::<unsafe extern "C" fn(*mut curl_sys::CURL) -> curl_sys::CURLcode>(
                    b"curl_easy_upkeep",
                )
                .map_err(|_| {
                    let mut e = Error::new(curl_sys::CURLE_FAILED_INIT);
                    e.set_extra("Failed to find function: curl_easy_upkeep".to_string());
                    e
                })?;

            self.cvt(func(self.inner.handle))
        };

        panic::propagate();
        return ret;
    }

Whether that's easier to use I don't know! There does appear to be similar support in the Windows Library as well for this but am unsure it works in the same way.

@alexcrichton
Copy link
Owner

That's a good point about build-time vs runtime, but I think that it may be a concern best handled by applications themselves rather than this crate. For example if we unconditionally define the method here (not even do version detection), it shouldn't cause problems if you don't actually even call the method in your application (the function is GC'd away and the symbol is never referenced).

In that sense I don't think that adding this symbol is a detriment to existing users. I'd prefer to avoid detection if possible because it tends to be pretty heavyweight and not always the most portable.

Perhaps this could do build-time detection, and then eschew runtime detection to the caller? (e.g. applications would be responsible for providing the right knobs to doing this detection at runtime if necessary).

@cmeister2
Copy link
Contributor Author

I haven't seen a good way of fitting build-time detection in with the existing build.rs code; presumably you'd want to do something like:

  • in curl-sys, try and build against "the library" to detect the function, ala AC_CHECK_LIB.
    • not trivial because pkgconfig just outputs cargo build lines to stdout, rather than in an inspectable way.
  • if function is detected, set cfg variable with cargo:rustc-cfg=KEY[="VALUE"]
  • expose this up the chain somehow so that curl knows that curl-sys was compiled with a certain feature enabled.

There's a lot of steps there that I'm handwaving away because I'm not really sure how to achieve them.

@alexcrichton
Copy link
Owner

If you'd prefer to keep this as a Cargo feature that seems fine too. My experience is that this makes it pretty non-composable and this is pretty unlikely to ever get used as a result. By putting some extra work in it's possible to make it much more ergonomic to use and possible for consumers to use in more circumstances.

@cmeister2
Copy link
Contributor Author

In the short term I think I'd prefer "merged and using a feature" over "rewriting the build script system and not yet merged" 😄 After that I'd be happy to throw some thinking and prototyping power at doing feature detection - surely this must be solved in other -sys libraries, so I can do a bit of investigation there.

@alexcrichton alexcrichton merged commit 205117b into alexcrichton:master Mar 3, 2021
@alexcrichton
Copy link
Owner

Ok sounds fine by me.

bors referenced this pull request in rust-lang/rust-semverver Mar 15, 2021
Bump curl from 0.4.34 to 0.4.35

Bumps [curl](https://github.com/alexcrichton/curl-rust) from 0.4.34 to 0.4.35.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/alexcrichton/curl-rust/releases">curl's releases</a>.</em></p>
<blockquote>
<h2>0.4.35</h2>
<h2>Added</h2>
<ul>
<li>Add some more fields to <code>Debug</code> for <code>Version</code> (<a href="https://github.com/alexcrichton/curl-rust/issues/368">#368</a>)</li>
<li>Add <code>expect_100_timeout</code> option to mirror <a href="https://curl.se/libcurl/c/CURLOPT_EXPECT_100_TIMEOUT_MS.html"><code>CURLOPT_EXPECT_100_TIMEOUT_MS</code></a> (<a href="https://github.com/alexcrichton/curl-rust/issues/376">#376</a>)</li>
<li>Add feature-gated support for <code>curl_easy_upkeep</code>, introduced in 7.62.0. Use the <code>upkeep_7_62_0</code> feature to enable this method. (<a href="https://github.com/alexcrichton/curl-rust/issues/378">#378</a>)</li>
</ul>
<h2>Fixed</h2>
<ul>
<li>Probe for OpenSSL certificates only once (<a href="https://github.com/alexcrichton/curl-rust/issues/362">#362</a>, <a href="https://github.com/alexcrichton/curl-rust/issues/363">#363</a>)</li>
<li>Upgrade socket2 dependency to a version not making invalid assumptions about the memory layout of <code>std::net::SocketAddr</code>. (<a href="https://github.com/alexcrichton/curl-rust/issues/365">#365</a>)</li>
<li>Fix debug formatting for <code>Events</code> struct (<a href="https://github.com/alexcrichton/curl-rust/issues/377">#377</a>)</li>
<li>Fix <code>tcp_nodelay</code> not working for static builds on Unix (<a href="https://github.com/alexcrichton/curl-rust/issues/379">#379</a>, <a href="https://github.com/alexcrichton/curl-rust/issues/381">#381</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/alexcrichton/curl-rust/commit/e220da3c650ae6ea55c08591f36ead6d4a94f97e"><code>e220da3</code></a> Add support for zlib-ng (<a href="https://github.com/alexcrichton/curl-rust/issues/351">#351</a>)</li>
<li>See full diff in <a href="https://github.com/alexcrichton/curl-rust/compare/curl-sys-0.4.34...curl-sys-0.4.35">compare view</a></li>
</ul>
</details>
<br />

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=curl&package-manager=cargo&previous-version=0.4.34&new-version=0.4.35)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting ``@dependabot` rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- ``@dependabot` rebase` will rebase this PR
- ``@dependabot` recreate` will recreate this PR, overwriting any edits that have been made to it
- ``@dependabot` merge` will merge this PR after your CI passes on it
- ``@dependabot` squash and merge` will squash and merge this PR after your CI passes on it
- ``@dependabot` cancel merge` will cancel a previously requested merge and block automerging
- ``@dependabot` reopen` will reopen this PR if it is closed
- ``@dependabot` close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually

</details>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants