Skip to content

Commit 39bc01a

Browse files
committed
Handle EIO deadlock when H2 returned an error like 404.
Originally the code would only wait on the response and if H2 encountered an error, it would call error_handler instead of response_handler and because error_handler was not defined then errors were sliently ignored. This fix adds error handling with promise racing.
1 parent b629b55 commit 39bc01a

File tree

2 files changed

+48
-5
lines changed

2 files changed

+48
-5
lines changed

lib/grpc-eio/client.ml

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
type response_handler = H2.Client_connection.response_handler
2+
type error_handler = H2.Client_connection.error_handler
23

34
type do_request =
45
?flush_headers_immediately:bool ->
56
?trailers_handler:(H2.Headers.t -> unit) ->
67
H2.Request.t ->
8+
error_handler:error_handler ->
79
response_handler:response_handler ->
810
H2.Body.Writer.t
911

@@ -34,22 +36,61 @@ let make_trailers_handler () =
3436
let get_response_and_bodies request =
3537
let response, response_notify = Eio.Promise.create () in
3638
let read_body, read_body_notify = Eio.Promise.create () in
39+
let error_promise, error_notify = Eio.Promise.create () in
40+
3741
let response_handler response body =
3842
Eio.Promise.resolve response_notify response;
3943
Eio.Promise.resolve read_body_notify body
4044
in
41-
let write_body = request ~response_handler in
42-
let response = Eio.Promise.await response in
43-
let read_body = Eio.Promise.await read_body in
44-
(response, read_body, write_body)
45+
46+
let error_handler err =
47+
(* When H2 error occurs, resolve the error promise *)
48+
if not (Eio.Promise.is_resolved error_promise) then
49+
Eio.Promise.resolve error_notify (Some err)
50+
in
51+
52+
let write_body = request ~error_handler ~response_handler in
53+
54+
(* Race between getting response and error *)
55+
match Eio.Fiber.first
56+
(fun () ->
57+
let resp = Eio.Promise.await response in
58+
let body = Eio.Promise.await read_body in
59+
`Response (resp, body))
60+
(fun () ->
61+
let err = Eio.Promise.await error_promise in
62+
match err with
63+
| Some e -> `Error e
64+
| None ->
65+
(* This shouldn't happen *)
66+
failwith "Internal error: error promise resolved without value")
67+
with
68+
| `Response (response, read_body) -> (response, read_body, write_body)
69+
| `Error err ->
70+
(* Convert H2 error to exception *)
71+
let msg = match err with
72+
| `Protocol_error (code, msg) ->
73+
Printf.sprintf "H2 protocol error (%s): %s"
74+
(H2.Error_code.to_string code) msg
75+
| `Invalid_response_body_length resp ->
76+
Printf.sprintf "Invalid response body length for status %s"
77+
(H2.Status.to_string resp.H2.Response.status)
78+
| `Malformed_response msg ->
79+
Printf.sprintf "Malformed response: %s" msg
80+
| `Exn exn ->
81+
Printf.sprintf "H2 exception: %s" (Printexc.to_string exn)
82+
in
83+
failwith msg
4584

4685
let call ~service ~rpc ?(scheme = "https") ~handler ~(do_request : do_request)
4786
?(headers = default_headers) () =
4887
let request = make_request ~service ~rpc ~scheme ~headers in
4988
let status, trailers_handler = make_trailers_handler () in
5089
let response, read_body, write_body =
5190
get_response_and_bodies
52-
(do_request ~flush_headers_immediately:true request ~trailers_handler)
91+
(fun ~error_handler ~response_handler ->
92+
do_request ~flush_headers_immediately:true request ~trailers_handler
93+
~error_handler ~response_handler)
5394
in
5495
match response.status with
5596
| `OK ->

lib/grpc-eio/client.mli

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ module Rpc : sig
2525
end
2626

2727
type response_handler = H2.Client_connection.response_handler
28+
type error_handler = H2.Client_connection.error_handler
2829

2930
type do_request =
3031
?flush_headers_immediately:bool ->
3132
?trailers_handler:(H2.Headers.t -> unit) ->
3233
H2.Request.t ->
34+
error_handler:error_handler ->
3335
response_handler:response_handler ->
3436
H2.Body.Writer.t
3537
(** [do_request] is the type of a function that performs the request *)

0 commit comments

Comments
 (0)