Skip to content

Commit 0577627

Browse files
committed
Expose Netty's CONNECT_TIMEOUT_MILLIS client channel option
Netty has a default connect timeout for client channels (currently 30s) which was only accessible by means of `bootstrap-transform`. Since it's a rather important option to get predictable connection behavior, expose it as `connect-timeout` in all relevant APIs.
1 parent eef3c66 commit 0577627

File tree

6 files changed

+59
-11
lines changed

6 files changed

+59
-11
lines changed

src/aleph/http.clj

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@
157157
| `insecure?` | if `true`, ignores the certificate for any `https://` domains
158158
| `response-buffer-size` | the amount of the response, in bytes, that is buffered before the request returns, defaults to `65536`. This does *not* represent the maximum size response that the client can handle (which is unbounded), and is only a means of maximizing performance.
159159
| `keep-alive?` | if `true`, attempts to reuse connections for multiple requests, defaults to `true`.
160+
| `connect-timeout` | timeout for a connection to be established, in milliseconds. Default determined by Netty, see `aleph.netty/default-connect-timeout`.
160161
| `idle-timeout` | when set, forces keep-alive connections to be closed after an idle time, in milliseconds.
161162
| `transport` | the transport to use, one of `:nio`, `:epoll`, `:kqueue` or `:io-uring` (defaults to `:nio`).
162163
| `raw-stream?` | if `true`, bodies of responses will not be buffered at all, and represented as Manifold streams of `io.netty.buffer.ByteBuf` objects rather than as an `InputStream`. This will minimize copying, but means that care must be taken with Netty's buffer reference counting. Only recommended for advanced users.
@@ -275,6 +276,7 @@
275276
| `pipeline-transform` | an optional function that takes an `io.netty.channel.ChannelPipeline` object, which represents a connection, and modifies it.
276277
| `max-frame-payload` | maximum allowable frame payload length, in bytes, defaults to `65536`.
277278
| `max-frame-size` | maximum aggregate message size, in bytes, defaults to `1048576`.
279+
| `connect-timeout` | timeout for a connection to be established, in milliseconds. Default determined by Netty, see `aleph.netty/default-connect-timeout`.
278280
| `bootstrap-transform` | an optional function that takes an `io.netty.bootstrap.Bootstrap` object and modifies it.
279281
| `transport` | the transport to use, one of `:nio`, `:epoll`, `:kqueue` or `:io-uring` (defaults to `:nio`).
280282
| `heartbeats` | optional configuration to send Ping frames to the server periodically (if the connection is idle), configuration keys are `:send-after-idle` (in milliseconds), `:payload` (optional, empty frame by default) and `:timeout` (optional, to close the connection if Pong is not received after specified timeout)."
@@ -342,7 +344,7 @@
342344
343345
Param key | Description
344346
-------------------- | -----------------------------------------------------------------------------------------------------------------------------------------------------------------
345-
`connection-timeout` | timeout in milliseconds for the connection to become established
347+
`connection-timeout` | timeout in milliseconds for the connection to become established, defaults to 60s. Note that this timeout will be ineffective if the pool's `connect-timeout` is lower.
346348
`follow-redirects?` | whether to follow redirects, defaults to `true`; see `aleph.http.client-middleware/handle-redirects`
347349
`middleware` | custom client middleware for the request
348350
`pool-timeout` | timeout in milliseconds for the pool to generate a connection
@@ -496,7 +498,7 @@
496498
| `follow-redirects?` | whether to follow redirects, defaults to `true`; see `aleph.http.client-middleware/handle-redirects`
497499
| `pool` | a custom connection pool
498500
| `pool-timeout` | timeout in milliseconds for the pool to generate a connection
499-
| `connection-timeout` | timeout in milliseconds for the connection to become established
501+
| `connection-timeout` | timeout in milliseconds for the connection to become established, defaults to 60s. Note that this timeout will be ineffective if the pool's `connect-timeout` is lower.
500502
| `request-timeout` | timeout in milliseconds for the arrival of a response over the established connection
501503
| `read-timeout` | timeout in milliseconds for the response to be completed
502504
| `response-executor` | optional `java.util.concurrent.Executor` that will handle the requests (defaults to a `flow/utilization-executor` of 256 `max-threads` and a `queue-length` of 0)")

