···4343 | Invalid_config msg -> Fmt.pf ppf "Invalid configuration: %s" msg
4444 | Invalid_endpoint msg -> Fmt.pf ppf "Invalid endpoint: %s" msg
45454646+type Eio.Exn.err += E of error
4747+4848+let err e = Eio.Exn.create (E e)
4949+5050+let () =
5151+ Eio.Exn.register_pp (fun f -> function
5252+ | E e ->
5353+ Fmt.string f "Conpool ";
5454+ pp_error f e;
5555+ true
5656+ | _ -> false)
5757+5858+(** {1 Connection Types} *)
5959+6060+type connection_ty = [Eio.Resource.close_ty | Eio.Flow.two_way_ty]
6161+type connection = connection_ty Eio.Resource.t
6262+4663type endp_stats = {
4764 mutable active : int;
4865 mutable idle : int;
···152169 let flow =
153170 match pool.tls with
154171 | None ->
155155- (socket :> [ `Close | `Flow | `R | `Shutdown | `W ] Eio.Resource.t)
172172+ (socket :> connection)
156173 | Some tls_cfg ->
157174 Log.debug (fun m ->
158175 m "Initiating TLS handshake with %a" Endpoint.pp endpoint);
···167184 in
168185 Log.info (fun m ->
169186 m "TLS connection established to %a" Endpoint.pp endpoint);
170170- (tls_flow :> [ `Close | `Flow | `R | `Shutdown | `W ] Eio.Resource.t)
187187+ (tls_flow :> connection)
171188 in
172189173190 let now = get_time pool in
···474491475492(** {1 Public API - Connection Management} *)
476493477477-let with_connection (T pool) endpoint f =
494494+let connection_internal ~sw (T pool) endpoint =
478495 Log.debug (fun m -> m "Acquiring connection to %a" Endpoint.pp endpoint);
479496 let ep_pool = get_or_create_endpoint_pool pool endpoint in
497497+498498+ (* Create promises for connection handoff and cleanup signal *)
499499+ let conn_promise, conn_resolver = Eio.Promise.create () in
500500+ let done_promise, done_resolver = Eio.Promise.create () in
480501481502 (* Increment active count *)
482503 Eio.Mutex.use_rw ~protect:true ep_pool.mutex (fun () ->
483504 ep_pool.stats.active <- ep_pool.stats.active + 1);
484505485485- Fun.protect
486486- ~finally:(fun () ->
487487- (* Decrement active count *)
488488- Eio.Mutex.use_rw ~protect:true ep_pool.mutex (fun () ->
489489- ep_pool.stats.active <- ep_pool.stats.active - 1);
490490- Log.debug (fun m -> m "Released connection to %a" Endpoint.pp endpoint))
491491- (fun () ->
492492- (* Use Eio.Pool for resource management *)
493493- Eio.Pool.use ep_pool.pool (fun conn ->
494494- Log.debug (fun m ->
495495- m "Using connection to %a (uses=%d)" Endpoint.pp endpoint
496496- (Connection.use_count conn));
506506+ (* Fork a daemon fiber to manage the connection lifecycle *)
507507+ Eio.Fiber.fork_daemon ~sw (fun () ->
508508+ Fun.protect
509509+ ~finally:(fun () ->
510510+ (* Decrement active count *)
511511+ Eio.Mutex.use_rw ~protect:true ep_pool.mutex (fun () ->
512512+ ep_pool.stats.active <- ep_pool.stats.active - 1);
513513+ Log.debug (fun m -> m "Released connection to %a" Endpoint.pp endpoint))
514514+ (fun () ->
515515+ (* Use Eio.Pool for resource management *)
516516+ Eio.Pool.use ep_pool.pool (fun conn ->
517517+ Log.debug (fun m ->
518518+ m "Using connection to %a (uses=%d)" Endpoint.pp endpoint
519519+ (Connection.use_count conn));
520520+521521+ (* Update last used time and use count *)
522522+ Connection.update_usage conn ~now:(get_time pool);
523523+524524+ (* Update idle stats (connection taken from idle pool) *)
525525+ Eio.Mutex.use_rw ~protect:true ep_pool.mutex (fun () ->
526526+ ep_pool.stats.idle <- max 0 (ep_pool.stats.idle - 1));
527527+528528+ (* Hand off connection to caller *)
529529+ Eio.Promise.resolve conn_resolver conn.flow;
530530+531531+ try
532532+ (* Wait for switch to signal cleanup *)
533533+ Eio.Promise.await done_promise;
534534+535535+ (* Success - connection will be returned to pool by Eio.Pool *)
536536+ (* Update idle stats (connection returned to idle pool) *)
537537+ Eio.Mutex.use_rw ~protect:true ep_pool.mutex (fun () ->
538538+ ep_pool.stats.idle <- ep_pool.stats.idle + 1);
539539+540540+ `Stop_daemon
541541+ with e ->
542542+ (* Error - close connection so it won't be reused *)
543543+ Log.warn (fun m ->
544544+ m "Error with connection to %a: %s" Endpoint.pp endpoint
545545+ (Printexc.to_string e));
546546+ close_internal pool conn;
497547498498- (* Update last used time and use count *)
499499- Connection.update_usage conn ~now:(get_time pool);
548548+ (* Update error stats *)
549549+ Eio.Mutex.use_rw ~protect:true ep_pool.mutex (fun () ->
550550+ ep_pool.stats.errors <- ep_pool.stats.errors + 1);
500551501501- (* Update idle stats (connection taken from idle pool) *)
502502- Eio.Mutex.use_rw ~protect:true ep_pool.mutex (fun () ->
503503- ep_pool.stats.idle <- max 0 (ep_pool.stats.idle - 1));
552552+ raise e)));
504553505505- try
506506- let result = f conn.flow in
554554+ (* Signal cleanup when switch ends *)
555555+ Eio.Switch.on_release sw (fun () ->
556556+ Eio.Promise.resolve done_resolver ());
507557508508- (* Success - connection will be returned to pool by Eio.Pool *)
509509- (* Update idle stats (connection returned to idle pool) *)
510510- Eio.Mutex.use_rw ~protect:true ep_pool.mutex (fun () ->
511511- ep_pool.stats.idle <- ep_pool.stats.idle + 1);
558558+ (* Return the connection *)
559559+ Eio.Promise.await conn_promise
512560513513- result
514514- with e ->
515515- (* Error - close connection so it won't be reused *)
516516- Log.warn (fun m ->
517517- m "Error using connection to %a: %s" Endpoint.pp endpoint
518518- (Printexc.to_string e));
519519- close_internal pool conn;
561561+let connection ~sw t endpoint = connection_internal ~sw t endpoint
520562521521- (* Update error stats *)
522522- Eio.Mutex.use_rw ~protect:true ep_pool.mutex (fun () ->
523523- ep_pool.stats.errors <- ep_pool.stats.errors + 1);
563563+let with_connection t endpoint f =
564564+ Eio.Switch.run (fun sw -> f (connection ~sw t endpoint))
524565525525- raise e))
566566+let with_connection_exn t endpoint f =
567567+ try with_connection t endpoint f with Pool_error e -> raise (err e)
526568527569(** {1 Public API - Statistics} *)
528570
+59-15
lib/conpool.mli
···21212222(** {1 Core Types} *)
23232424-module Endpoint : module type of Endpoint
2424+module Endpoint = Endpoint
2525(** Network endpoint representation *)
26262727-module Tls_config : module type of Tls_config
2727+module Tls_config = Tls_config
2828(** TLS configuration for connection pools *)
29293030-module Config : module type of Config
3030+module Config = Config
3131(** Configuration for connection pools *)
32323333-module Stats : module type of Stats
3333+module Stats = Stats
3434(** Statistics for connection pool endpoints *)
35353636-module Cmd : module type of Cmd
3636+module Cmd = Cmd
3737(** Cmdliner terms for connection pool configuration *)
38383939(** {1 Errors} *)
···56565757 Most pool operations can raise this exception. Use {!pp_error} to get
5858 human-readable error messages. *)
5959+6060+type Eio.Exn.err += E of error
6161+(** Extension of Eio's error type for connection pool errors. *)
6262+6363+val err : error -> exn
6464+(** [err e] is [Eio.Exn.create (E e)].
6565+6666+ This converts a connection pool error to an Eio exception, allowing it to
6767+ be handled uniformly with other Eio I/O errors. *)
59686069val pp_error : error Fmt.t
6170(** Pretty-printer for error values. *)
62717272+(** {1 Connection Types} *)
7373+7474+type connection_ty = [Eio.Resource.close_ty | Eio.Flow.two_way_ty]
7575+(** The type tags for a pooled connection.
7676+ Connections support reading, writing, shutdown, and closing. *)
7777+7878+type connection = connection_ty Eio.Resource.t
7979+(** A connection resource from the pool. *)
8080+6381(** {1 Connection Pool} *)
64826583type t
···8510386104(** {1 Connection Usage} *)
871058888-val with_connection :
8989- t ->
9090- Endpoint.t ->
9191- ([ `Close | `Flow | `R | `Shutdown | `W ] Eio.Resource.t -> 'a) ->
9292- 'a
9393-(** Acquire connection, use it, automatically release back to pool.
106106+val connection : sw:Eio.Switch.t -> t -> Endpoint.t -> connection
107107+(** [connection ~sw pool endpoint] acquires a connection from the pool.
941089595- If idle connection available and healthy:
9696- - Reuse from pool (validates health first) Else:
109109+ The connection is automatically returned to the pool when [sw] finishes.
110110+ If the connection becomes unhealthy or an error occurs during use, it is
111111+ closed instead of being returned to the pool.
112112+113113+ If an idle connection is available and healthy:
114114+ - Reuse from pool (validates health first)
115115+116116+ Otherwise:
97117 - Create new connection (may block if endpoint at limit)
981189999- On success: connection returned to pool for reuse On error: connection
100100- closed, not returned to pool
119119+ Example:
120120+ {[
121121+ let endpoint = Conpool.Endpoint.make ~host:"example.com" ~port:443 in
122122+ Eio.Switch.run (fun sw ->
123123+ let conn = Conpool.connection ~sw pool endpoint in
124124+ Eio.Flow.copy_string "GET / HTTP/1.1\r\n\r\n" conn;
125125+ let buf = Eio.Buf_read.of_flow conn ~max_size:4096 in
126126+ Eio.Buf_read.take_all buf)
127127+ ]} *)
128128+129129+val with_connection : t -> Endpoint.t -> (connection -> 'a) -> 'a
130130+(** [with_connection pool endpoint fn] is a convenience wrapper around
131131+ {!val:connection}.
132132+133133+ Equivalent to:
134134+ {[
135135+ Eio.Switch.run (fun sw -> fn (connection ~sw pool endpoint))
136136+ ]}
101137102138 Example:
103139 {[
···108144 let buf = Eio.Buf_read.of_flow conn ~max_size:4096 in
109145 Eio.Buf_read.take_all buf)
110146 ]} *)
147147+148148+val with_connection_exn : t -> Endpoint.t -> (connection -> 'a) -> 'a
149149+(** [with_connection_exn pool endpoint fn] is like {!with_connection} but
150150+ converts {!Pool_error} exceptions to [Eio.Io] exceptions for better
151151+ integration with Eio error handling.
152152+153153+ This is useful when you want pool errors to be handled uniformly with other
154154+ Eio I/O errors. *)
111155112156(** {1 Statistics & Monitoring} *)
113157