Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.datastax.oss.driver.internal.core.channel.DriverChannel Maven / Gradle / Ivy
/*
* 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 com.datastax.oss.driver.internal.core.channel;
import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.ProtocolVersion;
import com.datastax.oss.driver.api.core.connection.BusyConnectionException;
import com.datastax.oss.driver.api.core.metadata.EndPoint;
import com.datastax.oss.driver.api.core.metadata.Node;
import com.datastax.oss.driver.internal.core.adminrequest.AdminRequestHandler;
import com.datastax.oss.driver.internal.core.adminrequest.ThrottledAdminRequestHandler;
import com.datastax.oss.driver.internal.core.pool.ChannelPool;
import com.datastax.oss.driver.internal.core.session.DefaultSession;
import com.datastax.oss.driver.internal.core.util.concurrent.UncaughtExceptions;
import com.datastax.oss.protocol.internal.Message;
import com.datastax.oss.driver.shaded.netty.channel.Channel;
import com.datastax.oss.driver.shaded.netty.channel.ChannelConfig;
import com.datastax.oss.driver.shaded.netty.channel.ChannelFuture;
import com.datastax.oss.driver.shaded.netty.channel.EventLoop;
import com.datastax.oss.driver.shaded.netty.util.AttributeKey;
import com.datastax.oss.driver.shaded.netty.util.concurrent.Future;
import com.datastax.oss.driver.shaded.netty.util.concurrent.Promise;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import net.jcip.annotations.ThreadSafe;
/**
* A thin wrapper around a Netty {@link Channel}, to send requests to a Cassandra node and receive
* responses.
*/
@ThreadSafe
public class DriverChannel {
static final AttributeKey CLUSTER_NAME_KEY = AttributeKey.valueOf("cluster_name");
static final AttributeKey>> OPTIONS_KEY =
AttributeKey.valueOf("options");
@SuppressWarnings("RedundantStringConstructorCall")
static final Object GRACEFUL_CLOSE_MESSAGE = new String("GRACEFUL_CLOSE_MESSAGE");
@SuppressWarnings("RedundantStringConstructorCall")
static final Object FORCEFUL_CLOSE_MESSAGE = new String("FORCEFUL_CLOSE_MESSAGE");
private final EndPoint endPoint;
private final Channel channel;
private final InFlightHandler inFlightHandler;
private final WriteCoalescer writeCoalescer;
private final ProtocolVersion protocolVersion;
private final AtomicBoolean closing = new AtomicBoolean();
private final AtomicBoolean forceClosing = new AtomicBoolean();
DriverChannel(
EndPoint endPoint,
Channel channel,
WriteCoalescer writeCoalescer,
ProtocolVersion protocolVersion) {
this.endPoint = endPoint;
this.channel = channel;
this.inFlightHandler = channel.pipeline().get(InFlightHandler.class);
this.writeCoalescer = writeCoalescer;
this.protocolVersion = protocolVersion;
}
/**
* @return a future that succeeds when the request frame was successfully written on the channel.
* Beyond that, the caller will be notified through the {@code responseCallback}.
*/
public Future write(
Message request,
boolean tracing,
Map customPayload,
ResponseCallback responseCallback) {
if (closing.get()) {
return channel.newFailedFuture(new IllegalStateException("Driver channel is closing"));
}
RequestMessage message = new RequestMessage(request, tracing, customPayload, responseCallback);
return writeCoalescer.writeAndFlush(channel, message);
}
/**
* Cancels a callback, indicating that the client that wrote it is no longer interested in the
* answer.
*
* Note that this does not cancel the request server-side (but might in the future if Cassandra
* supports it).
*/
public void cancel(ResponseCallback responseCallback) {
// To avoid creating an extra message, we adopt the convention that writing the callback
// directly means cancellation
writeCoalescer.writeAndFlush(channel, responseCallback).addListener(UncaughtExceptions::log);
}
/**
* Switches the underlying Cassandra connection to a new keyspace (as if a {@code USE ...}
* statement was issued).
*
*
The future will complete once the change is effective. Only one change may run at a given
* time, concurrent attempts will fail.
*
*
Changing the keyspace is inherently thread-unsafe: if other queries are running at the same
* time, the keyspace they will use is unpredictable.
*/
public Future setKeyspace(CqlIdentifier newKeyspace) {
Promise promise = channel.eventLoop().newPromise();
channel.pipeline().fireUserEventTriggered(new SetKeyspaceEvent(newKeyspace, promise));
return promise;
}
/**
* @return the name of the Cassandra cluster as returned by {@code system.local.cluster_name} on
* this connection.
*/
public String getClusterName() {
return channel.attr(CLUSTER_NAME_KEY).get();
}
public Map> getOptions() {
return channel.attr(OPTIONS_KEY).get();
}
/**
* @return the number of available stream ids on the channel; more precisely, this is the number
* of {@link #preAcquireId()} calls for which the id has not been released yet. This is used
* to weigh channels in pools that have a size bigger than 1, in the load balancing policy,
* and for monitoring purposes.
*/
public int getAvailableIds() {
return inFlightHandler.getAvailableIds();
}
/**
* Indicates the intention to send a request using this channel.
*
* There must be exactly one invocation of this method before each call to {@link
* #write(Message, boolean, Map, ResponseCallback)}. If this method returns true, the client
* must proceed with the write. If it returns false, it must not proceed.
*
*
This method is used together with {@link #getAvailableIds()} to track how many requests are
* currently executing on the channel, and avoid submitting a request that would result in a
* {@link BusyConnectionException}. The two methods follow atomic semantics: {@link
* #getAvailableIds()} returns the exact count of clients that have called {@link #preAcquireId()}
* and not yet released their stream id at this point in time.
*
*
Most of the time, the driver code calls this method automatically:
*
*
* if you obtained the channel from a pool ({@link ChannelPool#next()} or {@link
* DefaultSession#getChannel(Node, String)}), do not call this method: it has already
* been done as part of selecting the channel.
* if you use {@link ChannelHandlerRequest} or {@link AdminRequestHandler} for internal
* queries, do not call this method, those classes already do it.
* however, if you use {@link ThrottledAdminRequestHandler}, you must specify a {@code
* shouldPreAcquireId} argument to indicate whether to call this method or not. This is
* because those requests are sometimes used with a channel that comes from a pool
* (requiring {@code shouldPreAcquireId = false}), or sometimes with a standalone channel
* like in the control connection (requiring {@code shouldPreAcquireId = true}).
*
*/
public boolean preAcquireId() {
return inFlightHandler.preAcquireId();
}
/**
* @return the number of requests currently executing on this channel (including {@link
* #getOrphanedIds() orphaned ids}).
*/
public int getInFlight() {
return inFlightHandler.getInFlight();
}
/**
* @return the number of stream ids for requests that have either timed out or been cancelled, but
* for which we can't release the stream id because a request might still come from the
* server.
*/
public int getOrphanedIds() {
return inFlightHandler.getOrphanIds();
}
public EventLoop eventLoop() {
return channel.eventLoop();
}
public ProtocolVersion protocolVersion() {
return protocolVersion;
}
/** The endpoint that was used to establish the connection. */
public EndPoint getEndPoint() {
return endPoint;
}
public SocketAddress localAddress() {
return channel.localAddress();
}
/** @return The {@link ChannelConfig configuration} of this channel. */
public ChannelConfig config() {
return channel.config();
}
/**
* Initiates a graceful shutdown: no new requests will be accepted, but all pending requests will
* be allowed to complete before the underlying channel is closed.
*/
public Future close() {
if (closing.compareAndSet(false, true) && channel.isOpen()) {
// go through the coalescer: this guarantees that we won't reject writes that were submitted
// before, but had not been coalesced yet.
writeCoalescer
.writeAndFlush(channel, GRACEFUL_CLOSE_MESSAGE)
.addListener(UncaughtExceptions::log);
}
return channel.closeFuture();
}
/**
* Initiates a forced shutdown: any pending request will be aborted and the underlying channel
* will be closed.
*/
public Future forceClose() {
this.close();
if (forceClosing.compareAndSet(false, true) && channel.isOpen()) {
writeCoalescer
.writeAndFlush(channel, FORCEFUL_CLOSE_MESSAGE)
.addListener(UncaughtExceptions::log);
}
return channel.closeFuture();
}
/**
* Returns a future that will complete when a graceful close has started, but not yet completed.
*
* In other words, the channel has stopped accepting new requests, but is still waiting for
* pending requests to finish. Once the last response has been received, the channel will really
* close and {@link #closeFuture()} will be completed.
*
*
If there were no pending requests when the graceful shutdown was initiated, or if {@link
* #forceClose()} is called first, this future will never complete.
*/
public ChannelFuture closeStartedFuture() {
return this.inFlightHandler.closeStartedFuture;
}
/**
* Does not close the channel, but returns a future that will complete when it is completely
* closed.
*/
public ChannelFuture closeFuture() {
return channel.closeFuture();
}
@Override
public String toString() {
return channel.toString();
}
// This is essentially a stripped-down Frame. We can't materialize the frame before writing,
// because we need the stream id, which is assigned from within the event loop.
static class RequestMessage {
final Message request;
final boolean tracing;
final Map customPayload;
final ResponseCallback responseCallback;
RequestMessage(
Message message,
boolean tracing,
Map customPayload,
ResponseCallback responseCallback) {
this.request = message;
this.tracing = tracing;
this.customPayload = customPayload;
this.responseCallback = responseCallback;
}
}
static class SetKeyspaceEvent {
final CqlIdentifier keyspaceName;
final Promise promise;
public SetKeyspaceEvent(CqlIdentifier keyspaceName, Promise promise) {
this.keyspaceName = keyspaceName;
this.promise = promise;
}
}
}