spring-projects/spring-amqp — pull request #202 by garyrussell (2014-08-07 20:01:36+00:00)
Title: AMQP-417 CoExist With AutoRecoveryEnabled

JIRA: https://jira.spring.io/browse/AMQP-417

When the underlying connection factory has `autoRecoveryEnabled` set to
`true`, the client maintains a list of open channels (in
AutorecoveringConnection). When the client is able to reconnect, it
re-establishes these channels.

Channels are removed from the list of recovery candidates when they
are `close()`d.

These mechanisms are incompatible with the existing `CachingConnectionFactory`
and the `SimpleMessageListenerContainer`.

First, when the caching connection factory detects its connection is closed,
it attempts to create a new connection. It is not aware of the old 'recovered'
connection, which ends up leaked.

Second, when physically closing a channel (always done by container consumers),
`close()` is only called if `isOpen()` returns true - hence the "closed"
channel is not removed from the recovery list, but it is no longer in the
cache so the channel is leaked. Worse, the associated blocking queue will
receive messages after recovery, but since the consumer is no longer active
those messages will remain in the queue unacked.

Similarly, cached channels, when retrieved from the cache, are silently
dropped from the cache if `isOpen()` returns false. Again, these remain
as recovery candidates, but are no longer available in the cache.

USERS ARE ADVISED TO NOT USE AUTORECOVERYENABLED WITH SPRING AMQP

Full resolution will require changing the architecture of the listener
container.

This commit provides a work around for those users that do set the option;
in essence, it disables channel recovery for those channels that are managed
by Spring.

- `SimpleConnection.isOpen()` always returns true for `AutorecoveringConnection`s
This prevents the caching connection factory from creating a new connection
- it will reuse this connection when recovered.
- When pysically closing channels, don't check `isOpen()`. Instead, always call
`close()` and catch any `AlreadyClosedException`. This forces the
channel to remove itself (unregister) from the `AutoRecoveringConnection`'s
recoverable channels.

This is difficult to create an automated integration test. A test with mocks
veriifies that the channel is physically closed (run the test without the code
changes and it fails - `close()` is not called).

Also tested with the Spring Integration AMQP sample application. With
`autoRecoveryEnabled`, restarting the server causes 2 connections and 3
channels. With the commit, the recovery is disabled and a single connection
(auto-recovered) is established, with the consumer and producer channels
(the producer channel is only created when you send a message).