首先,感谢 H2O,太棒了! 我们将库用于“dnsdist-doh”。 这份报告大约是2.2.5。
如#142 中所述,dnsdist-doh 在异步模式下运行。 收到一个 http2 请求,然后移交给 DNS 处理。 返回通过管道发送,管道与 h2o_socket_read_start 挂钩。 如果从那里接收到数据,它包含一个指向原始“req”的指针。 然后我们使用该请求发送 DNS 应答。
此代码位于: https :
现在的问题是有时当“on_dnsdist”从管道接收数据时,HTTP2 连接已经重置。 正如 Valgrind 所指出的:
==11799== Invalid write of size 4
==11799== at 0x6490BF: finalostream_send (stream.c:341)
==11799== by 0x639760: h2o_send_inline (request.c:513)
==11799== by 0x61EF70: on_dnsdist(st_h2o_socket_t*, char const*) (doh.cc:538)
==11799== by 0x62F267: read_on_ready (evloop.c.h:235)
这是因为数据在这里是免费的:
==11799== at 0x4C30D3B: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==11799== by 0x643DFA: handle_rst_stream_frame (connection.c:797)
==11799== by 0x641A9C: expect_default (connection.c:837)
==11799== by 0x643AD0: parse_input (connection.c:873)
==11799== by 0x643AD0: on_read (connection.c:901)
==11799== by 0x62F267: read_on_ready (evloop.c.h:235)
在它最初被分配在这里之后:
==11799== Block was alloc'd at
==11799== at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==11799== by 0x64920D: h2o_mem_alloc (memory.h:328)
==11799== by 0x64920D: h2o_http2_stream_open (stream.c:37)
==11799== by 0x644AFC: handle_headers_frame (connection.c:596)
==11799== by 0x641A9C: expect_default (connection.c:837)
我对此的解释是 H2O 在我仍在处理请求时关闭了我的连接。
我的问题是,我该如何处理这种情况? 我可以在某处放置回调吗? 或者,只要我仍然“拥有”一个请求,我就可以说服 H2O 保持我的请求有效吗?
任何帮助将非常受欢迎!
我怀疑我的问题是我使用 h2o_send_inline 并且我应该使用一个生成器,如果连接消失,我应该在它上面调用 stop() ,并且我应该在将控制权返回给 h2o 事件循环之前注册生成器。 请让我知道我的想法是否正确!
如果连接消失,我应该使用一个生成器,它会在它上面调用 stop(),并且我应该在将控制权返回给 h2o 事件循环之前注册生成器。
是的! 是的! 是的! 感谢您自己寻找正确答案!
我认为这解决了它! 这是 doh.powerdns.org 的正常运行时间。
是否有 h2o 库文档,我可以在其中描述异步工作时使用 _inline 函数的限制?
极好的! 很高兴知道 dnsdist-doh 运行良好!
不幸的是,我们没有很好的文档点,尽管我认为将警告添加到 include/h2o.h 中 h2o_send_inline 的文档注释中是个好主意。
可能还值得一提的是, h2o_send_inline()
创建其输入的副本作为性能考虑。 这也意味着该函数不获取参数的所有权,因此后者可以在返回给调用者后立即释放。
我对#1896 中添加的文档有点困惑,因为后者似乎与#142 中所说的相矛盾。 我使用libh2o
事件循环来管理数据库连接,所以我的工作流程如下所示:
h2o_handler_t::on_req
回调除了启动异步数据库查询(如有必要,使用h2o_socket_read_start()
和h2o_socket_notify_write()
)并返回 0 之外不执行任何操作 - 特别是它不调用h2o_start_response()
或h2o_send_inline()
(或类似的东西)。libh2o
事件循环。h2o_send_inline()
或h2o_start_response()
后跟一系列h2o_send()
调用。根据#142 中的注释是正确的,因为在第 3 点没有足够的信息来生成 HTTP 响应标头(例如,响应正文大小取决于尚未收到的数据库查询结果),所以现在注册生成器还为时过早。
我已经查看了反向代理实现,它似乎在做或多或少相似的事情 - 据我所知,它第一次使用h2o_start_response()
是在on_head()
函数中,如果我理解正确,它将在源服务器将响应的第一部分返回给代理后调用(对应于我的工作流程中的第 4 点)。 另一方面,根据我对 #1896 的阅读,我需要设置h2o_generator_t::stop
回调并在返回 0 之前在点 2 调用h2o_start_response()
,这样我就可以避免对已释放内存的潜在访问. 那么,什么是正确的方法,或者我错过了什么?
@volyrique谢谢你提出这个问题。 我现在意识到我造成了混乱。
让我以不同的方式解释这一点。
h2o_req_t
在客户端关闭底层连接或突然丢弃 HTTP/2 流时被丢弃。 异步应用程序需要检测到并避免在之后使用h2o_req_t
。
有两种方法可以实现这一目标。
一种是将生成器注册到h2o_req_t
以接收被调用生成器的stop
回调。 您正确指出的缺点是生成器是通过调用h2o_start_response
:该函数只能在状态代码和响应标头准备就绪后调用。
另一种方法是从请求1的内存池中分配一个带有dispose
回调的内存。 代理处理程序使用该技术(请参阅lib/core/proxy.c 版本 2.2.5 的第 553 行); 当h2o_req_t
被丢弃时调用它的on_generator_dispose
。
@ahupowerdns我希望这条评论能更好地阐明 H2O 的工作原理。 我希望您的更改不是基于错误的假设,即在您调用h2o_start_response
以将生成器绑定到响应之前,将调用stop
回调。 最后,感谢您提交 #1896; 我将对 PR 进行调整以反映我在此处所写的内容并将其合并。
[1] 用于通过dispose
回调分配内存的函数称为h2o_mem_alloc_shared
,因为它是“共享”(即引用计数)内存分配的功能之一。
dnsdist-doh 现在使用 h2o_mem_alloc_shared,它 1) 更容易,2) 实际上更好,因为它允许我们准确存储哪个请求被终止。 使用 'stop' 方法,你确实得到了一个 'req' 需要停止的信号,但是 h2o 在实践中会快速重用相同的 req ptr,所以你需要非常小心地取消正确的未完成的工作。 使用 h2o_mem_alloc_shared 你可以添加一个指向你自己状态的指针。 谢谢!
最有用的评论
我认为这解决了它! 这是 doh.powerdns.org 的正常运行时间。
是否有 h2o 库文档,我可以在其中描述异步工作时使用 _inline 函数的限制?