asgi-webdav icon indicating copy to clipboard operation
asgi-webdav copied to clipboard

Draft: support zerocopysend asgi extension

Open synodriver opened this issue 3 years ago • 42 comments

第一个原型,还会添加更多测试。

实现细节:

  • 发送文件时完全使用os.open读取。这样做是因为一旦发现zerocopy不可用,还能使用os.read来读取为bytes,但是如果一开始就读取为bytes那是无论如何都不能再变回文件描述符了

synodriver avatar Jun 17 '22 12:06 synodriver

迭代文件描述符的时候使用了run_in_executor,在zerocopy不可用的时候提高效率

synodriver avatar Jun 17 '22 12:06 synodriver

这个中间件可以作为打开zerocopysend的开关来使用。

class UseSendFileMiddlware:
    def __init__(self, app, open=True):
        self.app = app
        self.open = open

    async def __call__(self, scope,receive,send):
        if not self.open:
            if "extensions" in scope and "http.response.zerocopysend" in scope["extensions"]:
                scope["extensions"].pop("http.response.zerocopysend")
        await self.app(scope,receive,send)

或许可以增加一个配置项?

synodriver avatar Jun 17 '22 12:06 synodriver

既然要集成进别的asgi框架,那logger应该也可以考虑下。目前的logger使用的比较散乱,是否可以添加一个app级别的logger统一使用?

synodriver avatar Jun 17 '22 12:06 synodriver

  • zero-copy 是否在 ASGI 接口部分应该是服务启动后即可知道的.
  • 应该可以一开始只是一个

这个中间件可以作为打开zerocopysend的开关来使用。

class UseSendFileMiddlware:
    def __init__(self, app, open=True):
        self.app = app
        self.open = open

    async def __call__(self, scope,receive,send):
        if not self.open:
            if "extensions" in scope and "http.response.zerocopysend" in scope["extensions"]:
                scope["extensions"].pop("http.response.zerocopysend")
        await self.app(scope,receive,send)

或许可以增加一个配置项?

使用配置来控制,这样也能方便 CLI 等方式来控制.

class Config():
    # response
    compression: Compression = Compression()
    cors: CORS = CORS()
    enable_dir_browser: bool = True
    enable_asgi_zero_copy: bool = True

代码部分检查这个新的控制变量来执行不同的行为

rexzhang avatar Jun 17 '22 12:06 rexzhang

既然要集成进别的asgi框架,那logger应该也可以考虑下。目前的logger使用的比较散乱,是否可以添加一个app级别的logger统一使用?

这个开一个新的 Issue 讨论比较好,如果你有比较完整的想法的话

rexzhang avatar Jun 17 '22 12:06 rexzhang

迭代文件描述符的时候使用了run_in_executor,在zerocopy不可用的时候提高效率

当 zerocopy 不可用时需要完全回落到以前的代码;也就是说 zerocopy 部分需要以可挂载的方式添加进来

rexzhang avatar Jun 17 '22 12:06 rexzhang

迭代文件描述符的时候使用了run_in_executor,在zerocopy不可用的时候提高效率

当 zerocopy 不可用时需要完全回落到以前的代码;也就是说 zerocopy 部分需要以可挂载的方式添加进来

目前是provider统一读取为文件描述符了,要完全回到原来那种很困难。主要是要考虑多方面因素

  • ASGI服务器是否支持zerocopysend?
  • 用户是否配置了zerocopysend?
  • 用户启用了zerocopysend但是ASGI服务器不支持怎么办?
  • 目前的构架,往里面更改整个实现逻辑实在是有点困难了,一旦zerocopy不可用,还能使用os.read来读,但是bytes就不可能变成文件描述符的,基于此可以认为让FileSystemProvider返回fd: int是比较合理的

synodriver avatar Jun 17 '22 12:06 synodriver

This pull request introduces 5 alerts when merging 5257dec2e8afe7a328836e73ea471a4c5301897b into 1b0dc6be9b13692373a25b2d6c27bae9d227c7e4 - view on LGTM.com

new alerts:

  • 5 for Module-level cyclic import

lgtm-com[bot] avatar Jun 17 '22 12:06 lgtm-com[bot]

迭代文件描述符的时候使用了run_in_executor,在zerocopy不可用的时候提高效率

当 zerocopy 不可用时需要完全回落到以前的代码;也就是说 zerocopy 部分需要以可挂载的方式添加进来

目前是provider统一读取为文件描述符了,要完全回到原来那种很困难。主要是要考虑多方面因素

* ASGI服务器是否支持zerocopysend?

* 用户是否配置了zerocopysend?

