572 lines
22 KiB
OCaml
572 lines
22 KiB
OCaml
|
|
(*----------------------------------------------------------------------------
|
||
|
|
* Copyright (c) 2019 António Nuno Monteiro
|
||
|
|
*
|
||
|
|
* All rights reserved.
|
||
|
|
*
|
||
|
|
* Redistribution and use in source and binary forms, with or without
|
||
|
|
* modification, are permitted provided that the following conditions are met:
|
||
|
|
*
|
||
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||
|
|
* this list of conditions and the following disclaimer.
|
||
|
|
*
|
||
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||
|
|
* notice, this list of conditions and the following disclaimer in the
|
||
|
|
* documentation and/or other materials provided with the distribution.
|
||
|
|
*
|
||
|
|
* 3. Neither the name of the copyright holder nor the names of its
|
||
|
|
* contributors may be used to endorse or promote products derived from this
|
||
|
|
* software without specific prior written permission.
|
||
|
|
*
|
||
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
||
|
|
*---------------------------------------------------------------------------*)
|
||
|
|
|
||
|
|
module StreamsTbl = struct
|
||
|
|
include Hashtbl.MakeSeeded (struct
|
||
|
|
type t = Stream_identifier.t
|
||
|
|
|
||
|
|
let equal = Stream_identifier.( === )
|
||
|
|
let hash i k = Hashtbl.seeded_hash i k
|
||
|
|
|
||
|
|
(* Required for OCaml >= 5.0.0, but causes errors for older compilers
|
||
|
|
because it is an unused value declaration. *)
|
||
|
|
let[@warning "-32"] seeded_hash = hash
|
||
|
|
end)
|
||
|
|
|
||
|
|
let[@inline] find_opt h key = try Some (find h key) with Not_found -> None
|
||
|
|
end
|
||
|
|
|
||
|
|
module type StreamDescriptor = sig
|
||
|
|
type t
|
||
|
|
|
||
|
|
val id : t -> Stream_identifier.t
|
||
|
|
val requires_output : t -> bool
|
||
|
|
val flush_write_body : t -> max_bytes:int -> int
|
||
|
|
val finish_stream : t -> Stream.closed_reason -> unit
|
||
|
|
val is_idle : t -> bool
|
||
|
|
end
|
||
|
|
|
||
|
|
module Make (Streamd : StreamDescriptor) = struct
|
||
|
|
module rec PriorityTreeNode : sig
|
||
|
|
type root = Root
|
||
|
|
type nonroot = NonRoot
|
||
|
|
|
||
|
|
type stream = nonroot node
|
||
|
|
and parent = Parent : _ node -> parent
|
||
|
|
|
||
|
|
and _ node =
|
||
|
|
(* From RFC7540§5.3.1:
|
||
|
|
* A stream that is not dependent on any other stream is given a stream
|
||
|
|
* dependency of 0x0. In other words, the non-existent stream 0 forms
|
||
|
|
* the root of the tree.
|
||
|
|
*
|
||
|
|
* Note:
|
||
|
|
* We use a GADT because the root of the tree doesn't have an
|
||
|
|
* associated request descriptor. It has the added advantage of
|
||
|
|
* allowing us to enforce that all (other) streams in the tree are
|
||
|
|
* associated with a request descriptor. *)
|
||
|
|
| Connection :
|
||
|
|
{ all_streams : stream StreamsTbl.t
|
||
|
|
; mutable t_last : int
|
||
|
|
; mutable children : PriorityQueue.t
|
||
|
|
; (* Connection-level flow control window.
|
||
|
|
* outbound flow control, what we're allowed to send.
|
||
|
|
*
|
||
|
|
* From RFC7540§6.9.1:
|
||
|
|
* Two flow-control windows are applicable: the stream
|
||
|
|
* flow-control window and the connection flow-control window. *)
|
||
|
|
mutable flow : Settings.WindowSize.t
|
||
|
|
; (* inbound flow control, what the client is allowed to send. *)
|
||
|
|
mutable inflow : Settings.WindowSize.t
|
||
|
|
; mutable marked_for_removal : Stream.closed StreamsTbl.t
|
||
|
|
}
|
||
|
|
-> root node
|
||
|
|
| Stream :
|
||
|
|
{ descriptor : Streamd.t
|
||
|
|
; mutable t_last : int
|
||
|
|
; mutable t : int
|
||
|
|
; mutable priority : Priority.t
|
||
|
|
; mutable parent : parent
|
||
|
|
; mutable children : PriorityQueue.t
|
||
|
|
; (* Stream-level flow control window. See connection-level above.
|
||
|
|
*
|
||
|
|
* From RFC7540§6.9.1:
|
||
|
|
* Two flow-control windows are applicable: the stream
|
||
|
|
* flow-control window and the connection flow-control window. *)
|
||
|
|
mutable flow : Settings.WindowSize.t
|
||
|
|
; mutable inflow : Settings.WindowSize.t
|
||
|
|
}
|
||
|
|
-> nonroot node
|
||
|
|
end =
|
||
|
|
PriorityTreeNode
|
||
|
|
|
||
|
|
and PriorityQueue :
|
||
|
|
(Psq.S with type k = Int32.t and type p = PriorityTreeNode.stream) =
|
||
|
|
Psq.Make
|
||
|
|
(Int32)
|
||
|
|
(struct
|
||
|
|
include PriorityTreeNode
|
||
|
|
|
||
|
|
type t = stream
|
||
|
|
|
||
|
|
let compare (Stream { t = t1; _ }) (Stream { t = t2; _ }) =
|
||
|
|
compare t1 t2
|
||
|
|
end)
|
||
|
|
|
||
|
|
include PriorityTreeNode
|
||
|
|
|
||
|
|
type t = root node
|
||
|
|
|
||
|
|
(* TODO(anmonteiro): change according to SETTINGS_MAX_CONCURRENT_STREAMS? *)
|
||
|
|
let make_root ?(capacity = 65536) () =
|
||
|
|
Connection
|
||
|
|
{ t_last = 0
|
||
|
|
; children = PriorityQueue.empty
|
||
|
|
; all_streams = StreamsTbl.create ~random:true capacity
|
||
|
|
; flow = Settings.WindowSize.default_initial_window_size
|
||
|
|
; inflow = Settings.WindowSize.default_initial_window_size
|
||
|
|
; marked_for_removal = StreamsTbl.create ~random:true 256
|
||
|
|
}
|
||
|
|
|
||
|
|
let create
|
||
|
|
~parent
|
||
|
|
~initial_send_window_size
|
||
|
|
~initial_recv_window_size
|
||
|
|
descriptor
|
||
|
|
=
|
||
|
|
Stream
|
||
|
|
{ descriptor
|
||
|
|
; t_last = 0
|
||
|
|
; t = 0
|
||
|
|
; (* From RFC7540§5.3.5:
|
||
|
|
* All streams are initially assigned a non-exclusive dependency on
|
||
|
|
* stream 0x0. Pushed streams (Section 8.2) initially depend on their
|
||
|
|
* associated stream. In both cases, streams are assigned a default
|
||
|
|
* weight of 16. *)
|
||
|
|
priority = Priority.default_priority
|
||
|
|
; parent
|
||
|
|
; children = PriorityQueue.empty
|
||
|
|
; flow = initial_send_window_size
|
||
|
|
; inflow = initial_recv_window_size
|
||
|
|
}
|
||
|
|
|
||
|
|
let stream_id : type a. a node -> int32 = function
|
||
|
|
| Connection _ -> Stream_identifier.connection
|
||
|
|
| Stream { descriptor; _ } -> Streamd.id descriptor
|
||
|
|
|
||
|
|
let children : type a. a node -> PriorityQueue.t = function
|
||
|
|
| Stream { children; _ } -> children
|
||
|
|
| Connection { children; _ } -> children
|
||
|
|
|
||
|
|
let remove_child : type a. a node -> int32 -> unit =
|
||
|
|
fun parent id ->
|
||
|
|
match parent with
|
||
|
|
| Connection ({ children; _ } as node) ->
|
||
|
|
(* From RFC7540§5.3.1:
|
||
|
|
* A stream that is not dependent on any other stream is given a stream
|
||
|
|
* dependency of 0x0. In other words, the non-existent stream 0 forms
|
||
|
|
* the root of the tree. *)
|
||
|
|
node.children <- PriorityQueue.remove id children
|
||
|
|
| Stream ({ children; _ } as node) ->
|
||
|
|
node.children <- PriorityQueue.remove id children
|
||
|
|
|
||
|
|
let update_children : type a. a node -> PriorityQueue.t -> unit =
|
||
|
|
fun parent updated_children ->
|
||
|
|
match parent with
|
||
|
|
| Connection s -> s.children <- updated_children
|
||
|
|
| Stream s -> s.children <- updated_children
|
||
|
|
|
||
|
|
let set_parent stream_node ~exclusive (Parent new_parent_node as new_parent) =
|
||
|
|
let (Stream ({ descriptor; parent = Parent old_parent_node; _ } as stream)) =
|
||
|
|
stream_node
|
||
|
|
in
|
||
|
|
let stream_id = Streamd.id descriptor in
|
||
|
|
remove_child old_parent_node stream_id;
|
||
|
|
stream.parent <- new_parent;
|
||
|
|
let new_children =
|
||
|
|
let new_children = children new_parent_node in
|
||
|
|
if exclusive
|
||
|
|
then (
|
||
|
|
(* From RFC7540§5.3.3:
|
||
|
|
* Dependent streams move with their parent stream if the parent is
|
||
|
|
* reprioritized. Setting a dependency with the exclusive flag for a
|
||
|
|
* reprioritized stream causes all the dependencies of the new parent
|
||
|
|
* stream to become dependent on the reprioritized stream. *)
|
||
|
|
stream.children <-
|
||
|
|
PriorityQueue.fold
|
||
|
|
(fun k (Stream p as p_node) pq ->
|
||
|
|
p.parent <- Parent stream_node;
|
||
|
|
PriorityQueue.add k p_node pq)
|
||
|
|
stream.children
|
||
|
|
new_children;
|
||
|
|
(* From RFC7540§5.3.1:
|
||
|
|
* An exclusive flag allows for the insertion of a new level of
|
||
|
|
* dependencies. The exclusive flag causes the stream to become the
|
||
|
|
* sole dependency of its parent stream, causing other dependencies
|
||
|
|
* to become dependent on the exclusive stream. *)
|
||
|
|
PriorityQueue.sg stream_id stream_node)
|
||
|
|
else PriorityQueue.add stream_id stream_node new_children
|
||
|
|
in
|
||
|
|
update_children new_parent_node new_children
|
||
|
|
|
||
|
|
let would_create_cycle ~new_parent (Stream { descriptor; _ }) =
|
||
|
|
let rec inner : type a. a node -> bool = function
|
||
|
|
| Connection _ -> false
|
||
|
|
| Stream { parent = Parent parent; _ }
|
||
|
|
when Stream_identifier.(stream_id parent === Streamd.id descriptor) ->
|
||
|
|
true
|
||
|
|
| Stream { parent = Parent parent; _ } -> inner parent
|
||
|
|
in
|
||
|
|
let (Parent parent_node) = new_parent in
|
||
|
|
inner parent_node
|
||
|
|
|
||
|
|
let reprioritize_stream (Connection root as t) ~priority stream_node =
|
||
|
|
let (Stream stream) = stream_node in
|
||
|
|
let new_parent, new_priority =
|
||
|
|
if Stream_identifier.is_connection priority.Priority.stream_dependency
|
||
|
|
then Parent t, priority
|
||
|
|
else
|
||
|
|
match
|
||
|
|
StreamsTbl.find_opt root.all_streams priority.stream_dependency
|
||
|
|
with
|
||
|
|
| Some parent_stream ->
|
||
|
|
(match
|
||
|
|
StreamsTbl.mem root.marked_for_removal priority.stream_dependency
|
||
|
|
with
|
||
|
|
| true ->
|
||
|
|
(* A stream that is marked for removal is also not present in the
|
||
|
|
tree *)
|
||
|
|
Parent t, Priority.default_priority
|
||
|
|
| false -> Parent parent_stream, priority)
|
||
|
|
| None ->
|
||
|
|
(* From RFC7540§5.3.1:
|
||
|
|
* A dependency on a stream that is not currently in the tree —
|
||
|
|
* such as a stream in the "idle" state — results in that stream
|
||
|
|
* being given a default priority (Section 5.3.5). *)
|
||
|
|
Parent t, Priority.default_priority
|
||
|
|
in
|
||
|
|
(* bail early if trying to set the same priority *)
|
||
|
|
if not (Priority.equal stream.priority new_priority)
|
||
|
|
then (
|
||
|
|
let { Priority.stream_dependency; exclusive; _ } = new_priority in
|
||
|
|
let (Parent current_parent_node) = stream.parent in
|
||
|
|
let current_parent_id = stream_id current_parent_node in
|
||
|
|
(* only need to set a different parent if the parent or exclusive status
|
||
|
|
* changed *)
|
||
|
|
if (not Stream_identifier.(stream_dependency === current_parent_id))
|
||
|
|
|| exclusive <> stream.priority.exclusive
|
||
|
|
then (
|
||
|
|
let (Parent new_parent_node) = new_parent in
|
||
|
|
(match new_parent_node with
|
||
|
|
| Stream new_parent_stream ->
|
||
|
|
if would_create_cycle ~new_parent stream_node
|
||
|
|
then (
|
||
|
|
(* From RFC7540§5.3.3:
|
||
|
|
* If a stream is made dependent on one of its own dependencies,
|
||
|
|
* the formerly dependent stream is first moved to be dependent
|
||
|
|
* on the reprioritized stream's previous parent. The moved
|
||
|
|
* dependency retains its weight. *)
|
||
|
|
set_parent new_parent_node ~exclusive:false stream.parent;
|
||
|
|
new_parent_stream.priority <-
|
||
|
|
{ new_parent_stream.priority with
|
||
|
|
stream_dependency = current_parent_id
|
||
|
|
})
|
||
|
|
| Connection _ ->
|
||
|
|
(* The root node cannot be dependent on any other streams, so we
|
||
|
|
* don't need to worry about it creating cycles. *)
|
||
|
|
());
|
||
|
|
(* From RFC7540§5.3.1:
|
||
|
|
* When assigning a dependency on another stream, the stream is added
|
||
|
|
* as a new dependency of the parent stream. *)
|
||
|
|
set_parent stream_node ~exclusive new_parent);
|
||
|
|
stream.priority <- new_priority)
|
||
|
|
|
||
|
|
let update_t node n =
|
||
|
|
let (Stream ({ parent = Parent parent; descriptor; _ } as stream)) = node in
|
||
|
|
let tlast_p =
|
||
|
|
match parent with
|
||
|
|
| Connection { t_last; _ } -> t_last
|
||
|
|
| Stream { t_last; _ } -> t_last
|
||
|
|
in
|
||
|
|
stream.t <- tlast_p + (n * 256 / stream.priority.weight);
|
||
|
|
let id = Streamd.id descriptor in
|
||
|
|
remove_child parent id;
|
||
|
|
let updated_children = PriorityQueue.add id node (children parent) in
|
||
|
|
update_children parent updated_children
|
||
|
|
|
||
|
|
let update_t_last : type a. a node -> int -> unit =
|
||
|
|
fun p_node t_last ->
|
||
|
|
match p_node with
|
||
|
|
| Connection p -> p.t_last <- t_last
|
||
|
|
| Stream p -> p.t_last <- t_last
|
||
|
|
|
||
|
|
let add
|
||
|
|
(Connection root as t)
|
||
|
|
~priority
|
||
|
|
~initial_send_window_size
|
||
|
|
~initial_recv_window_size
|
||
|
|
descriptor
|
||
|
|
=
|
||
|
|
let stream =
|
||
|
|
create
|
||
|
|
~parent:(Parent t)
|
||
|
|
~initial_send_window_size
|
||
|
|
~initial_recv_window_size
|
||
|
|
descriptor
|
||
|
|
in
|
||
|
|
let stream_id = Streamd.id descriptor in
|
||
|
|
StreamsTbl.add root.all_streams stream_id stream;
|
||
|
|
root.children <- PriorityQueue.add stream_id stream root.children;
|
||
|
|
if priority != Priority.default_priority
|
||
|
|
then reprioritize_stream t ~priority stream;
|
||
|
|
update_t stream 0;
|
||
|
|
stream
|
||
|
|
|
||
|
|
let get_node (Connection root) stream_id =
|
||
|
|
StreamsTbl.find_opt root.all_streams stream_id
|
||
|
|
|
||
|
|
let find t stream_id =
|
||
|
|
match get_node t stream_id with
|
||
|
|
| Some (Stream { descriptor; _ }) -> Some descriptor
|
||
|
|
| None -> None
|
||
|
|
|
||
|
|
let iter (Connection { all_streams; _ }) ~f =
|
||
|
|
StreamsTbl.iter (fun _id stream -> f stream) all_streams
|
||
|
|
|
||
|
|
let allowed_to_transmit (Connection root) (Stream stream) =
|
||
|
|
Int32.compare root.flow 0l > 0 && Int32.compare stream.flow 0l > 0
|
||
|
|
|
||
|
|
let allowed_to_receive (Connection root) (Stream stream) size =
|
||
|
|
size <= root.inflow && size <= stream.inflow
|
||
|
|
|
||
|
|
let write (Connection root as t) stream_node =
|
||
|
|
let (Stream ({ descriptor; _ } as stream)) = stream_node in
|
||
|
|
(* From RFC7540§6.9.1:
|
||
|
|
* Two flow-control windows are applicable: the stream flow-control
|
||
|
|
* window and the connection flow-control window. The sender MUST NOT
|
||
|
|
* send a flow-controlled frame with a length that exceeds the space
|
||
|
|
* available in either of the flow-control windows advertised by the
|
||
|
|
* receiver. *)
|
||
|
|
let allowed_bytes =
|
||
|
|
if allowed_to_transmit t stream_node
|
||
|
|
then min root.flow stream.flow
|
||
|
|
else
|
||
|
|
(* There might be a zero-length DATA frame (with the end stream flag
|
||
|
|
set) waiting to be sent. *)
|
||
|
|
0l
|
||
|
|
in
|
||
|
|
let written =
|
||
|
|
Streamd.flush_write_body
|
||
|
|
~max_bytes:(Int32.to_int allowed_bytes)
|
||
|
|
descriptor
|
||
|
|
in
|
||
|
|
let written32 = Int32.of_int written in
|
||
|
|
(* From RFC7540§6.9.1:
|
||
|
|
* After sending a flow-controlled frame, the sender reduces the space
|
||
|
|
* available in both windows by the length of the transmitted frame. *)
|
||
|
|
root.flow <- Int32.sub root.flow written32;
|
||
|
|
stream.flow <- Int32.sub stream.flow written32;
|
||
|
|
written
|
||
|
|
|
||
|
|
let mark_for_removal (Connection root) id closed =
|
||
|
|
StreamsTbl.replace root.marked_for_removal id closed
|
||
|
|
|
||
|
|
let implicitly_close_idle_stream descriptor max_seen_ids =
|
||
|
|
let implicitly_close_stream descriptor =
|
||
|
|
if Streamd.is_idle descriptor
|
||
|
|
then
|
||
|
|
(* From RFC7540§5.1.1:
|
||
|
|
* The first use of a new stream identifier implicitly closes all
|
||
|
|
* streams in the "idle" state that might have been initiated by
|
||
|
|
* that peer with a lower-valued stream identifier. *)
|
||
|
|
Streamd.finish_stream descriptor Finished
|
||
|
|
in
|
||
|
|
let max_client_stream_id, max_pushed_stream_id = max_seen_ids in
|
||
|
|
let stream_id = Streamd.id descriptor in
|
||
|
|
if Stream_identifier.is_request stream_id
|
||
|
|
then (
|
||
|
|
if stream_id < max_client_stream_id
|
||
|
|
then implicitly_close_stream descriptor)
|
||
|
|
else if stream_id < max_pushed_stream_id
|
||
|
|
then implicitly_close_stream descriptor
|
||
|
|
|
||
|
|
(* Scheduling algorithm from https://goo.gl/3sSHXJ (based on nghttp2):
|
||
|
|
*
|
||
|
|
* 1 def schedule(p):
|
||
|
|
* 2 if stream #p has data to send:
|
||
|
|
* 3 send data for #p, update nsent[p]
|
||
|
|
* 4 return
|
||
|
|
* 5 if #p's queue is empty:
|
||
|
|
* 6 return
|
||
|
|
* 7 pop #i from queue
|
||
|
|
* 8 update t_last[p] = t[i]
|
||
|
|
* 9 schedule(i)
|
||
|
|
* 10 if #i or its descendant is "active":
|
||
|
|
* 11 update t[i] and push it into queue again
|
||
|
|
* 12
|
||
|
|
* 13 schedule(0)
|
||
|
|
*)
|
||
|
|
let flush t max_seen_ids =
|
||
|
|
let rec schedule : type a. a node -> int * bool = function
|
||
|
|
| Connection _ as p_node ->
|
||
|
|
(* The root can never send data. *)
|
||
|
|
traverse p_node
|
||
|
|
| Stream ({ descriptor; _ } as stream) as p_node ->
|
||
|
|
let written =
|
||
|
|
if Streamd.requires_output descriptor
|
||
|
|
then
|
||
|
|
(* In this branch, flow-control has no bearing on activity, otherwise
|
||
|
|
* a flow-controlled stream would be considered inactive (because it
|
||
|
|
* can't make progress at the moment) and removed from the priority
|
||
|
|
* tree altogether. *)
|
||
|
|
write t p_node
|
||
|
|
else 0
|
||
|
|
in
|
||
|
|
if written > 0
|
||
|
|
then
|
||
|
|
(* We check for activity again, because the stream may have gone
|
||
|
|
* inactive after the call to `write` above. *)
|
||
|
|
let subtree_is_active =
|
||
|
|
Streamd.requires_output descriptor
|
||
|
|
|| not (PriorityQueue.is_empty stream.children)
|
||
|
|
in
|
||
|
|
written, subtree_is_active
|
||
|
|
else
|
||
|
|
(* If we haven't written anything, check if any of our children
|
||
|
|
have. *)
|
||
|
|
let written, subtree_is_active' = traverse p_node in
|
||
|
|
let subtree_is_active =
|
||
|
|
Streamd.requires_output descriptor || subtree_is_active'
|
||
|
|
in
|
||
|
|
(match written with
|
||
|
|
| 0 -> written, subtree_is_active
|
||
|
|
| written ->
|
||
|
|
(* If there's still more to write, put the node back in the tree. *)
|
||
|
|
if subtree_is_active then update_t p_node written;
|
||
|
|
written, subtree_is_active)
|
||
|
|
and traverse : type a. a node -> int * bool =
|
||
|
|
fun p_node ->
|
||
|
|
let rec loop remaining_children =
|
||
|
|
match PriorityQueue.pop remaining_children with
|
||
|
|
| Some ((id, (Stream i as i_node)), remaining_children') ->
|
||
|
|
update_t_last p_node i.t;
|
||
|
|
let written, subtree_is_active = schedule i_node in
|
||
|
|
|
||
|
|
if not subtree_is_active
|
||
|
|
then (
|
||
|
|
implicitly_close_idle_stream i.descriptor max_seen_ids;
|
||
|
|
(* XXX(anmonteiro): we may not want to remove from the tree right
|
||
|
|
* away. *)
|
||
|
|
remove_child p_node id);
|
||
|
|
|
||
|
|
(match written with
|
||
|
|
| 0 ->
|
||
|
|
(* If this subtree didn't write anything, check the other children
|
||
|
|
in the priority queue. *)
|
||
|
|
loop remaining_children'
|
||
|
|
| written ->
|
||
|
|
(* If there's still more to write, put the node back in the tree. *)
|
||
|
|
if subtree_is_active then update_t i_node written;
|
||
|
|
written, subtree_is_active)
|
||
|
|
| None ->
|
||
|
|
(* No data written, but queue was not originally empty.
|
||
|
|
* Therefore, we can't determine the subtree is inactive. *)
|
||
|
|
0, true
|
||
|
|
in
|
||
|
|
let children = children p_node in
|
||
|
|
match PriorityQueue.is_empty children with
|
||
|
|
| true ->
|
||
|
|
(* Queue is empty, see line 6 above. *)
|
||
|
|
0, false
|
||
|
|
| false -> loop children
|
||
|
|
in
|
||
|
|
|
||
|
|
let (Connection root) = t in
|
||
|
|
ignore (schedule t);
|
||
|
|
StreamsTbl.iter
|
||
|
|
(fun id closed ->
|
||
|
|
(* When a stream completes, i.e. doesn't require more output and
|
||
|
|
* enters the `Closed` state, we set a TTL value which represents the
|
||
|
|
* number of writer yields that the stream has before it is removed
|
||
|
|
* from the connection Hash Table. By doing this we avoid losing some
|
||
|
|
* potentially useful information regarding the stream's state at the
|
||
|
|
* cost of keeping it around for a little while longer. *)
|
||
|
|
if closed.Stream.ttl = 0
|
||
|
|
then (
|
||
|
|
StreamsTbl.remove root.marked_for_removal id;
|
||
|
|
StreamsTbl.remove root.all_streams id)
|
||
|
|
else closed.ttl <- closed.ttl - 1)
|
||
|
|
root.marked_for_removal
|
||
|
|
|
||
|
|
(* XXX(anmonteiro): Consider using `optint` for this?
|
||
|
|
* https://github.com/mirage/optint
|
||
|
|
*)
|
||
|
|
let check_flow flow growth flow' =
|
||
|
|
(* Check for overflow on 32-bit systems. *)
|
||
|
|
Int32.compare flow' growth > 0 = (Int32.compare flow 0l > 0)
|
||
|
|
&& Int32.compare flow' Settings.WindowSize.max_window_size <= 0
|
||
|
|
|
||
|
|
let add_flow : type a. a node -> int32 -> bool =
|
||
|
|
fun t growth ->
|
||
|
|
match t with
|
||
|
|
| Connection ({ flow; _ } as root) ->
|
||
|
|
let flow' = Int32.add flow growth in
|
||
|
|
let valid_flow = check_flow flow growth flow' in
|
||
|
|
if valid_flow then root.flow <- flow';
|
||
|
|
valid_flow
|
||
|
|
| Stream ({ flow; _ } as stream) ->
|
||
|
|
let flow' = Int32.add flow growth in
|
||
|
|
let valid_flow = check_flow flow growth flow' in
|
||
|
|
if valid_flow then stream.flow <- flow';
|
||
|
|
valid_flow
|
||
|
|
|
||
|
|
let add_inflow : type a. a node -> int32 -> bool =
|
||
|
|
fun t growth ->
|
||
|
|
match t with
|
||
|
|
| Connection ({ inflow; _ } as root) ->
|
||
|
|
let inflow' = Int32.add inflow growth in
|
||
|
|
let valid_inflow = check_flow inflow growth inflow' in
|
||
|
|
if valid_inflow then root.inflow <- inflow';
|
||
|
|
valid_inflow
|
||
|
|
| Stream ({ inflow; _ } as stream) ->
|
||
|
|
let inflow' = Int32.add inflow growth in
|
||
|
|
let valid_inflow = check_flow inflow growth inflow' in
|
||
|
|
if valid_inflow then stream.inflow <- inflow';
|
||
|
|
valid_inflow
|
||
|
|
|
||
|
|
let deduct_inflow : type a. a node -> int32 -> unit =
|
||
|
|
fun t size ->
|
||
|
|
match t with
|
||
|
|
| Connection ({ inflow; _ } as root) ->
|
||
|
|
(* no need to check, we verify that the peer is allowed to send. *)
|
||
|
|
root.inflow <- Int32.sub inflow size
|
||
|
|
| Stream ({ inflow; _ } as stream) -> stream.inflow <- Int32.sub inflow size
|
||
|
|
|
||
|
|
let pp_hum fmt t =
|
||
|
|
let rec pp_hum_inner level fmt t =
|
||
|
|
let pp_binding fmt (id, Stream { children; t; _ }) =
|
||
|
|
Format.fprintf
|
||
|
|
fmt
|
||
|
|
"\n%s%ld, %d -> [%a]"
|
||
|
|
(String.make (level * 2) ' ')
|
||
|
|
id
|
||
|
|
t
|
||
|
|
(pp_hum_inner (level + 1))
|
||
|
|
children
|
||
|
|
in
|
||
|
|
PriorityQueue.pp pp_binding fmt t
|
||
|
|
in
|
||
|
|
pp_hum_inner 0 fmt t
|
||
|
|
|
||
|
|
let pp_hum fmt (Connection { children; _ }) = pp_hum fmt children
|
||
|
|
end
|