/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.core.remoting.impl.invm;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
import org.apache.activemq.artemis.core.server.ActiveMQComponent;
import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle;
import org.apache.activemq.artemis.spi.core.remoting.AbstractConnector;
import org.apache.activemq.artemis.spi.core.remoting.Acceptor;
import org.apache.activemq.artemis.spi.core.remoting.BaseConnectionLifeCycleListener;
import org.apache.activemq.artemis.spi.core.remoting.BufferHandler;
import org.apache.activemq.artemis.spi.core.remoting.ClientConnectionLifeCycleListener;
import org.apache.activemq.artemis.spi.core.remoting.ClientProtocolManager;
import org.apache.activemq.artemis.spi.core.remoting.Connection;
import org.apache.activemq.artemis.spi.core.remoting.ConnectionLifeCycleListener;
import org.apache.activemq.artemis.utils.ActiveMQThreadFactory;
import org.apache.activemq.artemis.utils.ActiveMQThreadPoolExecutor;
import org.apache.activemq.artemis.utils.ConfigurationHelper;
import org.apache.activemq.artemis.utils.OrderedExecutorFactory;
import org.jboss.logging.Logger;
public class InVMConnector extends AbstractConnector {
private static final Logger logger = Logger.getLogger(InVMConnector.class);
public static final Map<String, Object> DEFAULT_CONFIG;
static {
Map<String, Object> config = new HashMap<>();
config.put(TransportConstants.SERVER_ID_PROP_NAME, TransportConstants.DEFAULT_SERVER_ID);
DEFAULT_CONFIG = Collections.unmodifiableMap(config);
}
// Used for testing failure only
public static volatile boolean failOnCreateConnection;
public static volatile int numberOfFailures = -1;
private static volatile int failures;
public static synchronized void resetFailures() {
InVMConnector.failures = 0;
InVMConnector.failOnCreateConnection = false;
InVMConnector.numberOfFailures = -1;
}
private static synchronized void incFailures() {
InVMConnector.failures++;
if (InVMConnector.failures == InVMConnector.numberOfFailures) {
InVMConnector.resetFailures();
}
}
protected final int id;
private final ClientProtocolManager protocolManager;
private final BufferHandler handler;
private final BaseConnectionLifeCycleListener listener;
private final InVMAcceptor acceptor;
private final ConcurrentMap<String, Connection> connections = new ConcurrentHashMap<>();
private volatile boolean started;
protected final OrderedExecutorFactory executorFactory;
private final Executor closeExecutor;
private final boolean bufferPoolingEnabled;
private static ExecutorService threadPoolExecutor;
public static synchronized void resetThreadPool() {
if (threadPoolExecutor != null) {
threadPoolExecutor.shutdown();
threadPoolExecutor = null;
}
}
private static synchronized ExecutorService getInVMExecutor() {
if (threadPoolExecutor == null) {
if (ActiveMQClient.getGlobalThreadPoolSize() <= -1) {
threadPoolExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), ActiveMQThreadFactory.defaultThreadFactory());
} else {
threadPoolExecutor = new ActiveMQThreadPoolExecutor(0, ActiveMQClient.getGlobalThreadPoolSize(), 60L, TimeUnit.SECONDS, ActiveMQThreadFactory.defaultThreadFactory());
}
}
return threadPoolExecutor;
}
public InVMConnector(final Map<String, Object> configuration,
final BufferHandler handler,
final ClientConnectionLifeCycleListener listener,
final Executor closeExecutor,
final Executor threadPool,
ClientProtocolManager protocolManager) {
super(configuration);
this.listener = listener;
id = ConfigurationHelper.getIntProperty(TransportConstants.SERVER_ID_PROP_NAME, 0, configuration);
bufferPoolingEnabled = ConfigurationHelper.getBooleanProperty(TransportConstants.BUFFER_POOLING, TransportConstants.DEFAULT_BUFFER_POOLING, configuration);
this.handler = handler;
this.closeExecutor = closeExecutor;
executorFactory = new OrderedExecutorFactory(getInVMExecutor());
InVMRegistry registry = InVMRegistry.instance;
acceptor = registry.getAcceptor(id);
this.protocolManager = protocolManager;
}
public Acceptor getAcceptor() {
return acceptor;
}
@Override
public synchronized void close() {
if (!started) {
return;
}
for (Connection connection : connections.values()) {
listener.connectionDestroyed(connection.getID());
}
started = false;
}
@Override
public boolean isStarted() {
return started;
}
@Override
public Connection createConnection() {
if (InVMConnector.failOnCreateConnection) {
InVMConnector.incFailures();
logger.debug("Returning null on InVMConnector for tests");
// For testing only
return null;
}
if (acceptor == null) {
return null;
}
if (acceptor.getConnectionsAllowed() == -1 || acceptor.getConnectionCount() < acceptor.getConnectionsAllowed()) {
Connection conn = internalCreateConnection(acceptor.getHandler(), new Listener(), acceptor.getExecutorFactory().getExecutor());
acceptor.connect((String) conn.getID(), handler, this, executorFactory.getExecutor());
return conn;
} else {
if (logger.isDebugEnabled()) {
logger.debug(new StringBuilder().append("Connection limit of ").append(acceptor.getConnectionsAllowed()).append(" reached. Refusing connection."));
}
return null;
}
}
@Override
public synchronized void start() {
started = true;
}
public BufferHandler getHandler() {
return handler;
}
public void disconnect(final String connectionID) {
if (!started) {
return;
}
Connection conn = connections.get(connectionID);
if (conn != null) {
conn.close();
}
}
// This may be an injection point for mocks on tests
protected Connection internalCreateConnection(final BufferHandler handler,
final ClientConnectionLifeCycleListener listener,
final Executor serverExecutor) {
// No acceptor on a client connection
InVMConnection inVMConnection = new InVMConnection(id, handler, listener, serverExecutor);
inVMConnection.setEnableBufferPooling(bufferPoolingEnabled);
listener.connectionCreated(null, inVMConnection, protocolManager);
return inVMConnection;
}
@Override
public boolean isEquivalent(Map<String, Object> configuration) {
int serverId = ConfigurationHelper.getIntProperty(TransportConstants.SERVER_ID_PROP_NAME, 0, configuration);
return id == serverId;
}
private class Listener implements ClientConnectionLifeCycleListener {
@Override
public void connectionCreated(final ActiveMQComponent component,
final Connection connection,
final ClientProtocolManager protocol) {
if (connections.putIfAbsent((String) connection.getID(), connection) != null) {
throw ActiveMQMessageBundle.BUNDLE.connectionExists(connection.getID());
}
if (listener instanceof ConnectionLifeCycleListener) {
listener.connectionCreated(component, connection, protocol.getName());
} else {
listener.connectionCreated(component, connection, protocol);
}
}
@Override
public void connectionDestroyed(final Object connectionID) {
if (connections.remove(connectionID) != null) {
// Close the corresponding connection on the other side
acceptor.disconnect((String) connectionID);
// Execute on different thread to avoid deadlocks
closeExecutor.execute(new Runnable() {
@Override
public void run() {
listener.connectionDestroyed(connectionID);
}
});
}
}
@Override
public void connectionException(final Object connectionID, final ActiveMQException me) {
// Execute on different thread to avoid deadlocks
closeExecutor.execute(new Runnable() {
@Override
public void run() {
listener.connectionException(connectionID, me);
}
});
}
@Override
public void connectionReadyForWrites(Object connectionID, boolean ready) {
}
}
}