org.elasticsearch.transport.local.LocalTransport Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of elasticsearch Show documentation
Show all versions of elasticsearch Show documentation
Elasticsearch - Open Source, Distributed, RESTful Search Engine
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch 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.elasticsearch.transport.local;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.util.Supplier;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.CheckedBiConsumer;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.breaker.CircuitBreaker;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.component.Lifecycle;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.BoundTransportAddress;
import org.elasticsearch.common.transport.LocalTransportAddress;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.common.util.concurrent.AbstractRunnable;
import org.elasticsearch.common.util.concurrent.EsExecutors;
import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.indices.breaker.CircuitBreakerService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.ActionNotFoundTransportException;
import org.elasticsearch.transport.ConnectTransportException;
import org.elasticsearch.transport.ConnectionProfile;
import org.elasticsearch.transport.NodeNotConnectedException;
import org.elasticsearch.transport.RemoteTransportException;
import org.elasticsearch.transport.RequestHandlerRegistry;
import org.elasticsearch.transport.ResponseHandlerFailureTransportException;
import org.elasticsearch.transport.Transport;
import org.elasticsearch.transport.TransportException;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.transport.TransportRequestOptions;
import org.elasticsearch.transport.TransportResponse;
import org.elasticsearch.transport.TransportResponseHandler;
import org.elasticsearch.transport.TransportSerializationException;
import org.elasticsearch.transport.TransportServiceAdapter;
import org.elasticsearch.transport.TransportStatus;
import org.elasticsearch.transport.Transports;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import static org.elasticsearch.common.util.concurrent.ConcurrentCollections.newConcurrentMap;
public class LocalTransport extends AbstractLifecycleComponent implements Transport {
public static final String LOCAL_TRANSPORT_THREAD_NAME_PREFIX = "local_transport";
final ThreadPool threadPool;
private final ThreadPoolExecutor workers;
private volatile TransportServiceAdapter transportServiceAdapter;
private volatile BoundTransportAddress boundAddress;
private volatile LocalTransportAddress localAddress;
private static final ConcurrentMap transports = newConcurrentMap();
private static final AtomicLong transportAddressIdGenerator = new AtomicLong();
private final ConcurrentMap connectedNodes = newConcurrentMap();
protected final NamedWriteableRegistry namedWriteableRegistry;
private final CircuitBreakerService circuitBreakerService;
private final AtomicLong requestIdGenerator = new AtomicLong();
public static final String TRANSPORT_LOCAL_ADDRESS = "transport.local.address";
public static final String TRANSPORT_LOCAL_WORKERS = "transport.local.workers";
public static final String TRANSPORT_LOCAL_QUEUE = "transport.local.queue";
public LocalTransport(Settings settings, ThreadPool threadPool,
NamedWriteableRegistry namedWriteableRegistry, CircuitBreakerService circuitBreakerService) {
super(settings);
this.threadPool = threadPool;
int workerCount = this.settings.getAsInt(TRANSPORT_LOCAL_WORKERS, EsExecutors.boundedNumberOfProcessors(settings));
int queueSize = this.settings.getAsInt(TRANSPORT_LOCAL_QUEUE, -1);
logger.debug("creating [{}] workers, queue_size [{}]", workerCount, queueSize);
final ThreadFactory threadFactory = EsExecutors.daemonThreadFactory(this.settings, LOCAL_TRANSPORT_THREAD_NAME_PREFIX);
this.workers = EsExecutors.newFixed(LOCAL_TRANSPORT_THREAD_NAME_PREFIX, workerCount, queueSize, threadFactory,
threadPool.getThreadContext());
this.namedWriteableRegistry = namedWriteableRegistry;
this.circuitBreakerService = circuitBreakerService;
}
@Override
public TransportAddress[] addressesFromString(String address, int perAddressLimit) {
return new TransportAddress[]{new LocalTransportAddress(address)};
}
@Override
public boolean addressSupported(Class extends TransportAddress> address) {
return LocalTransportAddress.class.equals(address);
}
@Override
protected void doStart() {
String address = settings.get(TRANSPORT_LOCAL_ADDRESS);
if (address == null) {
address = Long.toString(transportAddressIdGenerator.incrementAndGet());
}
localAddress = new LocalTransportAddress(address);
LocalTransport previous = transports.put(localAddress, this);
if (previous != null) {
throw new ElasticsearchException("local address [" + address + "] is already bound");
}
boundAddress = new BoundTransportAddress(new TransportAddress[] { localAddress }, localAddress);
}
@Override
protected void doStop() {
transports.remove(localAddress);
// now, go over all the transports connected to me, and raise disconnected event
for (final LocalTransport targetTransport : transports.values()) {
for (final Map.Entry entry : targetTransport.connectedNodes.entrySet()) {
if (entry.getValue() == this) {
targetTransport.disconnectFromNode(entry.getKey());
}
}
}
}
@Override
protected void doClose() {
ThreadPool.terminate(workers, 10, TimeUnit.SECONDS);
}
@Override
public void transportServiceAdapter(TransportServiceAdapter transportServiceAdapter) {
this.transportServiceAdapter = transportServiceAdapter;
}
@Override
public BoundTransportAddress boundAddress() {
return boundAddress;
}
@Override
public Map profileBoundAddresses() {
return Collections.emptyMap();
}
@Override
public boolean nodeConnected(DiscoveryNode node) {
return connectedNodes.containsKey(node);
}
@Override
public void connectToNode(DiscoveryNode node, ConnectionProfile connectionProfile,
CheckedBiConsumer connectionValidator)
throws ConnectTransportException {
synchronized (this) {
if (connectedNodes.containsKey(node)) {
return;
}
final LocalTransport targetTransport = transports.get(node.getAddress());
if (targetTransport == null) {
throw new ConnectTransportException(node, "Failed to connect");
}
connectedNodes.put(node, targetTransport);
transportServiceAdapter.onNodeConnected(node);
}
}
@Override
public void disconnectFromNode(DiscoveryNode node) {
synchronized (this) {
LocalTransport removed = connectedNodes.remove(node);
if (removed != null) {
transportServiceAdapter.onNodeDisconnected(node);
// here we notify that the connection to the node is closed -- use the remove transport as cache key
transportServiceAdapter.onConnectionClosed(getConnectionForTransport(removed, node, true));
}
}
}
@Override
public long serverOpen() {
return 0;
}
@Override
public Connection getConnection(DiscoveryNode node) {
final LocalTransport targetTransport = connectedNodes.get(node);
if (targetTransport == null) {
throw new NodeNotConnectedException(node, "Node not connected");
}
return getConnectionForTransport(targetTransport, node, true);
}
@Override
public Connection openConnection(DiscoveryNode node, ConnectionProfile profile) throws IOException {
final LocalTransport targetTransport = transports.get(node.getAddress());
if (targetTransport == null) {
throw new ConnectTransportException(node, "Failed to connect");
}
return getConnectionForTransport(targetTransport, node, false);
}
private Connection getConnectionForTransport(LocalTransport targetTransport, DiscoveryNode node, boolean registered) {
return new Connection() {
@Override
public DiscoveryNode getNode() {
return node;
}
@Override
public void sendRequest(long requestId, String action, TransportRequest request, TransportRequestOptions options)
throws IOException, TransportException {
if (transports.get(node.getAddress()) != targetTransport) {
// we double check that we are still connected since due to the nature of local transport we won't get any errors
// when we send to a closed / disconnected transport. Yet, if the target transport is closed concurrently before
// the transport service registers the client handler local transport will simply ignore the request which can cause
// requests to hang. By checking again here we guarantee to throw an exception which causes the client handler to run
// in such a situation, if the client handler is not registered yet when the connections is obtained.
throw new NodeNotConnectedException(node, " got disconnected");
}
LocalTransport.this.sendRequest(targetTransport, node, requestId, action, request, options);
}
@Override
public Object getCacheKey() {
// the cache key depends if it's registered connection or a single connection (not shared)
// we use the actual LocalTransport if it's a regsitered connection otherwise we use the identity
return registered ? targetTransport : this;
}
@Override
public void close() throws IOException {
transportServiceAdapter.onConnectionClosed(this);
LocalTransport.this.disconnectFromNode(node);
}
};
}
protected void sendRequest(LocalTransport targetTransport, final DiscoveryNode node, final long requestId, final String action,
final TransportRequest request,
TransportRequestOptions options) throws IOException, TransportException {
final Version version = Version.min(node.getVersion(), getVersion());
try (BytesStreamOutput stream = new BytesStreamOutput()) {
stream.setVersion(version);
stream.writeLong(requestId);
byte status = 0;
status = TransportStatus.setRequest(status);
stream.writeByte(status); // 0 for request, 1 for response.
threadPool.getThreadContext().writeTo(stream);
stream.writeString(action);
request.writeTo(stream);
stream.close();
final byte[] data = BytesReference.toBytes(stream.bytes());
transportServiceAdapter.addBytesSent(data.length);
transportServiceAdapter.onRequestSent(node, requestId, action, request, options);
targetTransport.receiveMessage(version, data, action, requestId, this);
}
}
/**
* entry point for incoming messages
*
* @param version the version used to serialize the message
* @param data message data
* @param action the action associated with this message (only used for error handling when data is not parsable)
* @param requestId requestId if the message is request (only used for error handling when data is not parsable)
* @param sourceTransport the source transport to respond to.
*/
public void receiveMessage(Version version, byte[] data, String action, @Nullable Long requestId, LocalTransport sourceTransport) {
try {
workers().execute(() -> {
ThreadContext threadContext = threadPool.getThreadContext();
try (ThreadContext.StoredContext context = threadContext.stashContext()) {
processReceivedMessage(data, action, sourceTransport, version, requestId);
}
});
} catch (EsRejectedExecutionException e) {
assert lifecycle.started() == false;
logger.trace("received request but shutting down. ignoring. action [{}], request id [{}]", action, requestId);
}
}
ThreadPoolExecutor workers() {
return this.workers;
}
CircuitBreaker inFlightRequestsBreaker() {
// We always obtain a fresh breaker to reflect changes to the breaker configuration.
return circuitBreakerService.getBreaker(CircuitBreaker.IN_FLIGHT_REQUESTS);
}
/** processes received messages, assuming thread passing and thread context have all been dealt with */
protected void processReceivedMessage(byte[] data, String action, LocalTransport sourceTransport, Version version,
@Nullable final Long sendRequestId) {
Transports.assertTransportThread();
try {
transportServiceAdapter.addBytesReceived(data.length);
StreamInput stream = StreamInput.wrap(data);
stream.setVersion(version);
long requestId = stream.readLong();
byte status = stream.readByte();
boolean isRequest = TransportStatus.isRequest(status);
threadPool.getThreadContext().readHeaders(stream);
if (isRequest) {
handleRequest(stream, requestId, data.length, sourceTransport, version);
} else {
final TransportResponseHandler handler = transportServiceAdapter.onResponseReceived(requestId);
// ignore if its null, the adapter logs it
if (handler != null) {
if (TransportStatus.isError(status)) {
handleResponseError(stream, handler);
} else {
handleResponse(stream, sourceTransport, handler);
}
}
}
} catch (Exception e) {
if (sendRequestId != null) {
TransportResponseHandler handler = sourceTransport.transportServiceAdapter.onResponseReceived(sendRequestId);
if (handler != null) {
RemoteTransportException error = new RemoteTransportException(nodeName(), localAddress, action, e);
sourceTransport.workers().execute(() -> {
ThreadContext threadContext = sourceTransport.threadPool.getThreadContext();
try (ThreadContext.StoredContext ignore = threadContext.stashContext()) {
sourceTransport.handleException(handler, error);
}
});
}
} else {
logger.warn((Supplier>) () -> new ParameterizedMessage("Failed to receive message for action [{}]", action), e);
}
}
}
private void handleRequest(StreamInput stream, long requestId, int messageLengthBytes, LocalTransport sourceTransport,
Version version) throws Exception {
stream = new NamedWriteableAwareStreamInput(stream, namedWriteableRegistry);
final String action = stream.readString();
final RequestHandlerRegistry reg = transportServiceAdapter.getRequestHandler(action);
transportServiceAdapter.onRequestReceived(requestId, action);
if (reg != null && reg.canTripCircuitBreaker()) {
inFlightRequestsBreaker().addEstimateBytesAndMaybeBreak(messageLengthBytes, "");
} else {
inFlightRequestsBreaker().addWithoutBreaking(messageLengthBytes);
}
final LocalTransportChannel transportChannel = new LocalTransportChannel(this, transportServiceAdapter, sourceTransport, action,
requestId, version, messageLengthBytes, threadPool.getThreadContext());
try {
if (reg == null) {
throw new ActionNotFoundTransportException("Action [" + action + "] not found");
}
final TransportRequest request = reg.newRequest();
request.remoteAddress(sourceTransport.boundAddress.publishAddress());
request.readFrom(stream);
if (ThreadPool.Names.SAME.equals(reg.getExecutor())) {
//noinspection unchecked
reg.processMessageReceived(request, transportChannel);
} else {
threadPool.executor(reg.getExecutor()).execute(new AbstractRunnable() {
@Override
protected void doRun() throws Exception {
//noinspection unchecked
reg.processMessageReceived(request, transportChannel);
}
@Override
public boolean isForceExecution() {
return reg.isForceExecution();
}
@Override
public void onFailure(Exception e) {
if (lifecycleState() == Lifecycle.State.STARTED) {
// we can only send a response transport is started....
try {
transportChannel.sendResponse(e);
} catch (Exception inner) {
inner.addSuppressed(e);
logger.warn(
(Supplier>) () -> new ParameterizedMessage(
"Failed to send error message back to client for action [{}]", action), inner);
}
}
}
});
}
} catch (Exception e) {
try {
transportChannel.sendResponse(e);
} catch (Exception inner) {
inner.addSuppressed(e);
logger.warn(
(Supplier>) () -> new ParameterizedMessage(
"Failed to send error message back to client for action [{}]", action), inner);
}
}
}
protected void handleResponse(StreamInput buffer, LocalTransport sourceTransport, final TransportResponseHandler handler) {
buffer = new NamedWriteableAwareStreamInput(buffer, namedWriteableRegistry);
final TransportResponse response = handler.newInstance();
response.remoteAddress(sourceTransport.boundAddress.publishAddress());
try {
response.readFrom(buffer);
} catch (Exception e) {
handleException(handler, new TransportSerializationException(
"Failed to deserialize response of type [" + response.getClass().getName() + "]", e));
return;
}
handleParsedResponse(response, handler);
}
protected void handleParsedResponse(final TransportResponse response, final TransportResponseHandler handler) {
threadPool.executor(handler.executor()).execute(() -> {
try {
handler.handleResponse(response);
} catch (Exception e) {
handleException(handler, new ResponseHandlerFailureTransportException(e));
}
});
}
private void handleResponseError(StreamInput buffer, final TransportResponseHandler handler) {
Exception exception;
try {
exception = buffer.readException();
} catch (Exception e) {
exception = new TransportSerializationException("Failed to deserialize exception response from stream", e);
}
handleException(handler, exception);
}
private void handleException(final TransportResponseHandler handler, Exception exception) {
if (!(exception instanceof RemoteTransportException)) {
exception = new RemoteTransportException("Not a remote transport exception", null, null, exception);
}
final RemoteTransportException rtx = (RemoteTransportException) exception;
try {
handler.handleException(rtx);
} catch (Exception e) {
logger.error((Supplier>) () -> new ParameterizedMessage("failed to handle exception response [{}]", handler), e);
}
}
@Override
public List getLocalAddresses() {
return Collections.singletonList("0.0.0.0");
}
@Override
public long newRequestId() {
return requestIdGenerator.incrementAndGet();
}
protected Version getVersion() { // for tests
return Version.CURRENT;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy