added RestartStrategies to be able to swap the context recreation strategy

This commit is contained in:
Sebastian Sdorra
2019-06-19 11:53:58 +02:00
parent f747be4331
commit 33702e0e9d
4 changed files with 219 additions and 45 deletions

View File

@@ -37,7 +37,6 @@ import com.github.legman.Subscribe;
import com.google.inject.servlet.GuiceFilter; import com.google.inject.servlet.GuiceFilter;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.event.RecreateEventBusEvent;
import sonia.scm.event.ScmEventBus; import sonia.scm.event.ScmEventBus;
import javax.servlet.FilterConfig; import javax.servlet.FilterConfig;
@@ -50,63 +49,29 @@ import javax.servlet.ServletException;
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
public class BootstrapContextFilter extends GuiceFilter public class BootstrapContextFilter extends GuiceFilter {
{
/** /**
* the logger for BootstrapContextFilter * the logger for BootstrapContextFilter
*/ */
private static final Logger logger = private static final Logger LOG = LoggerFactory.getLogger(BootstrapContextFilter.class);
LoggerFactory.getLogger(BootstrapContextFilter.class);
//~--- methods --------------------------------------------------------------
private final BootstrapContextListener listener = new BootstrapContextListener(); private final BootstrapContextListener listener = new BootstrapContextListener();
/** /** Field description */
* Restart the whole webapp context. private FilterConfig filterConfig;
*
*
* @param event restart event
*
* @throws ServletException
*/
@Subscribe
public void handleRestartEvent(RestartEvent event) throws ServletException
{
logger.warn("received restart event from {} with reason: {}",
event.getCause(), event.getReason());
if (filterConfig == null)
{
logger.error("filter config is null, scm-manager is not initialized");
}
else
{
logger.warn("destroy filter pipeline, because of a received restart event");
destroy();
logger.warn("send recreate eventbus event");
ScmEventBus.getInstance().post(new RecreateEventBusEvent());
ScmEventBus.getInstance().register(this);
logger.warn("reinitialize filter pipeline, because of a received restart event");
initGuice();
}
}
@Override @Override
public void init(FilterConfig filterConfig) throws ServletException public void init(FilterConfig filterConfig) throws ServletException {
{
this.filterConfig = filterConfig; this.filterConfig = filterConfig;
initGuice(); initGuice();
logger.info("register for restart events"); LOG.info("register for restart events");
ScmEventBus.getInstance().register(this); ScmEventBus.getInstance().register(this);
} }
public void initGuice() throws ServletException { private void initGuice() throws ServletException {
super.init(filterConfig); super.init(filterConfig);
listener.contextInitialized(new ServletContextEvent(filterConfig.getServletContext())); listener.contextInitialized(new ServletContextEvent(filterConfig.getServletContext()));
@@ -119,8 +84,39 @@ public class BootstrapContextFilter extends GuiceFilter
ServletContextCleaner.cleanup(filterConfig.getServletContext()); ServletContextCleaner.cleanup(filterConfig.getServletContext());
} }
//~--- fields --------------------------------------------------------------- /**
* Restart SCM-Manager.
*
* @param event restart event
*/
@Subscribe
public void handleRestartEvent(RestartEvent event) {
LOG.warn("received restart event from {} with reason: {}",
event.getCause(), event.getReason());
if (filterConfig == null) {
LOG.error("filter config is null, scm-manager is not initialized");
} else {
RestartStrategy restartStrategy = RestartStrategy.get();
restartStrategy.restart(new GuiceInjectionContext());
}
}
private class GuiceInjectionContext implements RestartStrategy.InjectionContext {
@Override
public void initialize() {
try {
BootstrapContextFilter.this.initGuice();
} catch (ServletException e) {
throw new IllegalStateException("failed to initialize guice", e);
}
}
@Override
public void destroy() {
BootstrapContextFilter.this.destroy();
}
}
/** Field description */
private FilterConfig filterConfig;
} }

View File

@@ -0,0 +1,50 @@
package sonia.scm.boot;
import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.event.RecreateEventBusEvent;
import sonia.scm.event.ScmEventBus;
import java.util.concurrent.atomic.AtomicLong;
/**
* Restart strategy implementation which destroy the injection context and re initialize it.
*/
public class InjectionContextRestartStrategy implements RestartStrategy {
private static final AtomicLong INSTANCE_COUNTER = new AtomicLong();
private static final Logger LOG = LoggerFactory.getLogger(InjectionContextRestartStrategy.class);
private long waitInMs = 250L;
@VisibleForTesting
void setWaitInMs(long waitInMs) {
this.waitInMs = waitInMs;
}
@Override
public void restart(InjectionContext context) {
LOG.warn("destroy injection context");
context.destroy();
LOG.warn("send recreate eventbus event");
ScmEventBus.getInstance().post(new RecreateEventBusEvent());
// restart context delayed, to avoid timing problems
new Thread(() -> {
try {
Thread.sleep(waitInMs);
LOG.warn("reinitialize injection context");
context.initialize();
LOG.debug("re register injection context for events");
ScmEventBus.getInstance().register(context);
} catch ( Exception ex) {
LOG.error("failed to restart", ex);
}
}, "Delayed-Restart-" + INSTANCE_COUNTER.incrementAndGet()).start();
}
}

View File

@@ -0,0 +1,38 @@
package sonia.scm.boot;
/**
* Strategy for restarting SCM-Manager.
*/
public interface RestartStrategy {
/**
* Context for Injection in SCM-Manager.
*/
interface InjectionContext {
/**
* Initialize the injection context.
*/
void initialize();
/**
* Destroys the injection context.
*/
void destroy();
}
/**
* Restart SCM-Manager.
* @param context injection context
*/
void restart(InjectionContext context);
/**
* Returns the configured strategy.
*
* @return configured strategy
*/
static RestartStrategy get() {
return new InjectionContextRestartStrategy();
}
}

View File

@@ -0,0 +1,90 @@
package sonia.scm.boot;
import com.github.legman.Subscribe;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.event.RecreateEventBusEvent;
import sonia.scm.event.ScmEventBus;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
class InjectionContextRestartStrategyTest {
@Mock
private RestartStrategy.InjectionContext context;
private InjectionContextRestartStrategy strategy = new InjectionContextRestartStrategy();
@BeforeEach
void setWaitToZero() {
strategy.setWaitInMs(0L);
}
@Test
void shouldCallDestroyAndInitialize() throws InterruptedException {
strategy.restart(context);
verify(context).destroy();
Thread.sleep(50L);
verify(context).initialize();
}
@Test
void shouldFireRecreateEventBusEvent() {
Listener listener = new Listener();
ScmEventBus.getInstance().register(listener);
strategy.restart(context);
assertThat(listener.event).isNotNull();
}
@Test
void shouldRegisterContextAfterRestart() throws InterruptedException {
TestingInjectionContext ctx = new TestingInjectionContext();
strategy.restart(ctx);
Thread.sleep(50L);
ScmEventBus.getInstance().post("hello event");
assertThat(ctx.event).isEqualTo("hello event");
}
public static class Listener {
private RecreateEventBusEvent event;
@Subscribe(async = false)
public void setEvent(RecreateEventBusEvent event) {
this.event = event;
}
}
public static class TestingInjectionContext implements RestartStrategy.InjectionContext {
private volatile String event;
@Subscribe(async = false)
public void setEvent(String event) {
this.event = event;
}
@Override
public void initialize() {
}
@Override
public void destroy() {
}
}
}