I solved the issue...kind of...by creating my own custom TcpConnector, TcpMessageDispatcher, and TcpMessageDispatcherFactory classes, as per Ian Gil Ragudo's suggestion
And here is how I define my connector:
<spring:bean id="dispatcherFactory" class="com.mycompany.mule.tcp.MyTcpMessageDispatcherFactory"/>
<spring:bean id="protocol" class="org.mule.transport.tcp.protocols.DirectProtocol"/>
<custom-connector name="myTcpConn" class="com.mycompany.mule.tcp.MyTcpConnector" >
<spring:property name="dispatcherFactory" ref="dispatcherFactory" />
<spring:property name="tcpProtocol" ref="protocol"/>
<spring:property name="keepAlive" value="true"/>
<spring:property name="keepSendSocketOpen" value="true"/>
<spring:property name="reuseAddress" value="true"/>
</custom-connector>
<tcp:endpoint connector-ref="myTcpConn" exchange-pattern="one-way" host="0.0.0.0" port="8081" name="TcpEndpoint" responseTimeout="10000" doc:name="TCP"/>
Unfortunately because of the visibility of the methods and instance variables, I had to resolve to copy+pasta the classes, update the casts, and only edited this bit in the MyTcpMessageDispatcher class:
@Override
protected synchronized void doDispatch(MuleEvent event) throws Exception
{
Socket socket = connector.getSocket(endpoint);
try
{
dispatchToSocket(socket, event);
}
catch (SocketException e)
{
System.err.println(e.toString());
socket.close();
throw e;
}
finally
{
connector.releaseSocket(socket, endpoint);
}
}
Known issue: When the external application to which the outbound endpoint goes down, and then comes back up, I seems to lose one message, even with transactions configured. It seems that it still successfully writes to the socket one last time before throwing socket write exceptions.