src/aleph/http/client.clj

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -770,13 +770,15 @@
770770
log-activity
771771
http-versions
772772
force-h2c?
773-
on-closed]
773+
on-closed
774+
connect-timeout]
774775
:or {raw-stream? false
775776
bootstrap-transform identity
776777
pipeline-transform identity
777778
keep-alive? true
778779
ssl-endpoint-id-alg netty/default-ssl-endpoint-id-alg
779780
response-buffer-size 65536
781+
connect-timeout netty/default-connect-timeout
780782
epoll? false
781783
name-resolver :default
782784
log-activity :debug
@@ -818,7 +820,8 @@
818820
:remote-address remote-address
819821
:local-address local-address
820822
:transport (netty/determine-transport transport epoll?)
821-
:name-resolver name-resolver})]
823+
:name-resolver name-resolver
824+
:connect-timeout connect-timeout})]
822825

823826
(attach-on-close-handler ch-d on-closed)
824827

src/aleph/http/websocket/client.clj

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,8 @@
244244
max-frame-payload
245245
max-frame-size
246246
compression?
247-
heartbeats]
247+
heartbeats
248+
connect-timeout]
248249
:or {bootstrap-transform identity
249250
pipeline-transform identity
250251
raw-stream? false
@@ -254,7 +255,8 @@
254255
extensions? false
255256
max-frame-payload 65536
256257
max-frame-size 1048576
257-
compression? false}
258+
compression? false
259+
connect-timeout netty/default-connect-timeout}
258260
:as options}]
259261

260262
(when (and (true? (:compression? options))
@@ -312,5 +314,6 @@
312314
:bootstrap-transform bootstrap-transform
313315
:remote-address remote-address
314316
:local-address local-address
315-
:transport (netty/determine-transport transport epoll?)})
317+
:transport (netty/determine-transport transport epoll?)
318+
:connect-timeout connect-timeout})
316319
(fn [_] s))))

src/aleph/netty.clj

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
EpollEventLoopGroup
3939
EpollServerSocketChannel
4040
EpollSocketChannel)
41+
(io.netty.channel.embedded
42+
EmbeddedChannel)
4143
(io.netty.channel.group
4244
ChannelGroup
4345
DefaultChannelGroup)
@@ -1516,6 +1518,14 @@
15161518
(ssl-handler ch ssl-ctx))))
15171519
(pipeline-builder p))))
15181520

1521+
(def ^:const
1522+
default-connect-timeout
1523+
"Netty's default connect timeout."
1524+
;; This hack is necessary due to io.netty.channel.DefaultChannelConfig/DEFAULT_CONNECT_TIMEOUT
1525+
;; being private.
1526+
(with-open [c (EmbeddedChannel.)]
1527+
(.getConnectTimeoutMillis (.config c))))
1528+
15191529
(defn ^:no-doc create-client-chan
15201530
"Returns a deferred containing a new Channel.
15211531
@@ -1527,7 +1537,9 @@
15271537
^SocketAddress remote-address
15281538
^SocketAddress local-address
15291539
transport
1530-
name-resolver]
1540+
name-resolver
1541+
connect-timeout]
1542+
:or {connect-timeout default-connect-timeout}
15311543
:as opts}]
15321544
(ensure-transport-available! transport)
15331545