* 用户启用了zerocopysend但是ASGI服务器不支持怎么办?

* 目前的构架,往里面更改整个实现逻辑实在是有点困难了,一旦zerocopy不可用,还能使用os.read来读,但是bytes就不可能变成文件描述符的,基于此可以认为让`FileSystemProvider`返回`fd: int`是比较合理的

前三条都是静态信息,启动完成后即确定的.架构的话,可以先确定需要在什么地方确定什么状态来调整

rexzhang avatar Jun 17 '22 13:06 rexzhang

ASGI服务器是否支持zerocopysend没法在服务器启动的时候知道,只有等第一个request来了以后去scope里面看

synodriver avatar Jun 17 '22 13:06 synodriver

ASGI服务器是否支持zerocopysend没法在服务器启动的时候知道,只有等第一个request来了以后去scope里面看

这就有点离谱了,难道 ASGI 服务器在启动后不能确定自己是否支持 zerocopysend? 这个应该可以修改 ASGI 服务器来实现吧? 而且可以认定,当用户设置了开启 enable_asgi_zero_copy 选项,即表示用户明确的知道 ASGI 服务器支持;在这个设计下,这个选项默认关闭即可

rexzhang avatar Jun 17 '22 13:06 rexzhang

ASGI服务器是否支持zerocopysend没法在服务器启动的时候知道,只有等第一个request来了以后去scope里面看

这就有点离谱了,难道 ASGI 服务器在启动后不能确定自己是否支持 zerocopysend? 这个应该可以修改 ASGI 服务器来实现吧? 而且可以认定,当用户设置了开启 enable_asgi_zero_copy 选项,即表示用户明确的知道 ASGI 服务器支持;在这个设计下,这个选项默认关闭即可

因为asgiapp可以部署在各种asgi兼容服务器上,uvicorn, hypercorn, daphne, nginx unit, prehttp, nonecorn都可以,甚至还可以是个云函数,环境多种多样,app甚至都不知道他在什么服务器上面跑。正规做法是去scope判定,不过如果用户明确设置了这个,则可以认为用户知道这样做会有什么后果,那可以不处理异常了?

然后是目前的实现相关。在FileSystemProvider._do_get,要判断是否使用zerocopy就得从这开始了。但是目前提供的函数是DAVResponse.can_be_compressed,它的参数是header的一个字段和rule,可是在FileSystemProvider._do_get里面header还没生成呢,要硬改的话改不好会cycle import

synodriver avatar Jun 17 '22 13:06 synodriver

因为asgiapp可以部署在各种asgi兼容服务器上,uvicorn, hypercorn, daphne, nginx unit, prehttp, nonecorn都可以,甚至还可以是个云函数,环境多种多样,app甚至都不知道他在什么服务器上面跑。正规做法是去scope判定,不过如果用户明确设置了这个,则可以认为用户知道这样做会有什么后果,那可以不处理异常了?

考虑到:会做性能调整的用户应该是高级用户的定位.只要在遇到明确是配置问题的地方抛出“配置/初始化”异常,同时退出服务;应该就可以了

rexzhang avatar Jun 17 '22 13:06 rexzhang

因为asgiapp可以部署在各种asgi兼容服务器上,uvicorn, hypercorn, daphne, nginx unit, prehttp, nonecorn都可以,甚至还可以是个云函数,环境多种多样,app甚至都不知道他在什么服务器上面跑。正规做法是去scope判定,不过如果用户明确设置了这个,则可以认为用户知道这样做会有什么后果,那可以不处理异常了?

考虑到:会做性能调整的用户应该是高级用户的定位.只要在遇到明确是配置问题的地方抛出“配置/初始化”异常,同时退出服务;应该就可以了

这个就简单了,把这个event原样扔给asgi服务器,asgi服务器收到不支持的事件自己会报错的

synodriver avatar Jun 17 '22 13:06 synodriver

然后是目前的实现相关。在FileSystemProvider._do_get,要判断是否使用zerocopy就得从这开始了。但是目前提供的函数是DAVResponse.can_be_compressed,它的参数是header的一个字段和rule,可是在FileSystemProvider._do_get里面header还没生成呢,要硬改的话改不好会cycle import

判断是否会执行压缩其实是基于 response 的 header 中的 Content-Type 确定的. 而 Content-Type 是有 request 中 GET 方法需要获取的这个文件的文件名和文件格式来确定的. 也就是说在 FileSystemProvider._do_get() 中, 如果配置中启用了 ZCS 就做一个判断,然后根据判断后的结果执行 ZCS 或者回落执行以前的代码. 挂载的方式加入是完全可行的

