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

Is it possible to use Python::allow_threads to release the GIL? #1

Closed
boholder opened this issue Mar 14, 2024 · 13 comments
Closed

Is it possible to use Python::allow_threads to release the GIL? #1

boholder opened this issue Mar 14, 2024 · 13 comments

Comments

@boholder
Copy link

boholder commented Mar 14, 2024

Hi, thanks for providing this neat library to save us who work on Nacos 2.x!

I'm using this library in an async style web server.

Since this library only provides sync style usage, I'd like to wrap the invocation into loop.run_in_executor() method.

I can run the invocation in either a thread pool, or a process pool maintained by myself.
It depends on whether this nacos-sdk-rust-binding-py library releases the GIL in its rust code, according to PyO3 document.
And it seems like currently the code isn't release it.

Thus I'm writing this issue for (well, shamelessly :p ) asking you to take a look at this perspective.
I know nothing about PyO3 so I can't come to a conclusion and contribute as a PR, sincerely sorry.
I'm not sure if it's suitable to do this.

According to The good scenario: Long-running C APIs that release the GIL section of this article,
You can call allow_threads before performing http/grpc requests to release the GIL, and acquire it again with with_gil before constructing python objects.
Well, I guess so, not sure :)

Best wishes for you.
中文回复也OK的。

@CherishCai
Copy link
Member

CherishCai commented Mar 14, 2024

很尴尬,其实我不熟悉 Python 也不够了解 PyO3,当时仅以最简单的方式 wrap 了下。pls see nacos-sdk-rust binding for Python
最初就想提供 asyncio 模式了,看描述是否研究下?

只知道 PyO3 可以额外组合 asyncio crate https://github.com/PyO3/pyo3/blob/main/README.md#tools-and-libraries

  • pyo3-asyncio Utilities for working with Python's Asyncio library and async functions

很抱歉我也不熟悉。。。

@boholder
Copy link
Author

boholder commented Mar 14, 2024

nacos-sdk-rust用的网络库是tokio
https://github.com/nacos-group/nacos-sdk-rust?tab=readme-ov-file#%E4%B8%BB%E8%A6%81%E4%BE%9D%E8%B5%96%E5%8C%85

然后pyo3-asyncio支持tokio的runtime
https://github.com/awestlake87/pyo3-asyncio?tab=readme-ov-file#pyo3-native-rust-modules

要做的是,先写出所有rust async形式的调用(我没在这个项目里搜到async fn,也不知道实现这个的难度多大。。),再用pyo3-asyncio提供的函数包装起来(这个看起来示例很清晰,照着做就行,不难)。
https://github.com/awestlake87/pyo3-asyncio?tab=readme-ov-file#awaiting-a-rust-future-in-python

@CherishCai
Copy link
Member

欢迎有空试试。
我之前没写过 Python ,日常工作也没真正用过,所以这一块还真不太会。

@boholder
Copy link
Author

Rua,我不会写这么复杂的rust...那我还是用进程池吧,期待有能人通过我们的讨论提PR

@CherishCai
Copy link
Member

好的,看是否有其他人改改,或者未来几周有空我研究一下~

@boholder
Copy link
Author

nacos-sdk-rust是暴露了async形式的API的,我猜直接包装就行。。猜的
https://github.com/search?q=repo%3Anacos-group%2Fnacos-sdk-rust%20async%20fn&type=code

@boholder
Copy link
Author

依赖pyo3-asyncio

[dependencies]
pyo3 = { version = "0.20", features = ["extension-module"] }
pyo3-asyncio = { version = "0.20", features = ["tokio-runtime"] }
tokio = "1"

nacos-sdk = { version = "0.3.5", features = ["async"] }

然后我卡在了

    pub fn register_instance(
        &self,
        py: Python,
        service_name: String,
        group: String,
        service_instance: NacosServiceInstance,
    ) -> PyResult<&PyAny> {
        pyo3_asyncio::tokio::future_into_py(py, async {
            let resp = self
                .inner
                .register_instance(
                    service_name,
                    Some(group),
                    transfer_ffi_instance_to_rust(&service_instance),
                )
                .await;

            Ok(resp.map_err(|nacos_err| PyRuntimeError::new_err(format!("{:?}", &nacos_err))))
        })
    }

