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-agnostic TCP/IP connection pooling library for Eio *)
7
8(** {1 Logging} *)
9
10val src : Logs.Src.t
11(** Logs source for the main connection pool. Configure logging with:
12 {[
13 Logs.Src.set_level Conpool.src (Some Logs.Debug);
14 Logs.set_reporter (Logs_fmt.reporter ())
15 ]}
16
17 Each submodule also exposes its own log source for fine-grained control:
18 - {!Endpoint.src} - endpoint operations
19 - {!Config.src} - pool configuration *)
20
21(** {1 Core Types} *)
22
23module Endpoint = Endpoint
24(** Network endpoint representation *)
25
26module Config = Config
27(** Configuration for connection pools *)
28
29module Stats = Stats
30(** Statistics for connection pool endpoints *)
31
32module Cmd = Cmd
33(** Cmdliner terms for connection pool configuration *)
34
35(** {1 Errors} *)
36
37type error =
38 | Dns_resolution_failed of { hostname : string }
39 (** DNS resolution failed for the given hostname *)
40 | Connection_failed of {
41 endpoint : Endpoint.t;
42 attempts : int;
43 last_error : string;
44 } (** Failed to establish connection after all retry attempts *)
45 | Connection_timeout of { endpoint : Endpoint.t; timeout : float }
46 (** Connection attempt timed out *)
47 | Invalid_config of string (** Invalid configuration parameter *)
48 | Invalid_endpoint of string (** Invalid endpoint specification *)
49
50type Eio.Exn.err += E of error
51(** Extension of Eio's error type for connection pool errors.
52
53 Pool operations raise [Eio.Io] exceptions with context information added at
54 each layer. The innermost error is often [E error], wrapped with context
55 strings that describe the operation being performed.
56
57 Example error message:
58 {[
59 Eio.Io Conpool Dns_resolution_failed { hostname = "invalid.example" },
60 resolving invalid.example:443,
61 connecting to invalid.example:443,
62 after 3 retry attempts
63 ]}
64
65 Use {!pp_error} to format just the error code, or let Eio format the full
66 exception with context. *)
67
68val err : error -> exn
69(** [err e] is [Eio.Exn.create (E e)].
70
71 This converts a connection pool error to an Eio exception, allowing it to
72 be handled uniformly with other Eio I/O errors and enabling context to be
73 added via [Eio.Exn.reraise_with_context]. *)
74
75val pp_error : error Fmt.t
76(** Pretty-printer for error values (without context).
77
78 For full error messages including context, use [Eio.Exn.pp] or simply let
79 the exception be printed naturally. *)
80
81(** {1 Connection Types} *)
82
83type connection_ty = [Eio.Resource.close_ty | Eio.Flow.two_way_ty]
84(** The type tags for a pooled connection.
85 Connections support reading, writing, shutdown, and closing. *)
86
87type connection = connection_ty Eio.Resource.t
88(** A connection resource from the pool. *)
89
90(** {1 Connection Pool} *)
91
92type t
93(** Connection pool managing multiple endpoints *)
94
95val create :
96 sw:Eio.Switch.t ->
97 net:'net Eio.Net.t ->
98 clock:'clock Eio.Time.clock ->
99 ?tls:Tls.Config.client ->
100 ?config:Config.t ->
101 unit ->
102 t
103(** Create connection pool bound to switch. All connections will be closed when
104 switch is released.
105
106 @param sw Switch for resource management
107 @param net Network interface for creating connections
108 @param clock Clock for timeouts and time-based validation
109 @param tls
110 Optional TLS client configuration applied to all connections. SNI
111 servername is automatically set to the endpoint's hostname.
112 @param config
113 Optional pool configuration (uses Config.default if not provided) *)
114
115(** {1 Connection Usage} *)
116
117val connection : sw:Eio.Switch.t -> t -> Endpoint.t -> connection
118(** [connection ~sw pool endpoint] acquires a connection from the pool.
119
120 The connection is automatically returned to the pool when [sw] finishes.
121 If the connection becomes unhealthy or an error occurs during use, it is
122 closed instead of being returned to the pool.
123
124 If an idle connection is available and healthy:
125 - Reuse from pool (validates health first)
126
127 Otherwise:
128 - Create new connection (may block if endpoint at limit)
129
130 Example:
131 {[
132 let endpoint = Conpool.Endpoint.make ~host:"example.com" ~port:443 in
133 Eio.Switch.run (fun sw ->
134 let conn = Conpool.connection ~sw pool endpoint in
135 Eio.Flow.copy_string "GET / HTTP/1.1\r\n\r\n" conn;
136 let buf = Eio.Buf_read.of_flow conn ~max_size:4096 in
137 Eio.Buf_read.take_all buf)
138 ]} *)
139
140val with_connection : t -> Endpoint.t -> (connection -> 'a) -> 'a
141(** [with_connection pool endpoint fn] is a convenience wrapper around
142 {!val:connection}.
143
144 Equivalent to:
145 {[
146 Eio.Switch.run (fun sw -> fn (connection ~sw pool endpoint))
147 ]}
148
149 Example:
150 {[
151 let endpoint = Conpool.Endpoint.make ~host:"example.com" ~port:443 in
152 Conpool.with_connection pool endpoint (fun conn ->
153 (* Use conn for HTTP request, Redis command, etc. *)
154 Eio.Flow.copy_string "GET / HTTP/1.1\r\n\r\n" conn;
155 let buf = Eio.Buf_read.of_flow conn ~max_size:4096 in
156 Eio.Buf_read.take_all buf)
157 ]} *)
158
159(** {1 Statistics & Monitoring} *)
160
161val stats : t -> Endpoint.t -> Stats.t
162(** Get statistics for specific endpoint *)
163
164val all_stats : t -> (Endpoint.t * Stats.t) list
165(** Get statistics for all endpoints in pool *)
166
167(** {1 Pool Management} *)
168
169val clear_endpoint : t -> Endpoint.t -> unit
170(** Clear all cached connections for a specific endpoint.
171
172 This removes the endpoint from the pool, discarding all idle connections.
173 Active connections will continue to work but won't be returned to the pool.
174
175 Use this when you know an endpoint's connections are no longer valid (e.g.,
176 server restarted, network reconfigured, credentials changed).
177
178 The pool will be automatically cleaned up when its switch is released. *)