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

Support passing name and doc to new_closure. #2686

Merged
merged 1 commit into from
Nov 6, 2022

Conversation

dalcde
Copy link
Contributor

@dalcde dalcde commented Oct 15, 2022

No description provided.

@dalcde dalcde force-pushed the closure-name-doc branch 3 times, most recently from 023b3d4 to 6cef339 Compare October 15, 2022 03:08
@dalcde
Copy link
Contributor Author

dalcde commented Oct 15, 2022 via email

@mejrs
Copy link
Member

mejrs commented Oct 15, 2022

What about this signature?

pub fn new_closure<'a, F, R>(
        py: Python<'a>,
        name: Option<&'static str,
        doc: Option<&'static str>,
        f: F,
    ) -> PyResult<&'a PyCFunction>

iirc both name and doc can be null (?) and I think I prefer using None rather than "" if I don't care about doc

@mejrs
Copy link
Member

mejrs commented Oct 15, 2022

I'm not sure if this PR caused this or not, but something like this

use pyo3::prelude::*;
use pyo3::types::*;

fn main() {
    Python::with_gil(|_py| loop {
        let pool = unsafe { _py.new_pool() };
        let py = pool.python();
        let add_one = |args: &PyTuple, _kwargs: Option<&PyDict>| -> PyResult<_> {
            let i = args.extract::<(i64,)>()?.0;
            Ok(i + 1)
        };
        let closure_py = PyCFunction::new_closure(py, "blah", "blah", add_one).unwrap();
    });
}

leaks memory at a rate of 400MB/s. I'm not familiar with closures and capsules, but that doesn't sound right.

@dalcde
Copy link
Contributor Author

dalcde commented Oct 15, 2022 via email

@mejrs
Copy link
Member

mejrs commented Oct 15, 2022

It leaks on pyo3 version 0.17 too 🤔

@dalcde
Copy link
Contributor Author

dalcde commented Oct 15, 2022 via email

@birkenfeld
Copy link
Member

If I read the CPython code correctly, at least name should not be NULL. But of course it is easy to unwrap_or("\0").

The leak is easy to fix in 0.17 since we control the literals and can null-terminate them. For after this patch there are several possibilities:

  • require &'static CStr (not easy to get without a helper crate, and breaks API consistency)
  • require null-terminated string literals and return Err otherwise (also breaks API consistency)
  • keep our own static storage of &'static CStrs

@davidhewitt
Copy link
Member

davidhewitt commented Oct 16, 2022

Yes, the general use of leaking in current PyO3 is not ideal and it's definitely a bug that each call to new_closure currently leaks.

It's been on the back of my mind to tidy up and I've personally wanted to go down the &'static CStr route but been waiting for an MSRV bump, because &'static CStr can't be const-constructed (i.e. without leaking until CStr::from_bytes_with_nul_unchecked became const in 1.59).

Maybe we should explore a solution like https://crates.io/crates/const-cstr ?

@dalcde
Copy link
Contributor Author

dalcde commented Oct 16, 2022 via email

@adamreichold
Copy link
Member

I think there are two use cases of closures that have different requirements. If the goal is to define a module function but you find it more convenient to define it via a closure for whatever reason, you don't really care about leaks too much because it's going to leave it around forever. The other use case is a short-lived function to feed into other python functions that require a function. In this case you probably don't care about name and doc. Given this, it seems reasonable to have a default new_closure that does not take name/doc and does not leak, plus a version that takes name/doc and leaks if the user doesn't null-terminate.

Couldn't the signature proposed in #2686 (comment) fulfil this as well without having separate entry points?

@mejrs
Copy link
Member

mejrs commented Oct 16, 2022

Let me see what I can come up with. 🙂

@dalcde
Copy link
Contributor Author

dalcde commented Oct 16, 2022 via email

@dalcde
Copy link
Contributor Author

dalcde commented Oct 23, 2022

@mejrs Do you think this is blocked on #2690? Or can they proceed independently?

(My understanding of the codebase is that if we use null-terminated static strings in PyMethodDef::cfunction_with_keywords, then it already does not leak. I suppose #2690 prepares for the case of user-supplied non-null-terminated strings?)

@mejrs
Copy link
Member

mejrs commented Oct 24, 2022

@mejrs Do you think this is blocked on #2690? Or can they proceed independently?

I'll need to do some more thinking on that PR, so I'm happy to merge this one first.

(My understanding of the codebase is that if we use null-terminated static strings in PyMethodDef::cfunction_with_keywords, then it already does not leak. I suppose #2690 prepares for the case of user-supplied non-null-terminated strings?)

That's true, but there is also a boxed PyMethodDef that is getting leaked. I don't mind that for this PR though.

I'd still like the api as #2686 (comment) . It can default to a dummy name/doc if None.

@dalcde
Copy link
Contributor Author

dalcde commented Oct 29, 2022

I've updated the pull request to accept optional name/doc.

@dalcde
Copy link
Contributor Author

dalcde commented Nov 6, 2022

Is this still blocked on anything? Or are we waiting for the next breaking release?

@mejrs
Copy link
Member

mejrs commented Nov 6, 2022

Is this still blocked on anything? Or are we waiting for the next breaking release?

I just hadn't gotten around to it. Thanks ❤️

@mejrs mejrs merged commit 6766d9f into PyO3:main Nov 6, 2022
@dalcde
Copy link
Contributor Author

dalcde commented Nov 6, 2022 via email

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.

5 participants