future_into_py方法非要rust异步函数返回IntoPy<Object>类型,
https://docs.rs/pyo3-asyncio/0.20.0/pyo3_asyncio/tokio/fn.future_into_py.html
问题是这个IntoPy类型并不支持失败(比如没impl Result类型)
PyO3/pyo3#1813
https://docs.rs/pyo3/latest/pyo3/conversion/trait.IntoPy.html

感觉无解。但是我不知道如果不通过结果,怎么把失败传给Python

@boholder
Copy link
Author

boholder commented Mar 15, 2024

再把这个结果自己包装一层,python那边拿到后看内容判断是否成功。像http 200 {content:error}那样。这解决方案可不咋样。

就返回string吧,把错误信息返回去,如果是none就没出错。

UPDATE:

这个错误解决了:boholder@6380ee8

现在rust抱怨的是:

error[E0521]: borrowed data escapes outside of method
  --> src\naming.rs:78:9
   |
67 |           &self,
   |           -----
   |           |
   |           `self` is a reference that is only valid in the method body
   |           let's call the lifetime of this reference `'1`
...
78 | /         pyo3_asyncio::tokio::future_into_py(py, async move {
79 | |             let resp = call.await;
80 | |             if resp.is_ok() {
81 | |                 return Ok(None);
...  |
84 | |             }
85 | |         })
   | |          ^
   | |          |
   | |__________`self` escapes the method body here
   |            argument requires that `'1` must outlive `'static`

error: lifetime may not live long enough
  --> src\naming.rs:78:9
   |
68 |           py: Python,
   |           -- has type `pyo3::Python<'2>`
...
78 | /         pyo3_asyncio::tokio::future_into_py(py, async move {
79 | |             let resp = call.await;
80 | |             if resp.is_ok() {
81 | |                 return Ok(None);
...  |
84 | |             }
85 | |         })
   | |__________^ returning this value requires that `'2` must outlive `'static`

结合AI,我知道这是在抱怨生命周期不够长,但是我没找到如何延长生命周期。。我决定放弃实现异步了,再去看看那个序列化。

@CherishCai
Copy link
Member

不好意思之前提供的 pls see nacos-sdk-rust binding for Python 链接维护有误,重新修改了下。
期待你的贡献,届时可以 0.4.0-ALPHA 发布 asyncio 版本包

@CherishCai
Copy link
Member

打包发布了 0.3.6-ALPHA
https://pypi.org/project/nacos-sdk-rust-binding-py/0.3.6a0/

@boholder
Copy link
Author

牛逼哥,谢谢谢谢,我晚会试试。
nacos-sdk要求在同步和异步间二选一,python它也是支持类似rust crate的feature那种方式的,提供不同的实现,比如martuin["不同底层包"]。这样这个包就能同时提供同步和异步实现了。
就是不知道是否能共存在一个分支上,毕竟nacos-sdk限制了能调用的api。

@CherishCai
Copy link
Member

可以看下 examples ,其实一个包提供了两种: block 与 async api

@boholder
Copy link
Author

LGTM! 👏
更新版本并换成async形式后,测试和debug运行都不会报错,成功注册到nacos server上了。
实在太感谢了!

另外在这里给看到这个issue的人解释一下。一般Python的异步函数,都是async def这样直接用async关键字定义的。我们当然可以不用关键字,比如现在本项目的AsyncNacosNamingClient类的(自动生成的)签名是这样的:

class AsyncNacosNamingClient(object):
    def register_instance(self, *args, **kwargs): # real signature unknown
        pass

补全的话应该是:

class AsyncNacosNamingClient(object):
    def register_instance(self, *args, **kwargs) -> asyncio.coroutine: # <----- 返回coroutine对象
        pass

这样我们看起来是在await普通函数,实际上是在await函数返回的coroutine对象,一样可以用。
至于为什么PyO3没能生成正确的签名,查了下已经有能力生成了,目前被上游的cpython block住了

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

No branches or pull requests

2 participants