Skip to content

Commit

Permalink
[WFLY-7207] Allow active transaction requests to pass by EjbSuspendIn…
Browse files Browse the repository at this point in the history
…terceptor before server transaction suspension is complete

Server transaction suspension is considered complete after all open/active transactions are finished.
Also:
-  add enabled graceful shutdown attribute to ejb3 subsystem model, and add corresponding element in subsystem xml. Default value is false
- update testsuite with new tests for graceful ejb txn shutdown
  • Loading branch information
fl4via committed Jan 24, 2017
1 parent e11bc80 commit 32afade
Show file tree
Hide file tree
Showing 31 changed files with 890 additions and 118 deletions.
14 changes: 10 additions & 4 deletions ejb3/src/main/java/org/jboss/as/ejb3/component/EJBComponent.java
Expand Up @@ -28,6 +28,9 @@
import java.security.PrivilegedExceptionAction;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;

import javax.ejb.EJBHome;
import javax.ejb.EJBLocalHome;
Expand All @@ -43,10 +46,6 @@
import javax.transaction.TransactionSynchronizationRegistry;
import javax.transaction.UserTransaction;

import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;

import org.jboss.as.core.security.ServerSecurityManager;
import org.jboss.as.ee.component.BasicComponent;
import org.jboss.as.ee.component.ComponentView;
Expand All @@ -57,6 +56,7 @@
import org.jboss.as.ejb3.logging.EjbLogger;
import org.jboss.as.ejb3.remote.EJBRemoteTransactionsRepository;
import org.jboss.as.ejb3.security.EJBSecurityMetaData;
import org.jboss.as.ejb3.suspend.EJBSuspendHandlerService;
import org.jboss.as.ejb3.timerservice.TimerServiceImpl;
import org.jboss.as.ejb3.tx.ApplicationExceptionDetails;
import org.jboss.as.naming.ManagedReference;
Expand Down Expand Up @@ -106,6 +106,7 @@ public abstract class EJBComponent extends BasicComponent implements ServerActiv
private final String distinctName;
private final String policyContextID;
private final EJBRemoteTransactionsRepository ejbRemoteTransactionsRepository;
private final EJBSuspendHandlerService ejbSuspendHandlerService;

private final InvocationMetrics invocationMetrics = new InvocationMetrics();
private final ShutDownInterceptorFactory shutDownInterceptorFactory;
Expand Down Expand Up @@ -181,6 +182,7 @@ protected EJBComponent(final EJBComponentCreateService ejbComponentCreateService
this.securityDomain = ejbComponentCreateService.getSecurityDomain();
this.incomingRunAsIdentity = null;
this.identityOutflowFunction = ejbComponentCreateService.getIdentityOutflowFunction();
this.ejbSuspendHandlerService = ejbComponentCreateService.getEJBSuspendHandler();
}

protected <T> T createViewInstanceProxy(final Class<T> viewInterface, final Map<Object, Object> contextData) {
Expand Down Expand Up @@ -553,6 +555,10 @@ public EJBRemoteTransactionsRepository getEjbRemoteTransactionsRepository() {
return this.ejbRemoteTransactionsRepository;
}

public EJBSuspendHandlerService getEjbSuspendHandlerService() {
return this.ejbSuspendHandlerService;
}

public AllowedMethodsInformation getAllowedMethodsInformation() {
return isBeanManagedTransaction() ? AllowedMethodsInformation.INSTANCE_BMT : AllowedMethodsInformation.INSTANCE_CMT;
}
Expand Down
Expand Up @@ -53,6 +53,7 @@
import org.jboss.as.ejb3.security.EJBSecurityMetaData;
import org.jboss.as.ejb3.subsystem.ApplicationSecurityDomainService.ApplicationSecurityDomain;
import org.jboss.as.ejb3.subsystem.ApplicationSecurityDomainService.Registration;
import org.jboss.as.ejb3.suspend.EJBSuspendHandlerService;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.invocation.InterceptorFactory;
import org.jboss.invocation.Interceptors;
Expand Down Expand Up @@ -111,6 +112,7 @@ public class EJBComponentCreateService extends BasicComponentCreateService {
private final InjectedValue<AtomicBoolean> exceptionLoggingEnabled = new InjectedValue<>();
private final InjectedValue<ApplicationSecurityDomain> applicationSecurityDomain = new InjectedValue<>();
private final InjectedValue<Function> identityOutflowFunction = new InjectedValue<>();
private final InjectedValue<EJBSuspendHandlerService> ejbSuspendHandler = new InjectedValue<>();

private final ShutDownInterceptorFactory shutDownInterceptorFactory;

Expand Down Expand Up @@ -363,6 +365,14 @@ EJBRemoteTransactionsRepository getEJBRemoteTransactionsRepository() {
return this.ejbRemoteTransactionsRepository.getOptionalValue();
}

public Injector<EJBSuspendHandlerService> getEJBSuspendHandlerInjector() {
return this.ejbSuspendHandler;
}

EJBSuspendHandlerService getEJBSuspendHandler() {
return this.ejbSuspendHandler.getValue();
}

Injector<TransactionManager> getTransactionManagerInjector() {
return this.transactionManagerInjectedValue;
}
Expand Down
Expand Up @@ -96,6 +96,7 @@
import org.jboss.as.ejb3.security.SecurityDomainInterceptorFactory;
import org.jboss.as.ejb3.subsystem.ApplicationSecurityDomainDefinition;
import org.jboss.as.ejb3.subsystem.ApplicationSecurityDomainService.ApplicationSecurityDomain;
import org.jboss.as.ejb3.suspend.EJBSuspendHandlerService;
import org.jboss.as.ejb3.timerservice.AutoTimer;
import org.jboss.as.ejb3.timerservice.NonFunctionalTimerService;
import org.jboss.as.security.deployment.SecurityAttachments;
Expand Down Expand Up @@ -382,6 +383,9 @@ public void configureDependency(ServiceBuilder<?> serviceBuilder, EJBComponentCr
// setup dependencies on the transaction manager services
addTransactionManagerDependencies();

// setup ejb suspend handler dependency
addEJBSuspendHandlerDependency();

// setup dependency on ServerSecurityManager
addServerSecurityManagerDependency();
}
Expand Down Expand Up @@ -585,6 +589,25 @@ public void configureDependency(final ServiceBuilder<?> serviceBuilder, final EJ
});
}

