Giter Club home page Giter Club logo

Comments (8)

wilkinsona avatar wilkinsona commented on May 5, 2024

Unfortunately, the stack trace isn't useful as, while it tells us that a series of beans is being created, it doesn't tell us what they are. You can figure that out using the debugger by walking back up the stack and looking at the value of the beanName parameter for each occurrence of ….createBean(AbstractAutowireCapableBeanFactory.java:522). If you can share with us those bean names plus details of how each is defined, we may be able to figure out the cause.

from spring-boot.

ilx avatar ilx commented on May 5, 2024

beans are:

  • meterRegistryPostProcessor
  • actorSystemHolder
  • xlrDatabaseInformation
  • sqlConfiguration
  • databaseProxyFeature
  • distributedFeaturesConfiguration

where actorSystemHolder is (most of the code is Scala codebase):

class ActorSystemHolder(xlrConfig: XlrConfig, @Qualifier("xlrDatabaseInformation") dbInfo: DatabaseInfo)
  extends Logging with ScalaSpringAwareBean with FactoryBean[ActorSystem] {

  private var running: Boolean = false

  lazy val actorSystem: ActorSystem = {
    info("Starting up actor system.")
    if (xlrConfig.isActiveOnStartup) {
      NodeState.setActive(true)
    }

    val pekkoConfig = getSlickConfig.withFallback(xlrConfig.pekko.config)
    val system = ActorSystem(xlrConfig.pekko.systemName, pekkoConfig)
    system.registerExtension(SpringExtension)
    running = true
    system
  }

  def shutdown(): Unit = {
    if (running) {
      info("Initiating actor system shutdown.")
      if (!AutoDowning.isDowning) {
        // Actor system should be terminated by Coordinated Shutdown Pekko extension
        val result = CoordinatedShutdown.get(actorSystem).run(GracefulShutdownReason)
        info("Waiting for actor system shutdown (indefinitely).")
        Await.result(result, atMost = Duration.Inf)
      }
      running = false
      info("Actor system shutdown finished.")
    }
  }

  override def getObject: ActorSystem = actorSystem

  override def getObjectType: Class[_] = classOf[ActorSystem]

DatabaseInfo is just a bean inside SqlConfiguration:

  @Bean
  def xlrDatabaseInformation: DatabaseInfo = {
    DatabaseInfo(xlrRepositoryDataSourceProxy())
  }

where it extracts database information via connection.getMetadata:

object DatabaseInfo {

  def apply(ds: DataSource): DatabaseInfo = {
    map(ds.getConnection) { connection =>
      val metadata = connection.getMetaData
      DatabaseInfo(metadata)
    }
  }
class SqlConfiguration(xlrConfig: XlrConfig,
                       databaseProxyFeature: DatabaseProxyFeature,
                       metricsTrackerFactory: MetricsTrackerFactory
                      )

DatabaseProxyFeature and MetricsTrackerFactory are making it a bit complicated but I can remove them. In that case list of beans is shorter:

  • meterRegistryPostProcessor
  • actorSystemHolder
  • xlrDatabaseInformation
  • sqlConfiguration

from spring-boot.

wilkinsona avatar wilkinsona commented on May 5, 2024

Thanks for the details thus far. What's the bean that's being retrieved right at the bottom of the stack?

	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:205)
	at org.springframework.context.support.PostProcessorRegistrationDelegate.registerBeanPostProcessors(PostProcessorRegistrationDelegate.java:277)

from spring-boot.

ilx avatar ilx commented on May 5, 2024

Bean at the bottom of the stack is meterRegistryPostProcessor

image

from spring-boot.

ilx avatar ilx commented on May 5, 2024

Hmmm I noticed that the bean itself is configured via xml config:

    <bean id="actorSystemHolder" class="com.xebialabs.xlrelease.actors.ActorSystemHolder" destroy-method="shutdown"
          depends-on="beforeLiquibaseUpgrade">
        <constructor-arg name="xlrConfig" ref="xlrConfig"/>
        <constructor-arg name="dbInfo" ref="xlrDatabaseInformation" />
    </bean>

not sure if that makes difference

from spring-boot.

wilkinsona avatar wilkinsona commented on May 5, 2024

It being defined in XML may make a difference. It may mean that the bean factory can't tell statically what type the com.xebialabs.xlrelease.actors.ActorSystemHolder factory bean will produce so it's being created really early to figure that out. Defining it in an @Configuration class may help with that as may using ObjectProvider to injects its arguments as injection into a FactoryBean is a known cause of problems:

The XML snippet also shows that beforeLiquibaseUpgrade is involved here too. That will affect things.

I'm afraid that we're going to need the complete picture here – and ideally a sample that reproduces the problem – to make any further progress.

Injecting the application context into the constructor should not be a problem as there's special handling for that and it isn't treated like a normal bean. We can't change that without a complete understanding of the problem as it may not be the optimal solution and it may have unwanted side-effects that we couldn't justify without a full understanding.

from spring-boot.

ilx avatar ilx commented on May 5, 2024

TBH I suspect it has something to do with XML vs Annotation config.
It would take me some time to create reproducible sample (as this just works elsewhere).

In the meantime, i have traced this to the applicationContext passed via org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration#meterRegistryPostProcessor

Maybe it would be fine to make this postprocessor ApplicationContextAware and invoke hasNoCompositeMeterRegistryBeans once postProcessAfterInitialization is invoked?

i.e.

class MeterRegistryPostProcessor implements BeanPostProcessor, SmartInitializingSingleton, ApplicationContextAware {

	private final ObjectProvider<MetricsProperties> properties;

	private final ObjectProvider<MeterRegistryCustomizer<?>> customizers;

	private final ObjectProvider<MeterFilter> filters;

	private final ObjectProvider<MeterBinder> binders;

	private volatile boolean deferBinding = true;

	private final Set<MeterRegistry> deferredBindings = new LinkedHashSet<>();
	
	private ApplicationContext applicationContext;

	private boolean hasNoCompositeMeterRegistryBeans(ApplicationContext applicationContext) {
		return applicationContext.getBeanNamesForType(CompositeMeterRegistry.class, false, false).length == 0;
	}

	MeterRegistryPostProcessor(ObjectProvider<MetricsProperties> properties,
	                           ObjectProvider<MeterRegistryCustomizer<?>> customizers, ObjectProvider<MeterFilter> filters,
	                           ObjectProvider<MeterBinder> binders) {
		this.properties = properties;
		this.customizers = customizers;
		this.filters = filters;
		this.binders = binders;

	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if (bean instanceof MeterRegistry meterRegistry) {
			postProcessMeterRegistry(meterRegistry);
		}
		return bean;
	}

	@Override
	public void afterSingletonsInstantiated() {
		synchronized (this.deferredBindings) {
			this.deferBinding = false;
			this.deferredBindings.forEach(this::applyBinders);
		}
	}

	private void postProcessMeterRegistry(MeterRegistry meterRegistry) {
		// Customizers must be applied before binders, as they may add custom tags or
		// alter timer or summary configuration.
		applyCustomizers(meterRegistry);
		applyFilters(meterRegistry);
		addToGlobalRegistryIfNecessary(meterRegistry);
		if (isBindable(meterRegistry)) {
			applyBinders(meterRegistry);
		}
	}

	@SuppressWarnings("unchecked")
	private void applyCustomizers(MeterRegistry meterRegistry) {
		List<MeterRegistryCustomizer<?>> customizers = this.customizers.orderedStream().toList();
		LambdaSafe.callbacks(MeterRegistryCustomizer.class, customizers, meterRegistry)
				.withLogger(MeterRegistryPostProcessor.class)
				.invoke((customizer) -> customizer.customize(meterRegistry));
	}

	private void applyFilters(MeterRegistry meterRegistry) {
		if (meterRegistry instanceof AutoConfiguredCompositeMeterRegistry) {
			return;
		}
		this.filters.orderedStream().forEach(meterRegistry.config()::meterFilter);
	}

	private void addToGlobalRegistryIfNecessary(MeterRegistry meterRegistry) {
		if (this.properties.getObject().isUseGlobalRegistry() && !isGlobalRegistry(meterRegistry)) {
			Metrics.addRegistry(meterRegistry);
		}
	}

	private boolean isGlobalRegistry(MeterRegistry meterRegistry) {
		return meterRegistry == Metrics.globalRegistry;
	}

	private boolean isBindable(MeterRegistry meterRegistry) {
		return hasNoCompositeMeterRegistryBeans(applicationContext) || isCompositeMeterRegistry(meterRegistry);
	}

	@Override
	public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}

	private boolean isCompositeMeterRegistry(MeterRegistry meterRegistry) {
		return meterRegistry instanceof CompositeMeterRegistry;
	}

	void applyBinders(MeterRegistry meterRegistry) {
		if (this.deferBinding) {
			synchronized (this.deferredBindings) {
				if (this.deferBinding) {
					this.deferredBindings.add(meterRegistry);
					return;
				}
			}
		}
		this.binders.orderedStream().forEach((binder) -> binder.bindTo(meterRegistry));
	}


}

I tried this and it does not cause the checker to fail and I do not see any problems.
I'm not sure if there is another post processor that uses the same pattern...

Is there a chance to accept this change or at least somehow make processor instantiation "tweakable" (maybe check presence of the postprocessor with same name)?

from spring-boot.

wilkinsona avatar wilkinsona commented on May 5, 2024

I tried this and it does not cause the checker to fail and I do not see any problems. I'm not sure if there is another post processor that uses the same pattern...

Is there a chance to accept this change

I tried to address this at the end of my previous comment:

Injecting the application context into the constructor should not be a problem as there's special handling for that and it isn't treated like a normal bean. We can't change that without a complete understanding of the problem as it may not be the optimal solution and it may have unwanted side-effects that we couldn't justify without a full understanding.

I am still of the opinion that we should not change this without a full understanding of the problem. How the ApplicationContext is injected should not make any difference. Apparently it is making a difference so there's something unknown here that we don't understand. Until we do, it would be hard to justify making a change.

somehow make processor instantiation "tweakable" (maybe check presence of the postprocessor with same name)

You could perhaps use a bean factory post process to replace the definition of the MeterRegistryPostProcessor. In your situation, I would invest time in creating a reproducer rather than resorting to such a hack.

from spring-boot.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.