Skip to content

Commit 475033b

Browse files
mp911deavinash-anand
authored andcommitted
Add config options for TCP nodelay and keepalive
[resolves pgjdbc#296]
1 parent efa2b04 commit 475033b

File tree

6 files changed

+155
-7
lines changed

6 files changed

+155
-7
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ Mono<Connection> connectionMono = Mono.from(connectionFactory.create());
9191
| `sslCert` | Path to SSL certificate for TLS authentication in PEM format. _(Optional)_
9292
| `sslPassword` | Key password to decrypt SSL key. _(Optional)_
9393
| `sslHostnameVerifier` | `javax.net.ssl.HostnameVerifier` implementation. _(Optional)_
94+
| `tcpNoDelay` | Enabled/disable TCP NoDelay. Disabled by default. _(Optional)_
95+
| `tcpKeepAlive` | Enabled/disable TCP KeepAlive. Disabled by default _(Optional)_
9496

9597
**Programmatic Configuration**
9698

src/main/java/io/r2dbc/postgresql/PostgresqlConnectionConfiguration.java

+57-5
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
import javax.net.ssl.HostnameVerifier;
3636
import java.io.File;
37+
import java.net.Socket;
3738
import java.time.Duration;
3839
import java.util.ArrayList;
3940
import java.util.Collections;
@@ -90,15 +91,19 @@ public final class PostgresqlConnectionConfiguration {
9091

9192
private final SSLConfig sslConfig;
9293

94+
private final boolean tcpKeepAlive;
95+
96+
private final boolean tcpNoDelay;
97+
9398
private final int preparedStatementCacheQueries;
9499

95-
private PostgresqlConnectionConfiguration(String applicationName, boolean autodetectExtensions,
96-
@Nullable Duration connectTimeout, @Nullable String database, LogLevel errorResponseLogLevel, List<Extension> extensions,
100+
private PostgresqlConnectionConfiguration(String applicationName, boolean autodetectExtensions, @Nullable Duration connectTimeout, @Nullable String database, LogLevel errorResponseLogLevel,
101+
List<Extension> extensions,
97102
ToIntFunction<String> fetchSize, boolean forceBinary,
98103
LogLevel noticeLogLevel, @Nullable String host,
99104
@Nullable Map<String, String> options, @Nullable CharSequence password, int port, @Nullable String schema,
100-
@Nullable String socket, String username,
101-
SSLConfig sslConfig, int preparedStatementCacheQueries) {
105+
@Nullable String socket, boolean tcpKeepAlive, boolean tcpNoDelay, String username, SSLConfig sslConfig,
106+
int preparedStatementCacheQueries) {
102107
this.applicationName = Assert.requireNonNull(applicationName, "applicationName must not be null");
103108
this.autodetectExtensions = autodetectExtensions;
104109
this.connectTimeout = connectTimeout;
@@ -120,6 +125,8 @@ private PostgresqlConnectionConfiguration(String applicationName, boolean autode
120125
this.socket = socket;
121126
this.username = Assert.requireNonNull(username, "username must not be null");
122127
this.sslConfig = sslConfig;
128+
this.tcpKeepAlive = tcpKeepAlive;
129+
this.tcpNoDelay = tcpNoDelay;
123130
this.preparedStatementCacheQueries = preparedStatementCacheQueries;
124131
}
125132

@@ -148,6 +155,8 @@ public String toString() {
148155
", options='" + this.options + '\'' +
149156
", password='" + obfuscate(this.password != null ? this.password.length() : 0) + '\'' +
150157
", port=" + this.port +
158+
", tcpKeepAlive=" + this.tcpKeepAlive +
159+
", tcpNoDelay=" + this.tcpNoDelay +
151160
", username='" + this.username + '\'' +
152161
'}';
153162
}
@@ -235,6 +244,14 @@ boolean isForceBinary() {
235244
return this.forceBinary;
236245
}
237246

247+
boolean isTcpKeepAlive() {
248+
return this.tcpKeepAlive;
249+
}
250+
251+
boolean isTcpNoDelay() {
252+
return this.tcpNoDelay;
253+
}
254+
238255
boolean isUseSocket() {
239256
return getSocket() != null;
240257
}
@@ -249,6 +266,8 @@ ConnectionSettings getConnectionSettings() {
249266
.errorResponseLogLevel(this.errorResponseLogLevel)
250267
.noticeLogLevel(this.noticeLogLevel)
251268
.sslConfig(getSslConfig())
269+
.tcpKeepAlive(isTcpKeepAlive())
270+
.tcpNoDelay(isTcpNoDelay())
252271
.build();
253272
}
254273

@@ -328,6 +347,10 @@ public static final class Builder {
328347

329348
private Function<SslContextBuilder, SslContextBuilder> sslContextBuilderCustomizer = Function.identity();
330349

350+
private boolean tcpKeepAlive;
351+
352+
private boolean tcpNoDelay;
353+
331354
@Nullable
332355
private String username;
333356

@@ -379,7 +402,8 @@ public PostgresqlConnectionConfiguration build() {
379402
}
380403

381404
return new PostgresqlConnectionConfiguration(this.applicationName, this.autodetectExtensions, this.connectTimeout, this.database, this.errorResponseLogLevel, this.extensions,
382-
this.fetchSize, this.forceBinary, this.noticeLogLevel, this.host, this.options, this.password, this.port, this.schema, this.socket, this.username, this.createSslConfig(),
405+
this.fetchSize, this.forceBinary, this.noticeLogLevel, this.host, this.options, this.password, this.port, this.schema, this.socket, this.tcpKeepAlive, this.tcpNoDelay, this.username
406+
, this.createSslConfig(),
383407
this.preparedStatementCacheQueries);
384408
}
385409

@@ -660,6 +684,32 @@ public Builder sslRootCert(String sslRootCert) {
660684
return this;
661685
}
662686

687+
/**
688+
* Configure TCP KeepAlive.
689+
*
690+
* @param enabled whether to enable TCP KeepAlive
691+
* @return this {@link Builder}
692+
* @see Socket#setKeepAlive(boolean)
693+
* @since 0.8.4
694+
*/
695+
public Builder tcpKeepAlive(boolean enabled) {
696+
this.tcpKeepAlive = enabled;
697+
return this;
698+
}
699+
700+
/**
701+
* Configure TCP NoDelay.
702+
*
703+
* @param enabled whether to enable TCP NoDelay
704+
* @return this {@link Builder}
705+
* @see Socket#setTcpNoDelay(boolean)
706+
* @since 0.8.4
707+
*/
708+
public Builder tcpNoDelay(boolean enabled) {
709+
this.tcpNoDelay = enabled;
710+
return this;
711+
}
712+
663713
/**
664714
* Configure the username.
665715
*
@@ -710,6 +760,8 @@ public String toString() {
710760
", sslCert='" + this.sslCert + '\'' +
711761
", sslKey='" + this.sslKey + '\'' +
712762
", sslHostnameVerifier='" + this.sslHostnameVerifier + '\'' +
763+
", tcpKeepAlive='" + this.tcpKeepAlive + '\'' +
764+
", tcpNoDelay='" + this.tcpNoDelay + '\'' +
713765
", preparedStatementCacheQueries='" + this.preparedStatementCacheQueries + '\'' +
714766
'}';
715767
}

src/main/java/io/r2dbc/postgresql/PostgresqlConnectionFactoryProvider.java

+16
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,20 @@ public final class PostgresqlConnectionFactoryProvider implements ConnectionFact
136136
*/
137137
public static final Option<String> SSL_ROOT_CERT = Option.valueOf("sslRootCert");
138138

139+
/**
140+
* Enable TCP KeepAlive.
141+
*
142+
* @since 0.8.4
143+
*/
144+
public static final Option<Boolean> TCP_KEEPALIVE = Option.valueOf("tcpKeepAlive");
145+
146+
/**
147+
* Enable TCP NoDelay.
148+
*
149+
* @since 0.8.4
150+
*/
151+
public static final Option<Boolean> TCP_NODELAY = Option.valueOf("tcpNoDelay");
152+
139153
/**
140154
* Determine the number of queries that are cached in each connection.
141155
* The default is {@code -1}, meaning there's no limit. The value of {@code 0} disables the cache. Any other value specifies the cache size.
@@ -208,6 +222,8 @@ private static PostgresqlConnectionConfiguration.Builder fromConnectionFactoryOp
208222
builder.host(options.getRequiredValue(HOST));
209223
setupSsl(builder, mapper);
210224
});
225+
mapper.from(TCP_KEEPALIVE).map(OptionMapper::toBoolean).to(builder::tcpKeepAlive);
226+
mapper.from(TCP_NODELAY).map(OptionMapper::toBoolean).to(builder::tcpNoDelay);
211227
builder.username(options.getRequiredValue(USER));
212228

213229
return builder;

src/main/java/io/r2dbc/postgresql/client/ConnectionSettings.java

+47-2
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616

1717
package io.r2dbc.postgresql.client;
1818

19+
import io.r2dbc.postgresql.PostgresqlConnectionConfiguration;
1920
import io.r2dbc.postgresql.message.backend.ErrorResponse;
2021
import io.r2dbc.postgresql.message.backend.NoticeResponse;
2122
import io.r2dbc.postgresql.util.Assert;
2223
import io.r2dbc.postgresql.util.LogLevel;
2324
import reactor.netty.resources.ConnectionProvider;
2425
import reactor.util.annotation.Nullable;
2526

27+
import java.net.Socket;
2628
import java.time.Duration;
2729
import java.util.function.Consumer;
2830

@@ -40,14 +42,21 @@ public final class ConnectionSettings {
4042

4143
private final SSLConfig sslConfig;
4244

45+
private final boolean tcpKeepAlive;
46+
47+
private final boolean tcpNoDelay;
48+
4349
private final LogLevel errorResponseLogLevel;
4450

4551
private final LogLevel noticeLogLevel;
4652

47-
ConnectionSettings(@Nullable Duration connectTimeout, ConnectionProvider connectionProvider, SSLConfig sslConfig, LogLevel errorResponseLogLevel, LogLevel noticeLogLevel) {
53+
ConnectionSettings(@Nullable Duration connectTimeout, ConnectionProvider connectionProvider, SSLConfig sslConfig, boolean tcpKeepAlive, boolean tcpNoDelay, LogLevel errorResponseLogLevel,
54+
LogLevel noticeLogLevel) {
4855
this.connectTimeout = connectTimeout;
4956
this.connectionProvider = connectionProvider;
5057
this.sslConfig = sslConfig;
58+
this.tcpKeepAlive = tcpKeepAlive;
59+
this.tcpNoDelay = tcpNoDelay;
5160
this.errorResponseLogLevel = errorResponseLogLevel;
5261
this.noticeLogLevel = noticeLogLevel;
5362
}
@@ -103,6 +112,14 @@ SSLConfig getSslConfig() {
103112
return this.sslConfig;
104113
}
105114

115+
boolean isTcpKeepAlive() {
116+
return this.tcpKeepAlive;
117+
}
118+
119+
boolean isTcpNoDelay() {
120+
return this.tcpNoDelay;
121+
}
122+
106123
LogLevel getErrorResponseLogLevel() {
107124
return this.errorResponseLogLevel;
108125
}
@@ -128,6 +145,10 @@ public static final class Builder {
128145

129146
private SSLConfig sslConfig = new SSLConfig(SSLMode.DISABLE, null, null);
130147

148+
private boolean tcpKeepAlive;
149+
150+
private boolean tcpNoDelay;
151+
131152
private Builder() {
132153
}
133154

@@ -137,7 +158,7 @@ private Builder() {
137158
* @return a configured {@link ConnectionSettings}
138159
*/
139160
public ConnectionSettings build() {
140-
return new ConnectionSettings(this.connectTimeout, this.connectionProvider, this.sslConfig, this.errorResponseLogLevel, this.noticeLogLevel);
161+
return new ConnectionSettings(this.connectTimeout, this.connectionProvider, this.sslConfig, this.tcpKeepAlive, this.tcpNoDelay, this.errorResponseLogLevel, this.noticeLogLevel);
141162
}
142163

143164
/**
@@ -196,6 +217,30 @@ public Builder sslConfig(SSLConfig sslConfig) {
196217
return this;
197218
}
198219

220+
/**
221+
* Configure TCP KeepAlive.
222+
*
223+
* @param enabled whether to enable TCP KeepAlive
224+
* @return this {@link PostgresqlConnectionConfiguration.Builder}
225+
* @see Socket#setKeepAlive(boolean)
226+
*/
227+
public Builder tcpKeepAlive(boolean enabled) {
228+
this.tcpKeepAlive = enabled;
229+
return this;
230+
}
231+
232+
/**
233+
* Configure TCP NoDelay.
234+
*
235+
* @param enabled whether to enable TCP NoDelay
236+
* @return this {@link PostgresqlConnectionConfiguration.Builder}
237+
* @see Socket#setTcpNoDelay(boolean)
238+
*/
239+
public Builder tcpNoDelay(boolean enabled) {
240+
this.tcpNoDelay = enabled;
241+
return this;
242+
}
243+
199244
}
200245

201246
}

src/main/java/io/r2dbc/postgresql/client/ReactorNettyClient.java

+3
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,9 @@ public static Mono<ReactorNettyClient> connect(SocketAddress socketAddress, Conn
363363

364364
if (!(socketAddress instanceof InetSocketAddress)) {
365365
tcpClient = tcpClient.runOn(new SocketLoopResources(), true);
366+
} else {
367+
tcpClient = tcpClient.option(ChannelOption.SO_KEEPALIVE, settings.isTcpKeepAlive());
368+
tcpClient = tcpClient.option(ChannelOption.TCP_NODELAY, settings.isTcpNoDelay());
366369
}
367370

368371
if (settings.hasConnectionTimeout()) {

src/test/java/io/r2dbc/postgresql/PostgresqlConnectionFactoryProviderUnitTests.java

+30
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.SOCKET;
3838
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.SSL_CONTEXT_BUILDER_CUSTOMIZER;
3939
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.SSL_MODE;
40+
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.TCP_KEEPALIVE;
41+
import static io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider.TCP_NODELAY;
4042
import static io.r2dbc.spi.ConnectionFactoryOptions.DRIVER;
4143
import static io.r2dbc.spi.ConnectionFactoryOptions.HOST;
4244
import static io.r2dbc.spi.ConnectionFactoryOptions.PASSWORD;
@@ -326,6 +328,34 @@ void shouldApplySslContextBuilderCustomizer() {
326328
assertThatIllegalStateException().isThrownBy(() -> factory.getConfiguration().getSslConfig().getSslProvider().get()).withMessageContaining("Works!");
327329
}
328330

331+
@Test
332+
void shouldConfigureTcpKeepAlive() {
333+
334+
PostgresqlConnectionFactory factory = this.provider.create(builder()
335+
.option(DRIVER, POSTGRESQL_DRIVER)
336+
.option(HOST, "test-host")
337+
.option(PASSWORD, "test-password")
338+
.option(USER, "test-user")
339+
.option(TCP_KEEPALIVE, true)
340+
.build());
341+
342+
assertThat(factory.getConfiguration().isTcpKeepAlive()).isTrue();
343+
}
344+
345+
@Test
346+
void shouldConfigureTcpNoDelay() {
347+
348+
PostgresqlConnectionFactory factory = this.provider.create(builder()
349+
.option(DRIVER, POSTGRESQL_DRIVER)
350+
.option(HOST, "test-host")
351+
.option(PASSWORD, "test-password")
352+
.option(USER, "test-user")
353+
.option(TCP_NODELAY, true)
354+
.build());
355+
356+
assertThat(factory.getConfiguration().isTcpNoDelay()).isTrue();
357+
}
358+
329359
@Test
330360
void shouldConnectUsingUnixDomainSocket() {
331361

0 commit comments

Comments
 (0)