Skip to content
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

Avoid duplicate ApplicationListener firing (proxy vs. target) #28322

Merged
merged 2 commits into from
Oct 10, 2023

Conversation

kriegaex
Copy link
Contributor

In AbstractApplicationEventMulticaster.retrieveApplicationListeners, despite best efforts to avoid it, unwrapped proxies (singleton targets) can end up in the list of programmatically registered listeners. In order to avoid duplicates, we need to find and replace them by their proxy counterparts, because if both a proxy and its target end up in allListeners, listeners will fire twice.

Fixes #28283.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Apr 11, 2022
@kriegaex
Copy link
Contributor Author

kriegaex commented Apr 19, 2022

Hello. I am not sure who is going to be able to look into this, maybe @jhoeller or @sbrannen? Is there anything I can or need to do in order to facilitate this being reviewed? I know there are many open bugs in Spring, but only maybe 10% of them have actual PRs attached. Maybe there is a way to expedite this and get it off the table. If I just should be waiting because of work overload or Easter holidays, feel free to scold me, I would not mind. At least I would know that this was noticed and I just need to wait. 🙂

@rstoyanchev rstoyanchev added the in: core Issues in core modules (aop, beans, core, context, expression) label Apr 25, 2022
@AgataKowal
Copy link

Hi @kriegaex
Is there a chance for this PR to be ever merged? Are you aware if this problem has been addressed in any other PRs? It was not easy to find this problem and I was happy to see the solution... I have bumped into the same problem and it causes indexing issues (the event that should always be received only once was received twice). I am wondering how to handle it externally, without changes in the spring core code.

@kriegaex
Copy link
Contributor Author

@AgataKowal, I am not a committer, just a contributor. Therefore, I have no idea why this is still "in triage" and has never been commented on, not to mention merged. I even directly addressed two developers who previously committed code in this area. There is not much more I can do, only wait and hope.

Copy link
Member

@sbrannen sbrannen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @kriegaex,

Apologies for the belated review. This one slipped through the cracks.

Can you please introduce a test that fails before the change and passes afterwards?

Thanks

@sbrannen sbrannen added type: bug A general bug status: waiting-for-feedback We need additional information before we can continue and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Jul 10, 2023
@kriegaex
Copy link
Contributor Author

kriegaex commented Jul 10, 2023

@sbrannen, I will be travelling a lot in the next few days, but try to see what I can do. It was long ago, but my comments on the related bug should give me enough information to reproduce it. Can you please point me to a directory with similar tests, so I can learn what your usual tests look like? I guess we are talking about an integration test, don't we?

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Jul 10, 2023
@sbrannen
Copy link
Member

Can you please point me to a directory with similar tests, so I can learn what your usual tests look like. I guess we are talking about an integration test, don't we?

Please take a look at org.springframework.context.event.AbstractApplicationEventListenerTests and the other test classes in the same package in the spring-context module. If that doesn't help, let us know.

@kriegaex
Copy link
Contributor Author

kriegaex commented Jul 18, 2023

@sbrannen: I just rebased on the main branch locally. Now, IDEA cannot even import the Gradle build anymore. Unfortunately, I am a Maven person and do not speak Gradle. How can I build this thing without JDK 21?

Update: Sigh - never mind, I installed it and somehow managed to make Gradle detect it according to https://github.com/spring-projects/spring-framework/wiki/Build-from-Source#before-you-start. But that was the time box earlier today for actually trying to write a regression test, which still I have not started to do.

@kriegaex kriegaex force-pushed the issue-28283 branch 2 times, most recently from 8ccdc30 to 6ff626d Compare July 19, 2023 01:48
@kriegaex
Copy link
Contributor Author

kriegaex commented Jul 19, 2023

@sbrannen, unfortunately, I am not an active Spring user, just an AOP expert trying to help where I can. My debugging skills were enough to fix this issue, but I really do not comprehend how those tests work and how I can add my test case to the test bed in the right way. However, I hope that you can.

