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

cohttp-eio: "large" chunked-response → exception Eio.Io Unix_error (Invalid argument, "writev", "") #1005

Open
smondet opened this issue Nov 7, 2023 · 4 comments
Labels

Comments

@smondet
Copy link

smondet commented Nov 7, 2023

This may be related to observations in #1004

Using the master branch.

I've modifed cohttp-eio/examples/server1.ml like this:

let handler _socket request _body =
  match Http.Request.resource request with
  | "/" -> (Http.Response.make (), Cohttp_eio.Body.of_string text)
  | "/ok" ->
      ( Http.Response.make (),
        Cohttp_eio.Body.of_string (String.make 3_146_724 'B') )
  | "/ok2" ->
      ( Http.Response.make (),
        Cohttp_eio.Body.of_string (String.make 3_146_725 'B') )
  | "/ok3" ->
      (Http.Response.make (), Eio.Flow.string_source (String.make 3_146_724 'B'))
  | "/ko" ->
      (Http.Response.make (), Eio.Flow.string_source (String.make 3_146_725 'B'))
  | "/html" ->
      ( Http.Response.make
          ~headers:(Http.Header.of_list [ ("content-type", "text/html") ])
          (),
        (* Use a plain flow to test chunked encoding *)
        Eio.Flow.string_source text )
  | _ -> (Http.Response.make ~status:`Not_found (), Cohttp_eio.Body.of_string "")

When we call /ko, the server dies with:

Fatal error: exception Eio.Io Unix_error (Invalid argument, "writev", ""),
  handling connection from tcp:127.0.0.1:33122

Here are the corresponding curl calls:

 $ curl --verbose --fail-with-body http://localhost:2000/ok | wc -c
*   Trying 127.0.0.1:2000...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Connected to localhost (127.0.0.1) port 2000 (#0)
> GET /ok HTTP/1.1
> Host: localhost:2000
> User-Agent: curl/7.81.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-length: 3146724
< 
{ [32724 bytes data]
100 3072k  100 3072k    0     0   161M      0 --:--:-- --:--:-- --:--:--  166M
* Connection #0 to host localhost left intact
3146724
 $ curl --verbose --fail-with-body http://localhost:2000/ok2 | wc -c
*   Trying 127.0.0.1:2000...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Connected to localhost (127.0.0.1) port 2000 (#0)
> GET /ok2 HTTP/1.1
> Host: localhost:2000
> User-Agent: curl/7.81.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-length: 3146725
< 
{ [65492 bytes data]
100 3072k  100 3072k    0     0   179M      0 --:--:-- --:--:-- --:--:--  187M
* Connection #0 to host localhost left intact
3146725
 $ curl --verbose --fail-with-body http://localhost:2000/ok3 | wc -c
*   Trying 127.0.0.1:2000...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Connected to localhost (127.0.0.1) port 2000 (#0)
> GET /ok3 HTTP/1.1
> Host: localhost:2000
> User-Agent: curl/7.81.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< 
{ [65489 bytes data]
100 3072k    0 3072k    0     0   174M      0 --:--:-- --:--:-- --:--:--  176M
* Connection #0 to host localhost left intact
3146724
 $ curl --verbose --fail-with-body http://localhost:2000/ko | wc -c
*   Trying 127.0.0.1:2000...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Connected to localhost (127.0.0.1) port 2000 (#0)
> GET /ko HTTP/1.1
> Host: localhost:2000
> User-Agent: curl/7.81.0
> Accept: */*
> 
* Empty reply from server
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
* Closing connection 0
curl: (52) Empty reply from server
0
@mseri mseri added the Bug label Nov 7, 2023
@smondet
Copy link
Author

smondet commented Nov 7, 2023

Actually with exactly the master branch now (e5a66f1c1e7c2e5051723e09260222994dff40cf) the /ok3 enpoint also causes the failure.

With this change, we get the behavior above (/ok3 succeeds. /ko fails):

diff --git a/vendor/cohttp/cohttp-eio/src/utils.ml b/vendor/cohttp/cohttp-eio/src/utils.ml
index 8478eac4..8d1cee93 100644
--- a/vendor/cohttp/cohttp-eio/src/utils.ml
+++ b/vendor/cohttp/cohttp-eio/src/utils.ml
@@ -45,7 +45,7 @@ let flow_of_reader =
   fun read_body_chunk -> Eio.Resource.T (Reader_flow.v read_body_chunk, handler)
 
 let flow_to_writer flow writer write_body =
-  let input = Eio.Buf_read.of_flow ~max_size:max_int flow in
+  let input = Eio.Buf_read.of_flow ~initial_size:1024 ~max_size:max_int flow in
   let rec loop () =
     let () =
       let () = Eio.Buf_read.ensure input 1 in

@smondet
Copy link
Author

smondet commented Nov 7, 2023

With let input = Eio.Buf_read.of_flow ~initial_size:1023 ~max_size:max_int flow in

They both fail.

with let input = Eio.Buf_read.of_flow ~initial_size:1025 ~max_size:max_int flow in

They both succeed.

@smondet
Copy link
Author

smondet commented Nov 7, 2023

With:

let handler _socket request _body =
  let u = Http.Request.resource request |> Uri.of_string in
  match Uri.path u with
  | "/" ->
      (Http.Response.make (), Cohttp_eio.Body.of_string text) (* | "/body" -> *)
  | "/body" ->
      let size = Uri.get_query_param u "size" |> Option.get |> int_of_string in
      (Http.Response.make (), Cohttp_eio.Body.of_string (String.make size 'B'))
  | "/flow" ->
      let size = Uri.get_query_param u "size" |> Option.get |> int_of_string in
      (Http.Response.make (), Eio.Flow.string_source (String.make size 'B'))

We can make crash the Body.of_string version too:

curl --verbose --fail-with-body http://localhost:2000/body?size=3_000_000 | wc -c
succeeds
but
curl --verbose --fail-with-body http://localhost:2000/body?size=6_000_000 | wc -c
fails, with the same server error.

@smondet
Copy link
Author

smondet commented Nov 7, 2023

OK This might be an Eio bug (?).

Playing with the initial-buffer-sizes and this:

diff --git a/vendor/eio/lib_eio_posix/flow.ml b/vendor/eio/lib_eio_posix/flow.ml
index 2588bf11..3ac9795a 100644
--- a/vendor/eio/lib_eio_posix/flow.ml
+++ b/vendor/eio/lib_eio_posix/flow.ml
@@ -43,6 +43,8 @@ module Impl = struct
     try
       Low_level.writev t (Array.of_list bufs)
     with Unix.Unix_error (code, name, arg) ->
+      traceln "ERROR vendor/eio/lib_eio_posix/flow.ml bufs: %d"
+        (List.length bufs);
       raise (Err.wrap code name arg)
 
   let copy t ~src = Eio.Flow.Pi.simple_copy ~single_write t ~src

We can see that it fails when the call to posix-writev gets an array of more than 1024 buffers, which is indeed the value of IOV_MAX on my system.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants