TCP/TLS connection pooling for Eio
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. *)