Skip to content

Commit 2a08610

Browse files
authored
feat: yield the reader if reads not scheduled (#70)
* feat: yield the reader if reads not scheduled * fix tests * wip * fix tests * fix more
1 parent 1c05642 commit 2a08610

7 files changed

+313
-106
lines changed

examples/eio/echo_server.ml

+11-4
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,22 @@ let connection_handler ~sw : Eio.Net.Sockaddr.stream -> _ Eio.Net.stream_socket
66
let module Status = Httpun.Status in
77

88
let websocket_handler _client_address wsd =
9-
let frame ~opcode ~is_fin:_ ~len:_ payload =
9+
let frame ~opcode ~is_fin ~len payload =
10+
Format.eprintf "FRAME %a %d %B@." Httpun_ws.Websocket.Opcode.pp_hum opcode len is_fin;
1011
match (opcode: Httpun_ws.Websocket.Opcode.t) with
1112
| #Httpun_ws.Websocket.Opcode.standard_non_control as opcode ->
13+
let rec on_read bs ~off ~len =
14+
Format.eprintf "do it %d %S@." len (Bigstringaf.substring bs ~off ~len);
15+
Httpun_ws.Wsd.schedule wsd bs ~kind:opcode ~off ~len;
16+
Httpun_ws.Payload.schedule_read payload
17+
~on_eof:ignore
18+
~on_read
19+
in
1220
Httpun_ws.Payload.schedule_read payload
1321
~on_eof:ignore
14-
~on_read:(fun bs ~off ~len ->
15-
Httpun_ws.Wsd.schedule wsd bs ~kind:opcode ~off ~len)
22+
~on_read
1623
| `Connection_close ->
17-
Httpun_ws.Wsd.close wsd
24+
Httpun_ws.Wsd.close ~code:(`Other 1005) wsd
1825
| `Ping ->
1926
Httpun_ws.Wsd.send_pong wsd
2027
| `Pong

lib/client_connection.ml