rexzhang avatar Jun 17 '22 13:06 rexzhang

另外, PR 的代码请先执行 isort 再执行 black 格式化代码后再提交. 相关配置我都是做好了的,只需要在项目根目录执行即可

rexzhang avatar Jun 17 '22 13:06 rexzhang

ASGI服务器是否支持zerocopysend没法在服务器启动的时候知道,只有等第一个request来了以后去scope里面看

其实可以去 asgiref 提一个需求. 添加一个标准特性:提供一个 API, 允许在获取 ASGI 实例的当前支持的特性和扩展清单

rexzhang avatar Jun 17 '22 13:06 rexzhang

django组织的成员挺固执的……估计他们不会听,原来我那个trailingheader的扩展就没通过,我就自己实现了

synodriver avatar Jun 17 '22 14:06 synodriver

This pull request introduces 7 alerts when merging 6d353c1a2acd2ca91d39c3bfca1cb65e379eda58 into 1b0dc6be9b13692373a25b2d6c27bae9d227c7e4 - view on LGTM.com

new alerts:

  • 6 for Module-level cyclic import
  • 1 for Unused import

lgtm-com[bot] avatar Jun 17 '22 16:06 lgtm-com[bot]

This pull request introduces 1 alert when merging 41272e2ccd1013b8194a64bbccfdc45b20b8c20d into 1b0dc6be9b13692373a25b2d6c27bae9d227c7e4 - view on LGTM.com

new alerts:

  • 1 for Unused import

lgtm-com[bot] avatar Jun 17 '22 20:06 lgtm-com[bot]

This pull request introduces 1 alert when merging 28a236b0227dc7f6271d62e8bea9d60095127390 into 1b0dc6be9b13692373a25b2d6c27bae9d227c7e4 - view on LGTM.com

new alerts:

  • 1 for Module is imported with 'import' and 'import from'

lgtm-com[bot] avatar Jun 19 '22 07:06 lgtm-com[bot]

单元测试中的get_response_content需要修改一下,现在response._content可能是异步生成器也可能是zerocopy的fd,或者默认enable_asgi_zero_copy: bool是false?这里做了一个假设:用户会使用相应的asgi服务器来部署,不过看情况不能这样,还是应该保守一些

synodriver avatar Jun 28 '22 16:06 synodriver

单元测试中的get_response_content需要修改一下,现在response._content可能是异步生成器也可能是zerocopy的fd,或者默认enable_asgi_zero_copy: bool是false?这里做了一个假设:用户会使用相应的asgi服务器来部署,不过看情况不能这样,还是应该保守一些

需要如何改?欢迎直接提方案

rexzhang avatar Jul 01 '22 17:07 rexzhang

单元测试部分,可以先完善 PR 涉及的部分,有冲突部分后续解决也可.并行推进效率高很多

rexzhang avatar Jul 01 '22 17:07 rexzhang

相关的单元测试加了个patch,应该好了

synodriver avatar Jul 11 '22 16:07 synodriver

Codecov Report

Attention: Patch coverage is 43.01075% with 53 lines in your changes are missing coverage. Please review.

Project coverage is 65.25%. Comparing base (ac3161d) to head (e4b9946). Report is 5 commits behind head on main.

:exclamation: Current head e4b9946 differs from pull request most recent head df4e798. Consider uploading reports for the commit df4e798 to get more accurate results

Files Patch % Lines
asgi_webdav/response.py 42.50% 23 Missing :warning:
asgi_webdav/helpers.py 26.92% 19 Missing :warning:
asgi_webdav/provider/file_system.py 56.00% 11 Missing :warning:
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #20      +/-   ##
==========================================
- Coverage   65.99%   65.25%   -0.75%     
==========================================
  Files          24       24              
  Lines        2770     2849      +79     
==========================================
+ Hits         1828     1859      +31     
- Misses        942      990      +48     

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.

codecov[bot] avatar Jul 12 '22 07:07 codecov[bot]

可否添加些性能对比测试? uvicorn/nonecorn 打开 ZeroCopy/nonecorn 关闭 ZeroCopy

rexzhang avatar Jul 14 '22 04:07 rexzhang

我做过一般网站的性能测试,确实可以有数倍的提升

synodriver avatar Jul 14 '22 06:07 synodriver

可否针对这个项目做一个如上三个环境的对比性能测试? 可以放在 performance/asgi_zero_copy 目录下

rexzhang avatar Jul 14 '22 06:07 rexzhang

测试结果以文本文件的方式存储,可以简单的(在命令行)重定向输出到文件

rexzhang avatar Jul 14 '22 06:07 rexzhang