INT-3433 Delay Failed IOs

JIRA: https://jira.spring.io/browse/INT-3433`

If a read fails due to insufficient threads, delay the read
for (default 100ms) - do not re-enable OP_READ until that
time has elapsed. Avoids spinning the CPU.

INT-3433 More Polishing

If the assembler couldn't execute a new assembler after assembling
the current message (when it detected there is more data), in the
finally block it would "continue" only if the socket was still
open.

The test case closes the socket after sending 4 messages so when
this condition occurred, the assembler failed to continue and
data was left in the buffer.

Remove the `isOpen()` check in the finally block and always continue
if there's not another assembler running and there's data available.

INT-3433 More Polishing

We can still get starvation if the selector is in a long
wait (in select()) when a read is delayed.

Whenever a read is delayed, wake the selector so its next
select will use the readDelay timeout.

INT-3433 Reference Docs

Also remove Thread.yield().

Revert redundant boolean return from `TcpNioConnection#checkForAssembler()`
This commit is contained in:
Gary Russell
2014-06-14 11:24:38 -04:00
committed by Artem Bilan
parent 69e20fc7db
commit 3c063a265b
6 changed files with 243 additions and 84 deletions

View File

@@ -1001,6 +1001,9 @@
the <classname>CallerRunsPolicy</classname> (<code>CALLER_RUNS</code> when
using the <code>&lt;task/&gt;</code> namespace) and the queue capacity is small.
</para>
<para>
The following does not apply if you are not using a fixed thread pool.
</para>
<para>
With NIO connections there are 3 distinct task types; the IO Selector processing
is performed on one dedicated thread - detecting events, accepting new connections,
@@ -1028,30 +1031,34 @@
</para>
<para>
We must avoid the selector (or reader) threads performing the
assembly task to avoid this deadlock.
assembly task to avoid this deadlock. It is desirable to use seperate
pools for the IO and assembly operations.
</para>
<para>
Two classes are provided by the framework to avoid this problem. The
<classname>CompositeExecutor</classname> allows the configuration
The framework providers a
<classname>CompositeExecutor</classname>, which allows the configuration
of two distinct executors; one for performing IO operations, and
one for message assembly. The <classname>CallerBlocksPolicy</classname>
(which should be configured for the first task executors) will suspend
the IO operation until an assembler thread is available (or a timeout
occurs). In this environment, an IO thread can never
one for message assembly. In this environment, an IO thread can never
become an assembler thread, and the deadlock cannot occur.
Example configuration of the composite executor is shown below. The
<code>maxPoolSize</code> (or <code>queueCapacity</code>)
of the assembler executor should be slightly
larger than those on the IO executor.
</para>
<para>
In addition, the task executors should be configured to use a
<classname>AbortPolicy</classname> (ABORT when using <code>&lt;task&gt;</code>).
When an IO cannot be completed, it is deferred for a short time and
retried continually until it can be completed and an assembler
allocated.
</para>
<para>
Example configuration of the composite executor is shown below.
</para>
<programlisting language="java"><![CDATA[@Bean
private CompositeExecutor compositeExecutor() {
ThreadPoolTaskExecutor ioExec = new ThreadPoolTaskExecutor();
ioExec.setCorePoolSize(4);
ioExec.setMaxPoolSize(8);
ioExec.setMaxPoolSize(10);
ioExec.setQueueCapacity(0);
ioExec.setThreadNamePrefix("io-");
ioExec.setRejectedExecutionHandler(new CallerRunsPolicy());
ioExec.setRejectedExecutionHandler(new AbortPolicy());
ioExec.initialize();
ThreadPoolTaskExecutor assemblerExec = new ThreadPoolTaskExecutor();
assemblerExec.setCorePoolSize(4);
@@ -1062,6 +1069,14 @@ private CompositeExecutor compositeExecutor() {
assemblerExec.initialize();
return new CompositeExecutor(ioExec, assemblerExec);
}]]></programlisting>
<programlisting language="xml"><![CDATA[<bean id="myTaskExecutor" class="org.springframework.integration.util.CompositeExecutor">
<constructor-arg ref="io"/>
<constructor-arg ref="assembler"/>
</bean>
<task:executor id="io" pool-size="4-10" queue-capacity="0" rejection-policy="ABORT" />
<task:executor id="assembler" pool-size="4-10" queue-capacity="0" rejection-policy="ABORT" />]]></programlisting>
<programlisting language="xml"><![CDATA[<bean id="myTaskExecutor" class="org.springframework.integration.util.CompositeExecutor">
<constructor-arg>
<bean class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
@@ -1070,9 +1085,7 @@ private CompositeExecutor compositeExecutor() {
<property name="maxPoolSize" value="8" />
<property name="queueCapacity" value="0" />
<property name="rejectedExecutionHandler">
<bean class="org.springframework.integration.util.CallerBlocksPolicy">
<constructor-arg value="10000" />
</bean>
<bean class="java.util.concurrent.ThreadPoolExecutor.AbortPolicy" />
</property>
</bean>
</constructor-arg>