+2-2
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ let next_read_operation t =
121121
(* TODO(anmonteiro): handle this *)
122122
assert false
123123
(* set_error_and_handle t (`Exn (Failure message)); `Close *)
124-
| (`Read | `Close) as operation -> operation
124+
| (`Read | `Yield | `Close) as operation -> operation
125125

126126
let read t bs ~off ~len =
127127
match t.state with
@@ -152,7 +152,7 @@ let report_exn t exn =
152152
let yield_reader t f =
153153
match t.state with
154154
| Handshake handshake -> Client_handshake.yield_reader handshake f
155-
| Websocket _websocket -> assert false
155+
| Websocket websocket -> Websocket_connection.yield_reader websocket f
156156

157157
let yield_writer t f =
158158
match t.state with

lib/parse.ml

+79-36
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
type t =
2-
{ payload_length: int
3-
; is_fin: bool
4-
; mask: int32 option
5-
; payload: Payload.t
6-
; opcode: Websocket.Opcode.t
7-
}
2+
{ payload_length: int
3+
; is_fin: bool
4+
; mask: int32 option
5+
; opcode: Websocket.Opcode.t
6+
}
87

98
let is_fin headers =
109
let bits = Bigstringaf.unsafe_get headers 0 |> Char.code in
@@ -87,7 +86,7 @@ let parse_headers =
8786
>>= fun headers_len -> Unsafe.take headers_len Bigstringaf.sub
8887
;;
8988

90-
let payload_parser t =
89+
let payload_parser t payload =
9190
let open Angstrom in
9291
let unmask t bs ~src_off =
9392
match t.mask with
@@ -120,28 +119,24 @@ let payload_parser t =
120119
available >>= fun m ->
121120
let m' = (min m n) in
122121
let n' = n - m' in
123-
schedule_size ~src_off t.payload m'
122+
schedule_size ~src_off payload m'
124123
>>= fun () -> read_exact (src_off + m') n'
125124
in
126125
fun n -> read_exact 0 n
127126
in
128127
read_exact t.payload_length
129-
>>= fun () -> finish t.payload
128+
>>= fun () -> finish payload
130129
;;
131130

132-
let frame ~buf =
131+
let frame =
133132
let open Angstrom in
134133
parse_headers
135134
>>| fun headers ->
136135
let payload_length = payload_length_of_headers headers
137136
and is_fin = is_fin headers
138137
and opcode = opcode headers
139138
and mask = mask headers in
140-
let payload = match payload_length with
141-
| 0 -> Payload.empty
142-
| _ -> Payload.create buf
143-
in
144-
{ is_fin; opcode; mask; payload_length; payload }
139+
{ is_fin; opcode; mask; payload_length }
145140
;;
146141

147142
module Reader = struct
@@ -155,25 +150,53 @@ module Reader = struct
155150
type 'error t =
156151
{ parser : unit Angstrom.t
157152
; mutable parse_state : 'error parse_state
158-
; mutable closed : bool }
153+
; mutable closed : bool
154+
; mutable wakeup : Optional_thunk.t
155+
}
156+
157+
let wakeup t =
158+
let f = t.wakeup in
159+
t.wakeup <- Optional_thunk.none;
160+
Optional_thunk.call_if_some f
159161

160162
let create frame_handler =
161-
let parser =
163+
let rec parser t =
162164
let open Angstrom in
163165
let buf = Bigstringaf.create 0x1000 in
164166
skip_many
165-
(frame ~buf <* commit >>= fun frame ->
166-
let payload = frame.payload in
167-
let { is_fin; opcode; payload_length = len; _ } = frame in
168-
frame_handler ~opcode ~is_fin ~len payload;
169-
payload_parser frame)
167+
(frame <* commit >>= fun frame ->
168+
let { payload_length; _ } = frame in
169+
let payload =
170+
match payload_length with
171+
| 0 -> Payload.create_empty ()
172+
| _ ->
173+
Payload.create buf
174+
~when_ready_to_read:(Optional_thunk.some (fun () ->
175+
wakeup (Lazy.force t)))
176+
in
177+
frame_handler frame payload;
178+
payload_parser frame payload)
179+
and t = lazy (
180+
{ parser = parser t
181+
; parse_state = Done
182+
; closed = false
183+
; wakeup = Optional_thunk.none
184+
}
185+
)
170186
in
171-
{ parser
172-
; parse_state = Done
173-
; closed = false
174-
}
187+
Lazy.force t
175188
;;
176189

190+
let is_closed t =
191+
t.closed
192+
193+
let on_wakeup t k =
194+
if is_closed t
195+
then failwith "on_wakeup on closed reader"
196+
else if Optional_thunk.is_some t.wakeup
197+
then failwith "on_wakeup: only one callback can be registered at a time"
198+
else t.wakeup <- Optional_thunk.some k
199+
177200
let transition t state =
178201
match state with
179202
| AU.Done(consumed, ())
@@ -195,28 +218,48 @@ module Reader = struct
195218
t.parse_state <- Partial continue
196219
| _ -> assert false
197220

198-
let rec read_with_more t bs ~off ~len more =
221+
let rec _read_with_more t bs ~off ~len more =
222+
let initial = match t.parse_state with Done -> true | _ -> false in
199223
let consumed =
200224
match t.parse_state with
201225
| Fail _ -> 0
226+
(* Don't feed empty input when we're at a request boundary *)
227+
| Done when len = 0 -> 0
202228
| Done ->
203229
start t (AU.parse t.parser);
204-
read_with_more t bs ~off ~len more;
230+
_read_with_more t bs ~off ~len more;
205231
| Partial continue ->
206232
transition t (continue bs more ~off ~len)
207233
in
208-
begin match more with
209-
| Complete -> t.closed <- true;
210-
| Incomplete -> ()
211-
end;
234+
(* Special case where the parser just started and was fed a zero-length
235+
* bigstring. Avoid putting them parser in an error state in this scenario.
236+
* If we were already in a `Partial` state, return the error. *)
237+
if initial && len = 0 then t.parse_state <- Done;
238+
match t.parse_state with
239+
| Done when consumed < len ->
240+
let off = off + consumed
241+
and len = len - consumed in
242+
consumed + _read_with_more t bs ~off ~len more
243+
| _ -> consumed
244+
;;
245+
246+
let read_with_more t bs ~off ~len more =
247+
let consumed = _read_with_more t bs ~off ~len more in
248+
(match more with
249+
| Complete ->
250+
t.closed <- true
251+
| Incomplete -> ());
212252
consumed
213253

254+
let force_close t =
255+
t.closed <- true;
256+
;;
257+
214258
let next t =
215259
match t.parse_state with
216-
| Done ->
217-
if t.closed
218-
then `Close
219-
else `Read
220260
| Fail failure -> `Error failure
261+
| _ when t.closed -> `Close
262+
| Done -> `Read
221263
| Partial _ -> `Read
264+
;;
222265
end

lib/payload.ml

+39-13
Original file line numberDiff line numberDiff line change
@@ -38,43 +38,52 @@ module IOVec = Httpun.IOVec
3838
{ faraday : Faraday.t
3939
; mutable read_scheduled : bool
4040
; mutable on_eof : unit -> unit
41+
; mutable eof_has_been_called : bool
4142
; mutable on_read : Bigstringaf.t -> off:int -> len:int -> unit
43+
; when_ready_to_read : Optional_thunk.t
4244
}
4345

4446
let default_on_eof = Sys.opaque_identity (fun () -> ())
4547
let default_on_read = Sys.opaque_identity (fun _ ~off:_ ~len:_ -> ())
4648

47-
let of_faraday faraday =
48-
{ faraday
49+
let create buffer ~when_ready_to_read =
50+
{ faraday = Faraday.of_bigstring buffer
4951
; read_scheduled = false
52+
; eof_has_been_called = false
5053
; on_eof = default_on_eof
5154
; on_read = default_on_read
55+
; when_ready_to_read
5256
}
5357

54-
let create buffer =
55-
of_faraday (Faraday.of_bigstring buffer)
56-
5758
let create_empty () =
58-
let t = create Bigstringaf.empty in
59+
let t =
60+
create
61+
Bigstringaf.empty
62+
~when_ready_to_read:Optional_thunk.none
63+
in
5964
Faraday.close t.faraday;
6065
t
6166

62-
let empty = create_empty ()
63-
6467
let is_closed t =
6568
Faraday.is_closed t.faraday
6669

6770
let unsafe_faraday t =
6871
t.faraday
6972

73+
let ready_to_read t = Optional_thunk.call_if_some t.when_ready_to_read
74+
7075
let rec do_execute_read t on_eof on_read =
7176
match Faraday.operation t.faraday with
7277
| `Yield -> ()
7378
| `Close ->
7479
t.read_scheduled <- false;
7580
t.on_eof <- default_on_eof;
7681
t.on_read <- default_on_read;
77-
on_eof ()
82+
if not t.eof_has_been_called then begin
83+
t.eof_has_been_called <- true;
84+
on_eof ();
85+
end
86+
(* [Faraday.operation] never returns an empty list of iovecs *)
7887
| `Writev [] -> assert false
7988
| `Writev (iovec::_) ->
8089
t.read_scheduled <- false;
@@ -96,10 +105,27 @@ module IOVec = Httpun.IOVec
96105
t.on_eof <- on_eof;
97106
t.on_read <- on_read;
98107
end;
99-
do_execute_read t on_eof on_read
100-
101-
let is_read_scheduled t = t.read_scheduled
108+
do_execute_read t on_eof on_read;
109+
ready_to_read t
102110

103111
let close t =
104112
Faraday.close t.faraday;
105-
execute_read t
113+
execute_read t;
114+
ready_to_read t
115+
;;
116+
117+
let has_pending_output t = Faraday.has_pending_output t.faraday
118+
119+
let is_read_scheduled t = t.read_scheduled
120+
121+
type input_state =
122+
| Ready
123+
| Wait
124+
| Complete
125+
126+
let input_state t : input_state =
127+
if is_closed t
128+
then Complete
129+
else if is_read_scheduled t
130+
then Ready
131+
else Wait

lib/server_connection.ml

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ let read_eof t bs ~off ~len =
103103
let yield_reader t f =
104104
match t.state with
105105
| Handshake handshake -> Server_handshake.yield_reader handshake f
106-
| Websocket _ -> assert false
106+
| Websocket websocket -> Websocket_connection.yield_reader websocket f
107107

108108
let next_write_operation t =
109109
match t.state with

0 commit comments

Comments
 (0)