TCP/TLS connection pooling for Eio

Eliminate Obj.magic with type-safe equivalents

Replace unsafe Obj.magic casts with proper type-safe alternatives:

- conpool: Make protocol parameter required, add create_basic for simple
pools. The previous optional protocol with Obj.magic default was
fundamentally unsound as OCaml cannot have optional parameters that
change return types.

- publicsuffix: Add explicit id field to trie_node instead of using
Obj.magic to cast nodes to int for hashtable keys.

- yamlt: Add init_unknown_builder helper that properly handles GADT
refinement, returning () for Unknown_skip/Unknown_error cases where
builder=unit.

- jmap_brr: Use Jsont_brr.encode/decode Jsont.json instead of unsafe
casts between Jv.t and Jsont.json.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+43 -16
+4 -4
README.md
··· 20 20 21 21 let run env = 22 22 Switch.run (fun sw -> 23 - (* Create a connection pool *) 24 - let pool = Conpool.create 23 + (* Create a basic connection pool (no protocol state) *) 24 + let pool = Conpool.create_basic 25 25 ~sw 26 26 ~net:(Eio.Stdenv.net env) 27 27 ~clock:(Eio.Stdenv.clock env) ··· 49 49 let tls_config = Tls.Config.client ~authenticator:(Ca_certs.authenticator ()) () in 50 50 51 51 (* Create pool with TLS *) 52 - let pool = Conpool.create 52 + let pool = Conpool.create_basic 53 53 ~sw 54 54 ~net:(Eio.Stdenv.net env) 55 55 ~clock:(Eio.Stdenv.clock env) ··· 76 76 () 77 77 in 78 78 79 - let pool = Conpool.create ~sw ~net ~clock ~config () 79 + let pool = Conpool.create_basic ~sw ~net ~clock ~config () 80 80 ``` 81 81 82 82 Monitor pool statistics:
+4 -5
lib/conpool.ml
··· 566 566 (** {1 Public API} *) 567 567 568 568 let create ~sw ~(net : 'net Eio.Net.t) ~(clock : 'clock Eio.Time.clock) 569 - ?tls ?(config = Config.default) ?protocol () = 570 - let protocol = match protocol with 571 - | Some p -> p 572 - | None -> Obj.magic default_protocol (* Safe: unit is compatible with any 'state *) 573 - in 569 + ?tls ?(config = Config.default) ~protocol () = 574 570 575 571 Log.info (fun m -> 576 572 m "Creating connection pool (max_per_endpoint=%d)" ··· 599 595 Hashtbl.clear pool.endpoints)); 600 596 601 597 Pool pool 598 + 599 + let create_basic ~sw ~net ~clock ?tls ?config () = 600 + create ~sw ~net ~clock ?tls ?config ~protocol:default_protocol () 602 601 603 602 let connection ~sw (Pool pool) endpoint = 604 603 Log.debug (fun m -> m "Acquiring connection to %a" Endpoint.pp endpoint);
+34 -6
lib/conpool.mli
··· 13 13 14 14 For simple exclusive-access protocols (HTTP/1.x, Redis, etc.): 15 15 {[ 16 - let pool = Conpool.create ~sw ~net ~clock ~tls () in 16 + let pool = Conpool.create_basic ~sw ~net ~clock ~tls () in 17 17 Eio.Switch.run (fun conn_sw -> 18 18 let conn = Conpool.connection ~sw:conn_sw pool endpoint in 19 19 (* Use conn.flow for I/O *) ··· 105 105 106 106 (** {2 Pool Creation} *) 107 107 108 + val 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 + 108 113 val create : 109 114 sw:Eio.Switch.t -> 110 115 net:'net Eio.Net.t -> 111 116 clock:'clock Eio.Time.clock -> 112 117 ?tls:Tls.Config.client -> 113 118 ?config:Config.t -> 114 - ?protocol:'state Config.protocol_config -> 119 + protocol:'state Config.protocol_config -> 115 120 unit -> 116 121 'state t 117 - (** Create a connection pool. 122 + (** Create a connection pool with a protocol handler. 118 123 119 124 @param sw Switch for resource management 120 125 @param net Network interface for creating connections 121 126 @param clock Clock for timeouts 122 127 @param tls Optional TLS client configuration 123 128 @param config Pool configuration (uses {!Config.default} if not provided) 124 - @param protocol Protocol handler for state management. If not provided, 125 - creates a [unit t] pool with exclusive access mode (one user per connection). 129 + @param protocol Protocol handler for state management 126 130 127 131 Examples: 128 132 129 133 Simple pool for HTTP/1.x (exclusive access, no state): 130 134 {[ 131 - let pool = Conpool.create ~sw ~net ~clock ~tls () 135 + let pool = Conpool.create ~sw ~net ~clock ~tls 136 + ~protocol:Conpool.default_protocol () 132 137 ]} 133 138 134 139 HTTP/2 pool (shared access with H2 state): 135 140 {[ 136 141 let pool = Conpool.create ~sw ~net ~clock ~tls ~protocol:h2_handler () 142 + ]} *) 143 + 144 + val 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 () 137 165 ]} *) 138 166 139 167 (** {2 Connection Acquisition} *)
+1 -1
test/stress_test.ml
··· 267 267 () 268 268 in 269 269 270 - let pool = Conpool.create ~sw ~net ~clock ~config:pool_config () in 270 + let pool = Conpool.create_basic ~sw ~net ~clock ~config:pool_config () in 271 271 272 272 (* Record start time *) 273 273 let start_time = Eio.Time.now clock in