Please, take a look at temporary commit 294be23. If you run org.springframework.context.event.i28283.DemoApplication from the test directory with VM option -ea or -enableassertions on the JVM command line, the assertion will fail before and pass after the commit fixing the problem. That should give you an idea about where and how to add a regression test to one of the existing classes. Feel free to check out and push to this PR. After the test has been implemented, we can drop the commit containing the reproducer.

I noticed that there are tests checking certain things with CGLIB proxies. Somehow, they would have to be amended or modified to also reproduce the situation with the self-injected event listener.

@kriegaex
Copy link
Contributor Author

kriegaex commented Jul 21, 2023

OK, I took some more time to convert my reproducer into a simple test which fails before the fix and passes afterwards, see commit 0dba415. @sbrannen, I think, you can review the PR now.

kriegaex added 2 commits July 21, 2023 04:46
ApplicationContextEventTests.eventForSelfInjectedProxiedListenerFiredOnlyOnce
relates to and reproduces spring-projects#28283.
In AbstractApplicationEventMulticaster.retrieveApplicationListeners,
despite best efforts to avoid it, unwrapped proxies (singleton targets)
can end up in the list of programmatically registered listeners. In
order to avoid duplicates, we need to find and replace them by their
proxy counterparts, because if both a proxy and its target end up in
'allListeners', listeners will fire twice.

Fixes spring-projects#28283.
@kriegaex
Copy link
Contributor Author

@sbrannen, can we please get this off the table after 1.5 years and before I forget the whole context again and have to think myself into the problem one more time? Like this time, when trying to remember what it was all about, how I debugged it, and spending more time finding out how to write a test for it than fixing the actual bug? I know you must be busy, but please let us put this behind us, and please also port it back to all other versions which still get updates. Thank you.

@sbrannen sbrannen changed the title Avoid duplicate application listeners (proxy vs. proxy target) Avoid duplicate ApplicationListener firing (proxy vs. proxy target) Aug 23, 2023
@sbrannen sbrannen changed the title Avoid duplicate ApplicationListener firing (proxy vs. proxy target) Avoid duplicate ApplicationListener firing (proxy vs. target) Aug 23, 2023
@sbrannen sbrannen added this to the 6.0.12 milestone Aug 23, 2023
@sbrannen sbrannen self-assigned this Aug 23, 2023
@sbrannen sbrannen removed the status: feedback-provided Feedback has been provided label Aug 23, 2023
Copy link
Member

@sbrannen sbrannen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding the failing test. That helps a lot!

@sbrannen sbrannen removed their assignment Aug 23, 2023
@sbrannen
Copy link
Member

I took a look at the failing example and the proposed fix.

There's definitely an issue there when an ApplicationListener is proxied and relies on self-injection (via @Autowired in the example MyEventListener).

Note that AbstractApplicationEventMulticaster.addApplicationListener() already contains logic to:

Explicitly remove target for a proxy, if registered already, in order to avoid double invocations of the same listener.

However, that doesn't cover the use case in the provided example. Thus, we need to find the best way to address this.

The proposed fix does indeed allow the example to pass, but I'm not yet sure if the proposed fix is the only/best way to address this issue.

In light of that, someone from the team will need to take a closer look at the underlying issue, tentatively for inclusion in 6.0.12.

@jhoeller jhoeller self-assigned this Aug 23, 2023
@jhoeller jhoeller modified the milestones: 6.0.12, 6.1.0-RC1 Sep 12, 2023
@jhoeller jhoeller merged commit 20c688e into spring-projects:main Oct 10, 2023
jhoeller added a commit that referenced this pull request Oct 10, 2023
@kriegaex
Copy link
Contributor Author

kriegaex commented Oct 11, 2023

Thank you for the merge. 🙂

@jhoeller, is anything stopping you from porting back this fix to the 6.0.x and 5.3.x branches? Even in 5.2.x , I saw a commit as recent as July, so maybe that would be a candidate, too. But the former two definitely are, I guess.

Given the fact the the changed code is quite old, I would not expect any serious merge conflicts, if any at all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: bug A general bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Using AOP and DI itself causes Spring ApplicationListener to be fired twice
6 participants