H2o: 异步处理请求时连接重置崩溃

创建于 2018-11-07  ·  8评论  ·  资料来源: h2o/h2o

首先,感谢 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 保持我的请求有效吗?

任何帮助将非常受欢迎!

最有用的评论

我认为这解决了它! 这是 doh.powerdns.org 的正常运行时间。
是否有 h2o 库文档,我可以在其中描述异步工作时使用 _inline 函数的限制?

image

所有8条评论

我怀疑我的问题是我使用 h2o_send_inline 并且我应该使用一个生成器,如果连接消失,我应该在它上面调用 stop() ,并且我应该在将控制权返回给 h2o 事件循环之前注册生成器。 请让我知道我的想法是否正确!

如果连接消失,我应该使用一个生成器,它会在它上面调用 stop(),并且我应该在将控制权返回给 h2o 事件循环之前注册生成器。

是的! 是的! 是的! 感谢您自己寻找正确答案!

我认为这解决了它! 这是 doh.powerdns.org 的正常运行时间。
是否有 h2o 库文档,我可以在其中描述异步工作时使用 _inline 函数的限制?

image

极好的! 很高兴知道 dnsdist-doh 运行良好!

不幸的是,我们没有很好的文档点,尽管我认为将警告添加到 include/h2o.h 中 h2o_send_inline 的文档注释中是个好主意。

可能还值得一提的是, h2o_send_inline()创建其输入的副本作为性能考虑。 这也意味着该函数不获取参数的所有权,因此后者可以在返回给调用者后立即释放。

我对#1896 中添加的文档有点困惑,因为后者似乎与#142 中所说的相矛盾。 我使用libh2o事件循环来管理数据库连接,所以我的工作流程如下所示:

  1. 服务器收到一个 HTTP 请求。
  2. h2o_handler_t::on_req回调除了启动异步数据库查询(如有必要,使用h2o_socket_read_start()h2o_socket_notify_write() )并返回 0 之外不执行任何操作 - 特别是它不调用h2o_start_response()h2o_send_inline() (或类似的东西)。
  3. 控制返回到libh2o事件循环。
  4. 数据库查询结果到达并由回调处理。 后者调用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 你可以添加一个指向你自己状态的指针。 谢谢!

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

Ys88 picture Ys88  ·  5评论

daniel-lucio picture daniel-lucio  ·  5评论

utrenkner picture utrenkner  ·  3评论

ndac-todoroki picture ndac-todoroki  ·  5评论

basbebe picture basbebe  ·  3评论