/**
* Sets up a {@link ComponentConfigurator} which then sets up the dependency on the EJBSuspendHandlerService service for the {@link EJBComponentCreateService}
*/
protected void addEJBSuspendHandlerDependency() {
getConfigurators().add(new ComponentConfigurator() {
@Override
public void configure(final DeploymentPhaseContext context, final ComponentDescription description, final ComponentConfiguration componentConfiguration) throws DeploymentUnitProcessingException {
componentConfiguration.getCreateDependencies().add(new DependencyConfigurator<EJBComponentCreateService>() {
@Override public void configureDependency(final ServiceBuilder<?> serviceBuilder, final EJBComponentCreateService ejbComponentCreateService)
throws DeploymentUnitProcessingException {
serviceBuilder.addDependency(EJBSuspendHandlerService.SERVICE_NAME, EJBSuspendHandlerService.class,
ejbComponentCreateService.getEJBSuspendHandlerInjector());
}
});
}
});
}


/**
* Sets up a {@link ComponentConfigurator} which then sets up the dependency on the ServerSecurityManager service for the {@link EJBComponentCreateService}
*/
Expand Down
Expand Up @@ -34,6 +34,7 @@
* An interceptor that allows the component to shutdown gracefully.
*
* @author Stuart Douglas
* @author Flavia Rainone
*/
public class EjbSuspendInterceptor extends AbstractEJBInterceptor {

Expand All @@ -43,16 +44,22 @@ public Object processInvocation(InterceptorContext context) throws Exception {
if (invocation != InvocationType.REMOTE && invocation != InvocationType.MESSAGE_DELIVERY) {
return context.proceed();
}
// see if control point accepts or rejects this invocation
EJBComponent component = getComponent(context, EJBComponent.class);
ControlPoint entryPoint = component.getControlPoint();
RunResult result = entryPoint.beginRequest();
if (result == RunResult.REJECTED) {
throw EjbLogger.ROOT_LOGGER.containerSuspended();
// if control point rejected, check with suspend handler
if (!component.getEjbSuspendHandlerService().acceptInvocation(context))
throw EjbLogger.ROOT_LOGGER.containerSuspended();
}
try {
return context.proceed();
} finally {
entryPoint.requestComplete();
if (result == RunResult.REJECTED)
component.getEjbSuspendHandlerService().invocationComplete();
else
entryPoint.requestComplete();
}
}
}
7 changes: 7 additions & 0 deletions ejb3/src/main/java/org/jboss/as/ejb3/logging/EjbLogger.java
Expand Up @@ -60,6 +60,7 @@
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.transaction.RollbackException;
import javax.transaction.Transaction;
import javax.transaction.xa.XAException;
import javax.transaction.xa.Xid;
import javax.xml.stream.Location;
import javax.xml.stream.XMLStreamException;
Expand Down Expand Up @@ -3127,4 +3128,10 @@ public interface EjbLogger extends BasicLogger {
@Message(id = 488, value = "Unauthenticated (anonymous) access to this EJB method is not authorized")
SecurityException ejbAuthenticationRequired();

@Message(id = 489, value = "The transaction begin request was rejected as the container is suspended")
EJBException cannotBeginUserTransaction();

@LogMessage(level = WARN)
@Message(id = 490, value = "Unexpected XAException")
void unexpectedXAException(@Cause XAException exception);
}
Expand Up @@ -38,9 +38,9 @@
import org.jboss.as.ejb3.remote.protocol.versionone.ChannelAssociation;
import org.jboss.as.ejb3.remote.protocol.versionone.VersionOneProtocolChannelReceiver;
import org.jboss.as.ejb3.remote.protocol.versiontwo.VersionTwoProtocolChannelReceiver;
import org.jboss.as.ejb3.suspend.EJBSuspendHandlerService;
import org.jboss.as.network.ClientMapping;
import org.jboss.as.remoting.RemotingConnectorBindingInfoService;
import org.jboss.as.server.suspend.SuspendController;
import org.jboss.ejb.client.ConstantContextSelector;
import org.jboss.ejb.client.EJBClientTransactionContext;
import org.jboss.ejb.client.remoting.PackedInteger;
Expand Down Expand Up @@ -83,7 +83,7 @@ public class EJBRemoteConnectorService implements Service<EJBRemoteConnectorServ
private final InjectedValue<TransactionManager> txManager = new InjectedValue<TransactionManager>();
private final InjectedValue<TransactionSynchronizationRegistry> txSyncRegistry = new InjectedValue<TransactionSynchronizationRegistry>();
private final InjectedValue<RemotingConnectorBindingInfoService.RemotingConnectorInfo> remotingConnectorInfoInjectedValue = new InjectedValue<>();
private final InjectedValue<SuspendController> suspendControllerInjectedValue = new InjectedValue<>();
private final InjectedValue<EJBSuspendHandlerService> ejbSuspendHandlerInjectedValue = new InjectedValue<>();
private volatile Registration registration;
private final byte serverProtocolVersion;
private final String[] supportedMarshallingStrategies;
Expand Down Expand Up @@ -145,6 +145,10 @@ public Injector<TransactionSynchronizationRegistry> getTxSyncRegistryInjector()
return this.txSyncRegistry;
}

