TCP/TLS connection pooling for Eio
at main 206 lines 6.5 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6(** Conpool - Protocol-aware TCP/IP connection pooling library for Eio 7 8 Conpool provides efficient connection pooling with support for both 9 exclusive (HTTP/1.x) and shared (HTTP/2) access modes. All connections 10 carry protocol-specific state managed through callbacks. 11 12 {2 Quick Start} 13 14 For simple exclusive-access protocols (HTTP/1.x, Redis, etc.): 15 {[ 16 let pool = Conpool.create_basic ~sw ~net ~clock ~tls () in 17 Eio.Switch.run (fun conn_sw -> 18 let conn = Conpool.connection ~sw:conn_sw pool endpoint in 19 (* Use conn.flow for I/O *) 20 Eio.Flow.copy_string "GET / HTTP/1.1\r\n\r\n" conn.flow) 21 ]} 22 23 For multiplexed protocols (HTTP/2): 24 {[ 25 let pool = Conpool.create ~sw ~net ~clock ~tls ~protocol:h2_handler () in 26 Eio.Switch.run (fun conn_sw -> 27 let conn = Conpool.connection ~sw:conn_sw pool endpoint in 28 (* conn.state has H2_client.t, multiple streams share the connection *) 29 H2_client.request conn.flow conn.state ...) 30 ]} *) 31 32(** {1 Logging} *) 33 34val src : Logs.Src.t 35(** Logs source for the connection pool. Configure logging with: 36 {[ 37 Logs.Src.set_level Conpool.src (Some Logs.Debug); 38 Logs.set_reporter (Logs_fmt.reporter ()) 39 ]} *) 40 41(** {1 Core Types} *) 42 43module Endpoint = Endpoint 44(** Network endpoint representation *) 45 46module Config = Config 47(** Configuration for connection pools *) 48 49module Stats = Stats 50(** Statistics for connection pool endpoints *) 51 52module Cmd = Cmd 53(** Cmdliner terms for connection pool configuration *) 54 55(** {1 Errors} *) 56 57type error = 58 | Dns_resolution_failed of { hostname : string } 59 | Connection_failed of { 60 endpoint : Endpoint.t; 61 attempts : int; 62 last_error : string; 63 } 64 | Connection_timeout of { endpoint : Endpoint.t; timeout : float } 65 | Invalid_config of string 66 | Invalid_endpoint of string 67 68type Eio.Exn.err += E of error 69 70val err : error -> exn 71(** [err e] creates an Eio exception from a connection pool error. *) 72 73val pp_error : error Fmt.t 74(** Pretty-printer for error values. *) 75 76(** {1 Connection Types} *) 77 78type connection_ty = [Eio.Resource.close_ty | Eio.Flow.two_way_ty] 79(** Type tags for a pooled connection. *) 80 81type connection = connection_ty Eio.Resource.t 82(** A connection resource from the pool. *) 83 84(** {1 Connection Pool} 85 86 All pools are typed - they carry protocol-specific state with each 87 connection. For simple exclusive-access protocols, use the default 88 [unit] state which requires no protocol handler. *) 89 90type 'state t 91(** Connection pool with protocol-specific state ['state]. 92 93 - For HTTP/1.x: use [unit t] with exclusive access (one request per connection) 94 - For HTTP/2: use [h2_state t] with shared access (multiple streams per connection) *) 95 96(** Connection with protocol-specific state. *) 97type 'state connection_info = { 98 flow : connection; 99 (** The underlying connection flow for I/O. *) 100 tls_epoch : Tls.Core.epoch_data option; 101 (** TLS epoch data if connection uses TLS. *) 102 state : 'state; 103 (** Protocol-specific state (e.g., H2_client.t for HTTP/2). *) 104} 105 106(** {2 Pool Creation} *) 107 108val default_protocol : unit Config.protocol_config 109(** Default protocol configuration for simple exclusive-access protocols. 110 Use with {!create} for HTTP/1.x, Redis, and similar protocols where 111 each connection handles one request at a time with no extra state. *) 112 113val create : 114 sw:Eio.Switch.t -> 115 net:'net Eio.Net.t -> 116 clock:'clock Eio.Time.clock -> 117 ?tls:Tls.Config.client -> 118 ?config:Config.t -> 119 protocol:'state Config.protocol_config -> 120 unit -> 121 'state t 122(** Create a connection pool with a protocol handler. 123 124 @param sw Switch for resource management 125 @param net Network interface for creating connections 126 @param clock Clock for timeouts 127 @param tls Optional TLS client configuration 128 @param config Pool configuration (uses {!Config.default} if not provided) 129 @param protocol Protocol handler for state management 130 131 Examples: 132 133 Simple pool for HTTP/1.x (exclusive access, no state): 134 {[ 135 let pool = Conpool.create ~sw ~net ~clock ~tls 136 ~protocol:Conpool.default_protocol () 137 ]} 138 139 HTTP/2 pool (shared access with H2 state): 140 {[ 141 let pool = Conpool.create ~sw ~net ~clock ~tls ~protocol:h2_handler () 142 ]} *) 143 144val create_basic : 145 sw:Eio.Switch.t -> 146 net:'net Eio.Net.t -> 147 clock:'clock Eio.Time.clock -> 148 ?tls:Tls.Config.client -> 149 ?config:Config.t -> 150 unit -> 151 unit t 152(** Create a basic connection pool with no protocol state. 153 154 This is a convenience function equivalent to: 155 {[ 156 Conpool.create ~sw ~net ~clock ?tls ?config 157 ~protocol:Conpool.default_protocol () 158 ]} 159 160 Use for simple exclusive-access protocols like HTTP/1.x and Redis. 161 162 Example: 163 {[ 164 let pool = Conpool.create_basic ~sw ~net ~clock ~tls () 165 ]} *) 166 167(** {2 Connection Acquisition} *) 168 169val connection : sw:Eio.Switch.t -> 'state t -> Endpoint.t -> 'state connection_info 170(** [connection ~sw pool endpoint] acquires a connection from the pool. 171 172 The connection is automatically released when [sw] finishes: 173 - Exclusive mode: connection returns to idle pool 174 - Shared mode: user count is decremented 175 176 Behavior depends on access mode: 177 - Exclusive: blocks until a connection is available 178 - Shared: may share an existing connection if under max_concurrent limit 179 180 Example: 181 {[ 182 Eio.Switch.run (fun sw -> 183 let conn = Conpool.connection ~sw pool endpoint in 184 (* For HTTP/1.x: conn.state is () *) 185 (* For HTTP/2: conn.state is H2_client.t *) 186 Eio.Flow.copy_string data conn.flow) 187 ]} *) 188 189val with_connection : 'state t -> Endpoint.t -> ('state connection_info -> 'a) -> 'a 190(** [with_connection pool endpoint fn] is a convenience wrapper. 191 192 Equivalent to: 193 {[ 194 Eio.Switch.run (fun sw -> fn (connection ~sw pool endpoint)) 195 ]} *) 196 197(** {1 Statistics & Management} *) 198 199val stats : 'state t -> Endpoint.t -> Stats.t 200(** Get statistics for specific endpoint. *) 201 202val all_stats : 'state t -> (Endpoint.t * Stats.t) list 203(** Get statistics for all endpoints in pool. *) 204 205val clear_endpoint : 'state t -> Endpoint.t -> unit 206(** Clear all connections for an endpoint. *)