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

MeterRegistry throws BeanCreationNotAllowedException on shutdown #38240

Closed
wojciechkedziora opened this issue Nov 7, 2023 · 8 comments
Closed
Assignees
Labels
type: bug A general bug
Milestone

Comments

@wojciechkedziora
Copy link

We use Micrometer with NewRelic in our Spring Boot 3.1.5 applications.

After migration to the newest Spring Boot we noticed that following WARN log always appears during application shutdown:

Failed to apply the value function for the gauge '{{custom_gauge_name}}'.

The exception behind above log looks like:

`org.springframework.beans.factory.BeanCreationNotAllowedException: Error creating bean with name 'entityManagerFactory': Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!)`

App is integrated with NewRelic agent - i checked that mentioned exception is triggered when:

	at com.newrelic.telemetry.micrometer.NewRelicRegistry.publish(NewRelicRegistry.java:168)
	at io.micrometer.core.instrument.push.PushMeterRegistry.publishSafely(PushMeterRegistry.java:67
	at io.micrometer.core.instrument.push.PushMeterRegistry.close(PushMeterRegistry.java:135)
	at io.micrometer.core.instrument.step.StepMeterRegistry.close(StepMeterRegistry.java:156)
	at com.newrelic.telemetry.micrometer.NewRelicRegistry.close(NewRelicRegistry.java:157)

Please note that similar bug for older Spring Boot 2.2 version was reported under #19557. I've reproduced reported bug: https://github.com/wojciechkedziora/spring-boot-3-19557 (check this comment for more details).

@philwebb
Copy link
Member

philwebb commented Nov 8, 2023

Trying to use "depends on" configuration is turning out to be quite hard. The queueMeterBinder wants to use the QueueItemRepository but we also have MetricsRepositoryMethodInvocationListener which is trying to record metrics against all Spring Data Repository beans.

I'm wondering if the earlier idea floated in #19557 (comment) might be a better solution.

It feels like we should close all MeterRegistry beans before we start the destruction process.

The following seems to prevent the exception:

@Bean
MeterRegistryLifecycle meterRegistryLifecycle(ObjectProvider<MeterRegistry> meterRegistries) {
	return new MeterRegistryLifecycle(meterRegistries);
}
private static class MeterRegistryLifecycle implements SmartLifecycle {

	private volatile boolean running;

	private final List<MeterRegistry> meterRegistries;

	MeterRegistryLifecycle(ObjectProvider<MeterRegistry> meterRegistries) {
		this.meterRegistries = meterRegistries.orderedStream().toList();
	}

	@Override
	public void start() {
		this.running = true;
	}

	@Override
	public void stop() {
		this.running = false;
		meterRegistries.forEach(MeterRegistry::close);
	}

	@Override
	public boolean isRunning() {
		return this.running;
	}

}

@philwebb philwebb added the for: team-meeting An issue we'd like to discuss as a team to make progress label Nov 8, 2023
@wilkinsona
Copy link
Member

Configuring all MeterRegistry beans to depend on all MeterBinder beans also seems to help. I think closing the MeterRegistry beans early is less likely to have unwanted side-effects though. It would come at the cost of losing any metric updates that occur during shutdown but that's probably a price worth paying.

@philwebb philwebb added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged for: team-meeting An issue we'd like to discuss as a team to make progress labels Nov 13, 2023
@philwebb philwebb added this to the 3.2.x milestone Nov 13, 2023
@philwebb
Copy link
Member

We talked about this today as team and decided that we'd like to try the MeterRegistryLifecycle approach. Although this is a bug, we're concerned that it's too risky to put into earlier versions.

@mhalbritter mhalbritter self-assigned this Nov 15, 2023
@mhalbritter mhalbritter modified the milestones: 3.2.x, 3.2.0 Nov 15, 2023
@mhalbritter mhalbritter changed the title Micrometer - exception during application shutdown (bean dependency issue) MeterRegistry throws BeanCreationNotAllowedException on shutdown Nov 15, 2023
@wilkinsona
Copy link
Member

As pointed out by @shakuzen, the lifecycle approach may not play nicely with coordinated restore at checkpoint. When the checkpoint is taken, lifecycles will be stopped. This will result in all of the meter registries being closed. With nothing to re-open the registries (and no API that even makes that possible) they'll remain closed upon restore and no more metrics will be published.

@wilkinsona wilkinsona reopened this Nov 15, 2023
@wilkinsona wilkinsona added the for: team-meeting An issue we'd like to discuss as a team to make progress label Nov 15, 2023
@mhalbritter mhalbritter modified the milestones: 3.2.0, 3.2.x Nov 15, 2023
@mhalbritter
Copy link
Contributor

mhalbritter commented Nov 15, 2023

Something like this works (at least in the example, not sure if it works on every occasion):

    @Bean
    MicrometerRegistryCloser micrometerRegistryCloser(ObjectProvider<MeterRegistry> meterRegistries) {
        return new MicrometerRegistryCloser(meterRegistries.orderedStream().toList());
    }

    private static class MicrometerRegistryCloser implements DisposableBean {
        private final List<MeterRegistry> meterRegistries;

        private MicrometerRegistryCloser(List<MeterRegistry> meterRegistries) {
            this.meterRegistries = meterRegistries;
        }

        @Override
        public void destroy() {
            for (MeterRegistry meterRegistry : this.meterRegistries) {
                if (!meterRegistry.isClosed()) {
                    meterRegistry.close();
                }
            }
        }
    }

@mhalbritter
Copy link
Contributor

mhalbritter commented Nov 15, 2023

And this would work, too:

    private static class MicrometerRegistryCloser implements ApplicationListener<ContextClosedEvent> {
        private final List<MeterRegistry> meterRegistries;

        private MicrometerRegistryCloser(List<MeterRegistry> meterRegistries) {
            this.meterRegistries = meterRegistries;
        }

        @Override
        public void onApplicationEvent(ContextClosedEvent event) {
            for (MeterRegistry meterRegistry : this.meterRegistries) {
                if (!meterRegistry.isClosed()) {
                    meterRegistry.close();
                }
            }
        }
    }

Looking at org.springframework.context.support.AbstractApplicationContext#doClose, the ContextClosedEvent is published before the LifecycleProcessor is called and before destroy methods are invoked.

@wilkinsona
Copy link
Member

I prefer using ContextClosedEvent to a DisposableBean. The point during shutdown at which the disposable bean will be disposed is somewhat variable – I think it depends on when the bean's created for one – whereas the ContextClosedEvent will always be published at a well-defined point in the lifecycle.

@mhalbritter mhalbritter removed the for: team-meeting An issue we'd like to discuss as a team to make progress label Nov 15, 2023
@mhalbritter
Copy link
Contributor

We talked about it in our meeting and decided to go with the ApplicationListener approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

No branches or pull requests

5 participants