···3030 ?on_connection_closed
3131 ?on_connection_reused
3232 () =
3333+ (* Validate parameters *)
3434+ if max_connections_per_endpoint <= 0 then
3535+ invalid_arg (Printf.sprintf "max_connections_per_endpoint must be positive, got %d"
3636+ max_connections_per_endpoint);
3737+3838+ if max_idle_time <= 0.0 then
3939+ invalid_arg (Printf.sprintf "max_idle_time must be positive, got %.2f" max_idle_time);
4040+4141+ if max_connection_lifetime <= 0.0 then
4242+ invalid_arg (Printf.sprintf "max_connection_lifetime must be positive, got %.2f"
4343+ max_connection_lifetime);
4444+4545+ (match max_connection_uses with
4646+ | Some n when n <= 0 ->
4747+ invalid_arg (Printf.sprintf "max_connection_uses must be positive, got %d" n)
4848+ | _ -> ());
4949+5050+ if connect_timeout <= 0.0 then
5151+ invalid_arg (Printf.sprintf "connect_timeout must be positive, got %.2f" connect_timeout);
5252+5353+ if connect_retry_count < 0 then
5454+ invalid_arg (Printf.sprintf "connect_retry_count must be non-negative, got %d"
5555+ connect_retry_count);
5656+5757+ if connect_retry_delay <= 0.0 then
5858+ invalid_arg (Printf.sprintf "connect_retry_delay must be positive, got %.2f"
5959+ connect_retry_delay);
6060+3361 Log.debug (fun m ->
3462 m "Creating config: max_connections=%d, max_idle=%.1fs, max_lifetime=%.1fs"
3563 max_connections_per_endpoint max_idle_time max_connection_lifetime);
···1010module Stats = Stats
1111module Cmd = Cmd
12121313+(** {1 Error Types} *)
1414+1515+type error =
1616+ | Dns_resolution_failed of { hostname : string }
1717+ | Connection_failed of { endpoint : Endpoint.t; attempts : int; last_error : string }
1818+ | Connection_timeout of { endpoint : Endpoint.t; timeout : float }
1919+ | Invalid_config of string
2020+ | Invalid_endpoint of string
2121+2222+exception Pool_error of error
2323+2424+let pp_error ppf = function
2525+ | Dns_resolution_failed { hostname } ->
2626+ Fmt.pf ppf "DNS resolution failed for hostname: %s" hostname
2727+ | Connection_failed { endpoint; attempts; last_error } ->
2828+ Fmt.pf ppf "Failed to connect to %a after %d attempts: %s"
2929+ Endpoint.pp endpoint attempts last_error
3030+ | Connection_timeout { endpoint; timeout } ->
3131+ Fmt.pf ppf "Connection timeout to %a after %.2fs"
3232+ Endpoint.pp endpoint timeout
3333+ | Invalid_config msg ->
3434+ Fmt.pf ppf "Invalid configuration: %s" msg
3535+ | Invalid_endpoint msg ->
3636+ Fmt.pf ppf "Invalid endpoint: %s" msg
3737+1338type endp_stats = {
1439 mutable active : int;
1540 mutable idle : int;
···77102 addr
78103 | [] ->
79104 Log.err (fun m -> m "Failed to resolve hostname: %s" (Endpoint.host endpoint));
8080- failwith (Printf.sprintf "Failed to resolve hostname: %s" (Endpoint.host endpoint))
105105+ raise (Pool_error (Dns_resolution_failed { hostname = Endpoint.host endpoint }))
8110682107(** {1 Connection Creation with Retry} *)
831088484-let rec create_connection_with_retry (pool : ('clock, 'net) internal) endpoint attempt =
109109+let rec create_connection_with_retry (pool : ('clock, 'net) internal) endpoint attempt last_error =
85110 let retry_count = Config.connect_retry_count pool.config in
86111 if attempt > retry_count then begin
87112 Log.err (fun m -> m "Failed to connect to %a after %d attempts"
88113 Endpoint.pp endpoint retry_count);
8989- failwith (Printf.sprintf "Failed to connect to %s:%d after %d attempts"
9090- (Endpoint.host endpoint) (Endpoint.port endpoint) retry_count)
114114+ raise (Pool_error (Connection_failed { endpoint; attempts = retry_count; last_error }))
91115 end;
9211693117 Log.debug (fun m -> m "Connecting to %a (attempt %d/%d)"
···130154 last_used = now;
131155 use_count = 0;
132156 endpoint;
157157+ mutex = Eio.Mutex.create ();
133158 }
134159135160 with
136136- | Eio.Time.Timeout ->
161161+ | Eio.Time.Timeout as e ->
137162 Log.warn (fun m -> m "Connection timeout to %a (attempt %d)" Endpoint.pp endpoint attempt);
138138- (* Exponential backoff *)
139139- let delay = Config.connect_retry_delay pool.config *. (2.0 ** float_of_int (attempt - 1)) in
140140- Eio.Time.sleep pool.clock delay;
141141- create_connection_with_retry pool endpoint (attempt + 1)
163163+ let error_msg = Printexc.to_string e in
164164+ if attempt >= Config.connect_retry_count pool.config then
165165+ (* Last attempt - convert to our error type *)
166166+ match Config.connect_timeout pool.config with
167167+ | Some timeout ->
168168+ raise (Pool_error (Connection_timeout { endpoint; timeout }))
169169+ | None ->
170170+ raise (Pool_error (Connection_failed { endpoint; attempts = attempt; last_error = error_msg }))
171171+ else begin
172172+ (* Retry with exponential backoff *)
173173+ let delay = Config.connect_retry_delay pool.config *. (2.0 ** float_of_int (attempt - 1)) in
174174+ Eio.Time.sleep pool.clock delay;
175175+ create_connection_with_retry pool endpoint (attempt + 1) error_msg
176176+ end
142177 | e ->
143178 (* Other errors - retry with backoff *)
179179+ let error_msg = Printexc.to_string e in
144180 Log.warn (fun m -> m "Connection attempt %d to %a failed: %s"
145145- attempt Endpoint.pp endpoint (Printexc.to_string e));
181181+ attempt Endpoint.pp endpoint error_msg);
146182 if attempt < Config.connect_retry_count pool.config then (
147183 let delay = Config.connect_retry_delay pool.config *. (2.0 ** float_of_int (attempt - 1)) in
148184 Eio.Time.sleep pool.clock delay;
149149- create_connection_with_retry pool endpoint (attempt + 1)
185185+ create_connection_with_retry pool endpoint (attempt + 1) error_msg
150186 ) else
151151- raise e
187187+ raise (Pool_error (Connection_failed { endpoint; attempts = attempt; last_error = error_msg }))
152188153189let create_connection (pool : ('clock, 'net) internal) endpoint =
154154- create_connection_with_retry pool endpoint 1
190190+ create_connection_with_retry pool endpoint 1 "No attempts made"
155191156192(** {1 Connection Validation} *)
157193···406442 Endpoint.pp endpoint (Connection.use_count conn));
407443408444 (* Update last used time and use count *)
409409- conn.last_used <- get_time pool;
410410- conn.use_count <- conn.use_count + 1;
445445+ Connection.update_usage conn ~now:(get_time pool);
411446412447 (* Update idle stats (connection taken from idle pool) *)
413448 Eio.Mutex.use_rw ~protect:true ep_pool.mutex (fun () ->
+27
lib/conpool.mli
···3232(** Cmdliner terms for connection pool configuration *)
3333module Cmd : module type of Cmd
34343535+(** {1 Errors} *)
3636+3737+type error =
3838+ | Dns_resolution_failed of { hostname : string }
3939+ (** DNS resolution failed for the given hostname *)
4040+4141+ | Connection_failed of { endpoint : Endpoint.t; attempts : int; last_error : string }
4242+ (** Failed to establish connection after all retry attempts *)
4343+4444+ | Connection_timeout of { endpoint : Endpoint.t; timeout : float }
4545+ (** Connection attempt timed out *)
4646+4747+ | Invalid_config of string
4848+ (** Invalid configuration parameter *)
4949+5050+ | Invalid_endpoint of string
5151+ (** Invalid endpoint specification *)
5252+5353+exception Pool_error of error
5454+(** Exception raised by pool operations.
5555+5656+ Most pool operations can raise this exception. Use {!pp_error} to get
5757+ human-readable error messages. *)
5858+5959+val pp_error : error Fmt.t
6060+(** Pretty-printer for error values. *)
6161+3562(** {1 Connection Pool} *)
36633764type t
+8
lib/endpoint.ml
···99}
10101111let make ~host ~port =
1212+ (* Validate port range *)
1313+ if port < 1 || port > 65535 then
1414+ invalid_arg (Printf.sprintf "Invalid port number: %d (must be 1-65535)" port);
1515+1616+ (* Validate hostname is not empty *)
1717+ if String.trim host = "" then
1818+ invalid_arg "Hostname cannot be empty";
1919+1220 Log.debug (fun m -> m "Creating endpoint: %s:%d" host port);
1321 { host; port }
1422