public Injector<EJBSuspendHandlerService> getEjbSuspendHandlerInjector() {
return this.ejbSuspendHandlerInjectedValue;
}

public List<EjbListenerAddress> getListeningAddresses() {
final RemotingConnectorBindingInfoService.RemotingConnectorInfo info = remotingConnectorInfoInjectedValue.getValue();
return Collections.singletonList(new EjbListenerAddress(info.getSocketBinding().getSocketAddress(), info.getProtocol()));
Expand Down Expand Up @@ -251,20 +255,20 @@ public void handleMessage(Channel channel, MessageInputStream messageInputStream
final DeploymentRepository deploymentRepository = EJBRemoteConnectorService.this.deploymentRepositoryInjectedValue.getValue();
final RegistryCollector<String, List<ClientMapping>> clientMappingRegistryCollector = EJBRemoteConnectorService.this.clusterRegistryCollector.getValue();
final RemoteAsyncInvocationCancelStatusService asyncInvocationCancelStatus = EJBRemoteConnectorService.this.remoteAsyncInvocationCancelStatus.getValue();
final SuspendController suspendController = EJBRemoteConnectorService.this.suspendControllerInjectedValue.getValue();
final EJBSuspendHandlerService suspendHandler = EJBRemoteConnectorService.this.ejbSuspendHandlerInjectedValue.getValue();

switch (version) {
case 0x01:
final VersionOneProtocolChannelReceiver versionOneProtocolHandler = new VersionOneProtocolChannelReceiver(this.channelAssociation, deploymentRepository,
EJBRemoteConnectorService.this.ejbRemoteTransactionsRepositoryInjectedValue.getValue(), clientMappingRegistryCollector,
marshallerFactory, executorService.getOptionalValue(), asyncInvocationCancelStatus, suspendController);
marshallerFactory, executorService.getOptionalValue(), asyncInvocationCancelStatus, suspendHandler);
// trigger the receiving
versionOneProtocolHandler.startReceiving();
break;
case 0x02:
final VersionTwoProtocolChannelReceiver versionTwoProtocolHandler = new VersionTwoProtocolChannelReceiver(this.channelAssociation, deploymentRepository,
EJBRemoteConnectorService.this.ejbRemoteTransactionsRepositoryInjectedValue.getValue(), clientMappingRegistryCollector,
marshallerFactory, executorService.getOptionalValue(), asyncInvocationCancelStatus, suspendController);
marshallerFactory, executorService.getOptionalValue(), asyncInvocationCancelStatus, suspendHandler);
// trigger the receiving
versionTwoProtocolHandler.startReceiving();
break;
Expand Down Expand Up @@ -309,10 +313,6 @@ public InjectedValue<RemotingConnectorBindingInfoService.RemotingConnectorInfo>
return remotingConnectorInfoInjectedValue;
}

public InjectedValue<SuspendController> getSuspendControllerInjectedValue() {
return suspendControllerInjectedValue;
}

private boolean isSupportedMarshallingStrategy(final String strategy) {
return Arrays.asList(this.supportedMarshallingStrategies).contains(strategy);
}
Expand Down

0 comments on commit 32afade

Please sign in to comment.