Skip to content

Timed out waiting for buffer space in TcpNioConnection under moderate load #10671

@ouharri

Description

@ouharri

I am implementing a high‑throughput TCP gateway using Spring Boot 4, Spring Framework 7, and Spring Integration IP 7.0.0. The system is designed for a long‑term target of 10,000+ concurrent persistent TCP connections, each sending telemetry frames.

However, even under very moderate load (around 1,000–3,000 concurrent connections, each sending 1 message every ~10 seconds), the server begins dropping connections with repeated:

Timed out waiting for IO
Timed out waiting for buffer space

This happens even though:

  • I am using TcpNioConnectionFactory
  • Direct buffers are enabled
  • Connection factory has large OS buffer sizes
  • A custom CompositeExecutor (IO + assembler) is configured with large pools, no queues, abort/caller policies
  • A very fast deserializer is in place with zero copying

I read through the NIO note in the Spring docs:
https://docs.spring.io/spring-integration/reference/ip/note-nio.html

…and also past issues, especially this one:
#2222

But the symptoms remain identical even with the recommended setup.

I would like to confirm whether this is:

  1. a misconfiguration on my side, or
  2. a limitation/bug in Spring Integration NIO, or
  3. something requiring special tuning at OS/JDK level.

Any guidance would be hugely appreciated.


Load pattern

  • ~1,000–3,000 concurrent persistent TCP connections in current tests
  • Each connection sends about 1 small message every ~10 seconds

Logs

Below is an example of the recurring error :

ERROR [     nio-asm-19] TcpNioConnection : Read exception 0:0:0:0:0:0:0:1:61393:3333:da8e7d48-65d3-484a-9045-4f888f0f82a2 IOException:null:Timed out waiting for IO

ERROR [       nio-io-2]  TcpNioConnection : Exception on Read 0:0:0:0:0:0:0:1:61393:3333:da8e7d48-65d3-484a-9045-4f888f0f82a2 Timed out waiting for buffer space

java.io.IOException: Timed out waiting for buffer space
	at org.springframework.integration.ip.tcp.connection.TcpNioConnection$ChannelInputStream.write(TcpNioConnection.java:824) ~[spring-integration-ip-7.0.0.jar:7.0.0]
	at org.springframework.integration.ip.tcp.connection.TcpNioConnection.sendToPipe(TcpNioConnection.java:494) ~[spring-integration-ip-7.0.0.jar:7.0.0]
	at org.springframework.integration.ip.tcp.connection.TcpNioConnection.doRead(TcpNioConnection.java:477) ~[spring-integration-ip-7.0.0.jar:7.0.0]
	at org.springframework.integration.ip.tcp.connection.TcpNioConnection.readPacket(TcpNioConnection.java:534) ~[spring-integration-ip-7.0.0.jar:7.0.0]
	at org.springframework.integration.ip.tcp.connection.AbstractConnectionFactory.lambda$keyReadable$0(AbstractConnectionFactory.java:774) ~[spring-integration-ip-7.0.0.jar:7.0.0]
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090) ~[na:na]
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614) ~[na:na]
	at java.base/java.lang.Thread.run(Thread.java:1474) ~[na:na]

This eventually leads to connection closures.


Environment

  • Spring Boot: 4.0.0
  • Spring Framework: 7.x
  • Spring Integration IP: 7.0.0
  • JDK: Java 25
  • Platform: macOS (same behavior observed on Linux)

Using:

  • Tcp.inboundAdapter
  • TcpNioConnectionFactory
  • CompositeExecutor (IO + assembler pools)
  • VirtualThreadTaskExecutor for downstream message processing
  • Direct buffers enabled

Connection Factory Configuration

(only relevant pieces shown)

factory.setUsingDirectBuffers(true);
factory.setSoReceiveBufferSize(256 * 1024);
factory.setSoSendBufferSize(256 * 1024);
factory.setSoTcpNoDelay(true);
factory.setSoKeepAlive(true);
factory.setSingleUse(false);
factory.setReadDelay(10_000); // tested with different values
factory.setBacklog(65_535);
factory.setTaskExecutor(tcpNioCompositeExecutor())
factory.setNioHarvestInterval(5_000);

Custom CompositeExecutor

public CompositeExecutor tcpNioCompositeExecutor() {
    int cores = Runtime.getRuntime().availableProcessors();

    int ioCore = Math.max(4, cores);
    int ioMax  = Math.max(cores * 4, 16);

    int asmCore = Math.max(cores * 4, 16);
    int asmMax  = Math.max(cores * 16, 64);

    ThreadPoolTaskExecutor io = new ThreadPoolTaskExecutor();
    io.setThreadNamePrefix("nio-io-");
    io.setCorePoolSize(ioCore);
    io.setMaxPoolSize(ioMax);
    io.setQueueCapacity(0);
    io.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
    io.initialize();

    ThreadPoolTaskExecutor asm = new ThreadPoolTaskExecutor();
    asm.setThreadNamePrefix("nio-asm-");
    asm.setCorePoolSize(asmCore);
    asm.setMaxPoolSize(asmMax);
    asm.setQueueCapacity(0);
    asm.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    asm.initialize();

    return new CompositeExecutor(io, asm);
}

Please let me know if I am missing something in my configuration or understanding.
If not, a fix or clarification would be highly appreciated.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions