The way that AbstractPool uses the TransactionSynchronizationRegistry appears to be incorrect.
I discovered that if two XA pools exist and both are used in a transaction, the second pool hands out a reference to the first pool's ConnectionListener in getTransactionOldConnection().
The reason is that AbstractPool uses TSR.getResource() and TSR.putResource() using a key based on the current Transaction. When the second pool checks for an existing connection, it uses the same key as the first pool (since it exists in the same transaction).
I believe the correct approach is to use a key that is based on the relevant ManagedConnectionPool instead of the current Transaction.
A patch with tests will be added soon.