@@ -1546,6 +1558,7 @@
15461558
(instance? AddressResolverGroup name-resolver) name-resolver))
15471559
bootstrap (doto (Bootstrap.)
15481560
(.option ChannelOption/SO_REUSEADDR true)
1561+
(.option ChannelOption/CONNECT_TIMEOUT_MILLIS (int connect-timeout))
15491562
#_(.option ChannelOption/MAX_MESSAGES_PER_READ Integer/MAX_VALUE) ; option deprecated, removed in v5
15501563
(.group client-event-loop-group)
15511564
(.channel chan-class)

src/aleph/tcp.clj

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,26 @@
165165
| `ssl-endpoint-id-alg` | the name of the algorithm to use for SSL endpoint identification (see https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html#endpoint-identification-algorithms), defaults to \"HTTPS\" which is a reasonable default for non-HTTPS uses, too. Only used by SSL connections. Pass `nil` to disable endpoint identification.
166166
| `ssl?` | if true, the client attempts to establish a secure connection with the server.
167167
| `insecure?` | if true, the client will ignore the server's certificate.
168+
| `connect-timeout` | timeout for a connection to be established, in milliseconds. Default determined by Netty, see `aleph.netty/default-connect-timeout`.
168169
| `bootstrap-transform` | a function that takes an `io.netty.bootstrap.Bootstrap` object, which represents the client, and modifies it.
169170
| `pipeline-transform` | a function that takes an `io.netty.channel.ChannelPipeline` object, which represents a connection, and modifies it.
170171
| `raw-stream?` | if true, messages from the stream will be `io.netty.buffer.ByteBuf` objects rather than byte-arrays. This will minimize copying, but means that care must be taken with Netty's buffer reference counting. Only recommended for advanced users.
171172
| `transport` | the transport to use, one of `:nio`, `:epoll`, `:kqueue` or `:io-uring` (defaults to `:nio`)."
172-
[{:keys [host port remote-address local-address ssl-context ssl-endpoint-id-alg ssl? insecure? pipeline-transform bootstrap-transform epoll? transport]
173+
[{:keys [host
174+
port
175+
remote-address
176+
local-address
177+
ssl-context
178+
ssl-endpoint-id-alg
179+
ssl?
180+
insecure?
181+
connect-timeout
182+
pipeline-transform
183+
bootstrap-transform
184+
epoll?
185+
transport]
173186
:or {ssl-endpoint-id-alg netty/default-ssl-endpoint-id-alg
187+
connect-timeout netty/default-connect-timeout
174188
bootstrap-transform identity
175189
epoll? false}
176190
:as options}]
@@ -196,6 +210,7 @@
196210
:bootstrap-transform bootstrap-transform
197211
:remote-address remote-address
198212
:local-address local-address
199-
:transport (netty/determine-transport transport epoll?)})
213+
:transport (netty/determine-transport transport epoll?)
214+
:connect-timeout connect-timeout})
200215
(d/catch' #(d/error! s %)))
201216
s))

test/aleph/http_test.clj

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
ChannelHandlerContext
3232
ChannelOutboundHandlerAdapter
3333
ChannelPipeline
34-
ChannelPromise)
34+
ChannelPromise
35+
ConnectTimeoutException)
3536
(io.netty.handler.codec TooLongFrameException)
3637
(io.netty.handler.codec.compression
3738
CompressionOptions
@@ -633,6 +634,17 @@
633634
@(http/get "http://192.0.2.0" ;; "TEST-NET" in RFC 5737
634635
(merge (default-request-options) {:pool *pool* :connection-timeout 2}))))))
635636

637+
638+
(deftest test-pool-connect-timeout
639+
(binding [*connection-options* {:connect-timeout 2}]
640+
(with-handler basic-handler
641+
(is (thrown? ConnectTimeoutException
642+
(deref (http/get "http://192.0.2.0" ;; "TEST-NET" in RFC 5737
643+
(merge (default-request-options) {:pool *pool*
644+
:connection-timeout 500}))
645+
1e3
646+
:timeout))))))
647+
636648
(deftest test-request-timeout
637649
(with-handler basic-handler
638650
(is (thrown? TimeoutException

0 commit comments

Comments
 (0)