Skip to content

IMAP idle channel adapter reconnect behavior seems to be different when (not) using a mail filter expression #9297

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
smitsjelle opened this issue Jul 2, 2024 · 4 comments

Comments

@smitsjelle
Copy link
Contributor

Behavior observed in: Spring Integration 6.2.1 (as no recent changes I assume it applies to later versions as well)

Describe the bug
I have some Imap idle channel adapters set up with various settings. I notice that occasionally, one of the mailservers has a connection hiccup. For a normal jakarta.mail.StoreClosedException: * BYE Jakarta Mail Exception: java.net.SocketException: Connection reset this results in a resubmission (and reconnection) with logging 'Failed to execute IDLE task. Will attempt to resubmit in 10000 milliseconds.'.

However, on the idle channel adapter that also has a 'mail-filter-expression' it seems that this may also result in a problem while the filter expression is being evaluated, causing a SpelEvaluationException with as root exception a 'jakarta.mail.FolderClosedException'. Since in this specific case the ImapIdleChannelAdapter#callIdle method will break the while loop in the else clause because the ex.getCause() is not an instanceof jakarta.mail.MessagingException (even though ex.getCause().getCause().getCause() is).

I know that I can register an ApplicationEventListener that acts upon the ApplicationEvent published by the ImapIdleChannelAdapter#publishException (and essentially restarts the adapter), but I feel that it is odd that there is nice built-in behavior to restart the adapter when some jakarta.mail.MessagingException occurs as the cause of the current exception, but not when it is a deeper nested cause, because built-in SpEL expression logic is used. In my eyes, this would be 'fixed' as well if ImapIdleChannelAdapter line 203 would do check whether the stack trace of the Exception has a MessagingException cause, instead of only checking the cause directly above. (i.e. a (fictional) 'hasCause' instead of 'getCause')?

Expected behavior
The IMAP idle channel adapter has the same reconnect behavior whether a 'mail filter expression' is used or not

Sample
Although this is quite difficult to reproduce, as it is essentially seems to be caused by a server hiccup that is not generally the case for large mail providers such as Gmail or Outlook, I have provided a small XML configuration sample below of the used configuration.

<mail:imap-idle-channel-adapter channel="mailFromImap"
    java-mail-properties="mailProperties"
    auto-close-folder="true" 
    id="imapIdle"
    mail-filter-expression="subject matches '.*TEST.*'"
    should-delete-messages="false" 
    should-mark-messages-as-read="true"
    simple-content="true"
    store-uri="imaps://username:password@host/inbox"/>

<util:map id="mailProperties" 
    key-type="java.lang.String" 
    map-class="java.util.Properties"
    value-type="java.lang.String">
      <entry key="mail.imaps.timeout" value="240000"/>
</util:map>

Log message with (reversed) stack trace when the issue occurs:

Failed to execute IDLE task. Won't resubmit since not a 'shouldReconnectAutomatically' or not a 'jakarta.mail.MessagingException'
Stack trace:
jakarta.mail.FolderClosedException: null
	at org.eclipse.angus.mail.imap.IMAPMessage.getProtocol(IMAPMessage.java:182)
	at org.eclipse.angus.mail.imap.IMAPMessage.loadEnvelope(IMAPMessage.java:1502)
	at org.eclipse.angus.mail.imap.IMAPMessage.getSubject(IMAPMessage.java:454)
	... 18 common frames omitted
Wrapped by: java.lang.reflect.InvocationTargetException: null
	at jdk.internal.reflect.GeneratedMethodAccessor43.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.base/java.lang.reflect.Method.invoke(Unknown Source)
	at org.springframework.expression.spel.support.ReflectivePropertyAccessor$OptimalPropertyAccessor.read(ReflectivePropertyAccessor.java:694)
	... 14 common frames omitted
Wrapped by: org.springframework.expression.AccessException: Unable to access property 'subject' through getter method
	at org.springframework.expression.spel.support.ReflectivePropertyAccessor$OptimalPropertyAccessor.read(ReflectivePropertyAccessor.java:698)
	at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:215)
	... 13 common frames omitted
