Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,43 @@

package com.tencent.trpc.spring.boot.starters.listener;

import com.tencent.trpc.core.common.ConfigManager;
import com.tencent.trpc.core.logger.Logger;
import com.tencent.trpc.core.logger.LoggerFactory;
import com.tencent.trpc.spring.context.TRpcLifecycleManager;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.Order;

import java.util.concurrent.atomic.AtomicBoolean;

/**
* Handle the application failed event, the reason was TRpcLifecycleManager called before the application failed.
* <p>Only stop tRPC through the existing {@link TRpcLifecycleManager} singletons of the failed context,
* never create new beans, since the context may have been partly destroyed.</p>
*/
@Order
public class TRpcApplicationFailedListener implements ApplicationListener<ApplicationFailedEvent> {
private static final AtomicBoolean PROCESSED = new AtomicBoolean(false);

private static final Logger logger = LoggerFactory.getLogger(TRpcApplicationFailedListener.class);

@Override
public void onApplicationEvent(ApplicationFailedEvent event) {
if (PROCESSED.compareAndSet(false, true)) {
stopTRPC();
ConfigurableApplicationContext context = event.getApplicationContext();
if (context == null) {
return;
}
ConfigurableListableBeanFactory beanFactory;
try {
beanFactory = context.getBeanFactory();
} catch (IllegalStateException e) {
logger.debug("bean factory of the failed application context is not available", e);
return;
}
for (String beanName : beanFactory.getBeanNamesForType(TRpcLifecycleManager.class, false, false)) {
Object lifecycleManager = beanFactory.getSingleton(beanName);
if (lifecycleManager instanceof TRpcLifecycleManager) {
((TRpcLifecycleManager) lifecycleManager).stopIfStarted();
}
}
}

private void stopTRPC() {
ConfigManager.getInstance().stop();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Tencent is pleased to support the open source community by making tRPC available.
*
* Copyright (C) 2023 Tencent.
* All rights reserved.
*
* If you have downloaded a copy of the tRPC source code from Tencent,
* please note that tRPC source code is licensed under the Apache 2.0 License,
* A copy of the Apache 2.0 License can be found in the LICENSE file.
*/

package com.tencent.trpc.spring.boot.starters.listener;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;

import com.tencent.trpc.spring.context.TRpcLifecycleManager;
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.GenericApplicationContext;

public class TRpcApplicationFailedListenerTest {

private final TRpcApplicationFailedListener listener = new TRpcApplicationFailedListener();

@Test
public void testNullContext() {
assertDoesNotThrow(() -> listener.onApplicationEvent(newEvent(null)));
}

@Test
public void testBeanFactoryNotAvailable() {
// bean factory of a not refreshed refreshable context is not available
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext();
assertDoesNotThrow(() -> listener.onApplicationEvent(newEvent(context)));
}

@Test
public void testStopExistingLifecycleManager() {
CountingLifecycleManager lifecycleManager = new CountingLifecycleManager();
GenericApplicationContext context = new GenericApplicationContext();
context.getBeanFactory().registerSingleton("tRpcLifecycleManager", lifecycleManager);

listener.onApplicationEvent(newEvent(context));
assertEquals(1, lifecycleManager.stopCount);
}

@Test
public void testNotCreateLifecycleManager() {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("tRpcLifecycleManager", TRpcLifecycleManager.class);

listener.onApplicationEvent(newEvent(context));
assertFalse(context.getBeanFactory().containsSingleton("tRpcLifecycleManager"));
}

private static ApplicationFailedEvent newEvent(ConfigurableApplicationContext context) {
return new ApplicationFailedEvent(new SpringApplication(), new String[0], context,
new RuntimeException("startup failed"));
}

private static class CountingLifecycleManager extends TRpcLifecycleManager {

private int stopCount;

@Override
public void stopIfStarted() {
stopCount++;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ public TRpcLifecycleManager tRpcLifecycleManager() {
return new TRpcLifecycleManager();
}

/**
* Build {@link TRpcShutdownListener}
*
* @param tRpcLifecycleManager the {@link TRpcLifecycleManager} which started tRPC
* @return TRpcShutdownListener
*/
@Bean
public TRpcShutdownListener tRpcShutdownListener(TRpcLifecycleManager tRpcLifecycleManager) {
return new TRpcShutdownListener(tRpcLifecycleManager);
}

/**
* Build {@link TRpcConfigManagerInitializer}.
* <p>Users could implement {@link TRpcConfigManagerCustomizer} and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,65 +15,106 @@
import com.tencent.trpc.core.common.config.ProviderConfig;
import com.tencent.trpc.spring.util.TRpcSpringUtils;
import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.lang.NonNull;

/**
* Response on {@link ContextRefreshedEvent} and {@link ContextClosedEvent}
* to perform necessary tRPC operations.
* Start tRPC on {@link ContextRefreshedEvent}.
* <p>Runs late so that other {@link ContextRefreshedEvent} listeners (e.g. Spring Cloud RefreshScope eager
* initialization) have finished before tRPC begins to accept traffic. Shutdown is handled by
* {@link TRpcShutdownListener}, which runs first on {@link org.springframework.context.event.ContextClosedEvent}.</p>
* <p>Only events of the application context which this manager belongs to are handled, events propagated from
* child contexts (e.g. actuator management context, whose id is {@code {parentId}:management}) are ignored.</p>
*/
public class TRpcLifecycleManager implements SmartApplicationListener {
public class TRpcLifecycleManager implements ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware,
DisposableBean, Ordered {

/**
* tRPC 容器启动标记
* Order of starting tRPC on {@link ContextRefreshedEvent}.
*/
public static final int START_ORDER = Ordered.LOWEST_PRECEDENCE - 20;

/**
* Whether tRPC has been started by this manager.
*/
private final AtomicBoolean started = new AtomicBoolean(Boolean.FALSE);

/**
* The application context which this manager belongs to.
*/
private ApplicationContext applicationContext;

@Override
public boolean supportsEventType(@NonNull Class<? extends ApplicationEvent> eventType) {
return ContextRefreshedEvent.class == eventType
|| ContextClosedEvent.class == eventType;
public void setApplicationContext(@NonNull ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}

@Override
public void onApplicationEvent(@NonNull ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent) {
onContextRefreshedEvent((ContextRefreshedEvent) event);
} else {
onContextClosedEvent((ContextClosedEvent) event);
}
/**
* Whether tRPC has been started by this manager.
*
* @return true if started
*/
public boolean isStarted() {
return started.get();
}

/**
* Start tRPC services on {@link ContextRefreshedEvent}
* Whether the context is managed by this manager, i.e. it is the context this manager belongs to
* and requires aware of tRPC.
*
* @param context the context of the event
* @return true if managed
*/
private void onContextRefreshedEvent(ContextRefreshedEvent event) {
if (!TRpcSpringUtils.isAwareContext(event.getApplicationContext())) {
return;
public boolean isManagedContext(ApplicationContext context) {
if (context == null) {
return false;
}
if (started.compareAndSet(Boolean.FALSE, Boolean.TRUE)) {
replaceProvidersRef(event.getApplicationContext());
startTRPC();
if (applicationContext != null && applicationContext != context) {
return false;
}
return TRpcSpringUtils.isAwareContext(context);
}

/**
* Shutdown tRPC services on {@link ContextClosedEvent}
* Stop tRPC only if it has been started by this manager, e.g. on context close or application startup failure.
*/
private void onContextClosedEvent(ContextClosedEvent event) {
if (!TRpcSpringUtils.isAwareContext(event.getApplicationContext())) {
public void stopIfStarted() {
if (started.compareAndSet(Boolean.TRUE, Boolean.FALSE)) {
ConfigManager.getInstance().stop();
}
}

@Override
public void onApplicationEvent(@NonNull ContextRefreshedEvent event) {
if (!isManagedContext(event.getApplicationContext())) {
return;
}
if (started.compareAndSet(Boolean.TRUE, Boolean.FALSE)) {
stopTRPC();
if (started.compareAndSet(Boolean.FALSE, Boolean.TRUE)) {
try {
replaceProvidersRef(event.getApplicationContext());
ConfigManager.getInstance().start(false);
} catch (Throwable t) {
// tRPC lifecycle has already stopped itself on start failure, avoid stopping it again
started.set(Boolean.FALSE);
throw t;
}
}
}

/**
* Fallback of stopping tRPC when the context refresh fails after tRPC has started, in which case
* beans are destroyed without publishing {@link org.springframework.context.event.ContextClosedEvent}.
*/
@Override
public void destroy() {
stopIfStarted();
}

/**
* Replace tRPC provider ref to related spring beans.
*/
Expand All @@ -92,16 +133,8 @@ private void doReplaceProviderRef(ApplicationContext applicationContext, Provide
}
}

private void startTRPC() {
ConfigManager.getInstance().start(false);
}

private void stopTRPC() {
ConfigManager.getInstance().stop();
}

@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 20000;
return START_ORDER;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Tencent is pleased to support the open source community by making tRPC available.
*
* Copyright (C) 2023 Tencent.
* All rights reserved.
*
* If you have downloaded a copy of the tRPC source code from Tencent,
* please note that tRPC source code is licensed under the Apache 2.0 License,
* A copy of the Apache 2.0 License can be found in the LICENSE file.
*/

package com.tencent.trpc.spring.context;

import java.util.Objects;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.core.Ordered;
import org.springframework.lang.NonNull;

/**
* Stop tRPC on {@link ContextClosedEvent}.
* <p>Runs early so that tRPC unregisters and drains in-flight requests before other
* {@link ContextClosedEvent} listeners release resources.</p>
*/
public class TRpcShutdownListener implements ApplicationListener<ContextClosedEvent>, Ordered {

/**
* Order of stopping tRPC on {@link ContextClosedEvent}.
*/
public static final int STOP_ORDER = Ordered.HIGHEST_PRECEDENCE + 20000;

private final TRpcLifecycleManager lifecycleManager;

public TRpcShutdownListener(TRpcLifecycleManager lifecycleManager) {
this.lifecycleManager = Objects.requireNonNull(lifecycleManager, "lifecycleManager");
}

@Override
public void onApplicationEvent(@NonNull ContextClosedEvent event) {
if (!lifecycleManager.isManagedContext(event.getApplicationContext())) {
return;
}
lifecycleManager.stopIfStarted();
}

@Override
public int getOrder() {
return STOP_ORDER;
}
}
Loading
Loading