Wrapped by: org.springframework.expression.spel.SpelEvaluationException: EL1021E: A problem occurred whilst attempting to access the property 'subject': 'Unable to access property 'subject' through getter method'
	at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:220)
	at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:111)
	at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:99)
	at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:212)
	at org.springframework.expression.spel.ast.OperatorMatches.getValueInternal(OperatorMatches.java:88)
	at org.springframework.expression.spel.ast.OperatorMatches.getValueInternal(OperatorMatches.java:42)
	at org.springframework.expression.spel.ast.SpelNodeImpl.getTypedValue(SpelNodeImpl.java:119)
	at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:376)
	at org.springframework.integration.mail.AbstractMailReceiver.filterMessagesThruSelector(AbstractMailReceiver.java:556)
	at org.springframework.integration.mail.AbstractMailReceiver.searchAndFilterMessages(AbstractMailReceiver.java:425)
	at org.springframework.integration.mail.AbstractMailReceiver.receive(AbstractMailReceiver.java:380)
	at org.springframework.integration.mail.ImapIdleChannelAdapter.processIdle(ImapIdleChannelAdapter.java:229)
	at org.springframework.integration.mail.ImapIdleChannelAdapter.callIdle(ImapIdleChannelAdapter.java:198)
	at java.base/java.lang.Thread.run(Unknown Source)
@smitsjelle smitsjelle added status: waiting-for-triage The issue need to be evaluated and its future decided type: bug labels Jul 2, 2024
@artembilan
Copy link
Member

OK. So, your proposal is to iterate ex.getCause() until we meet jakarta.mail.MessagingException.
Any ideas how we can fix that?

I'm failing at the moment to find a useful utility which does that for us...

@artembilan artembilan added in: mail (EOL) for: backport-to-6.2.x for: backport-to-6.3.x and removed status: waiting-for-triage The issue need to be evaluated and its future decided labels Jul 2, 2024
@artembilan artembilan added this to the 6.4.0-M1 milestone Jul 2, 2024
@artembilan
Copy link
Member

So, I came up with this utility method:

	@Nullable
	private static jakarta.mail.MessagingException getJakartaMailMessagingExceptionFromCause(Throwable cause) {
		if (cause == null) {
			return null;
		}
		if (cause instanceof jakarta.mail.MessagingException messagingException) {
			return messagingException;
		}
		Throwable nextCause = cause.getCause();
		if (cause == nextCause) {
			return null;
		}
		return getJakartaMailMessagingExceptionFromCause(nextCause);
	}

Is that OK for what you are looking for?

@smitsjelle
Copy link
Contributor Author

Hi,

Thanks for your quick responses. This method would indeed nicely iterate to find the jakarta.mail.MessagingException, if any is in the stack trace and therefore seems to cover what I'd be looking for. For the stack trace provided, including this as a check in the catch clause of the callIdle() should prevent breaking out of the loop (and thus having to restart).

@artembilan
Copy link
Member

Right. So, pushing that one then.
Thanks for confirmation!

spring-builds pushed a commit that referenced this issue Jul 2, 2024
Fixes: #9297

For a normal `jakarta.mail.StoreClosedException: * BYE Jakarta Mail Exception: java.net.SocketException: Connection reset`
 the `ImapIdleChannelAdapter.callIdle()` results in a resubmission (and reconnection)
 with logging 'Failed to execute IDLE task. Will attempt to resubmit in 10000 milliseconds.'`
However, when `selectorExpression` is in used and that one is based on mail `Message` object,
we may fail with `FolderClosedException` which is wrapped to the `SpelEvaluationException` and some
other stack traces.
So, `jakarta.mail.MessagingException` might be deep in the cause chain.

* Fix `ImapIdleChannelAdapter` via introducing `getJakartaMailMessagingExceptionFromCause()` utility
method which searches for the `jakarta.mail.MessagingException` in cause chain.
* Rework `ImapIdleChannelAdapter.callIdle()` logic to rely on this new method

(cherry picked from commit 272fcd0)
spring-builds pushed a commit that referenced this issue Jul 2, 2024
Fixes: #9297

For a normal `jakarta.mail.StoreClosedException: * BYE Jakarta Mail Exception: java.net.SocketException: Connection reset`
 the `ImapIdleChannelAdapter.callIdle()` results in a resubmission (and reconnection)
 with logging 'Failed to execute IDLE task. Will attempt to resubmit in 10000 milliseconds.'`
However, when `selectorExpression` is in used and that one is based on mail `Message` object,
we may fail with `FolderClosedException` which is wrapped to the `SpelEvaluationException` and some
other stack traces.
So, `jakarta.mail.MessagingException` might be deep in the cause chain.

* Fix `ImapIdleChannelAdapter` via introducing `getJakartaMailMessagingExceptionFromCause()` utility
method which searches for the `jakarta.mail.MessagingException` in cause chain.
* Rework `ImapIdleChannelAdapter.callIdle()` logic to rely on this new method

(cherry picked from commit 272fcd0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants