All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.aerospike.client.async.NioRecover Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2012-2024 Aerospike, Inc.
 *
 * Portions may be licensed to Aerospike, Inc. under one or more contributor
 * license agreements WHICH ARE COMPATIBLE WITH THE APACHE LICENSE, VERSION 2.0.
 *
 * Licensed 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.aerospike.client.async;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.util.concurrent.TimeUnit;

import com.aerospike.client.AerospikeException;
import com.aerospike.client.Log;
import com.aerospike.client.async.HashedWheelTimer.HashedWheelTimeout;
import com.aerospike.client.cluster.Node;
import com.aerospike.client.command.Command;
import com.aerospike.client.util.Util;

public final class NioRecover implements INioCommand, TimerTask {
	//private static final AtomicInteger Counter = new AtomicInteger();

	private final NioEventLoop eventLoop;
	private final EventState eventState;
	private final Node node;
	private final NioConnection conn;
	private final HashedWheelTimeout timeoutTask;
	private final ByteBuffer byteBuffer;
	//private final int tranId;
	private int offset;
	private int length;
	private int state;
	private final boolean isSingle;
	private final boolean checkReturnCode;
	private boolean isLastGroup;

	public NioRecover(NioCommand cmd) {
		AsyncCommand a = cmd.command;
		this.eventLoop = cmd.eventLoop;
		this.eventState = cmd.eventState;
		this.node = cmd.node;
		this.conn = cmd.conn;
		this.byteBuffer = cmd.byteBuffer;

		switch (cmd.state) {
		case AsyncCommand.AUTH_READ_HEADER:
			this.offset = byteBuffer.position();
			this.length = byteBuffer.limit();
			this.state = AsyncCommand.COMMAND_READ_HEADER;
			this.isSingle = true;
			this.checkReturnCode = true;
			break;

		case AsyncCommand.AUTH_READ_BODY:
			this.offset = byteBuffer.position();
			this.length = byteBuffer.limit();
			this.state = AsyncCommand.COMMAND_READ_BODY;
			this.isSingle = true;
			this.checkReturnCode = true;
			break;

		case AsyncCommand.COMMAND_READ_BODY:
			if (a.isSingle) {
				this.offset = byteBuffer.position();
				this.length = byteBuffer.limit();
			}
			else {
				// Multi-record detail handles offsets differently.
				this.offset = a.dataOffset;
				this.length = a.receiveSize;

				if (a.dataOffset >= 4) {
					// Warning: The following code assumes multi-record responses always end with a separate proto
					// that only contains one header with the info3 last group bit.  This is always true for batch
					// and scan, but query does not conform.  Therefore, connection recovery for queries will
					// likely fail.
					byte info3 = a.dataBuffer[3];

					if ((info3 & Command.INFO3_LAST) != 0) {
						isLastGroup = true;
					}
				}
			}
			this.state = cmd.state;
			this.isSingle = a.isSingle;
			this.checkReturnCode = false;
			break;

		default:
			this.offset = byteBuffer.position();
			this.length = byteBuffer.limit();
			this.state = cmd.state;
			this.isSingle = a.isSingle;
			this.checkReturnCode = false;
			break;
		}

		//tranId = Counter.getAndIncrement();
		//System.out.println("" + tranId + " timeout:" + a.isSingle + ',' + cmd.state + ',' + offset + ',' + length);

		conn.attach(this);
		timeoutTask = new HashedWheelTimeout(this);
		eventLoop.timer.addTimeout(timeoutTask, System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(a.policy.timeoutDelay));
	}

	@Override
	public final void timeout() {
		//System.out.println("" + tranId + " timeout expired. close connection");

		// Command has been delayed long enough.
		// User has already been notified.
		// timeoutTask has already been removed, so avoid cancel.
		abort(false);
	}

	@Override
	public final void processEvent(SelectionKey key) {
		try {
			int ops = key.readyOps();

			if ((ops & SelectionKey.OP_READ) != 0) {
				switch (state) {
				case AsyncCommand.COMMAND_READ_HEADER:
					if (isSingle) {
						drainSingleHeader();
					}
					else {
						if (drainMultiHeader()) {
							drainMultiBody();
						}
					}
					break;

				case AsyncCommand.COMMAND_READ_BODY:
					if (isSingle) {
						drainSingleBody();
					}
					else {
						drainMultiBody();
					}
					break;
				}
			}
		}
		catch (Throwable e) {
			//System.out.println("" + tranId + " socket error:");
			//e.printStackTrace();
			abort(true);
		}
	}

	private final void drainSingleHeader() throws IOException {
		if (! conn.read(byteBuffer)) {
			return;
		}

		byteBuffer.position(0);
		length = ((int) (byteBuffer.getLong() & 0xFFFFFFFFFFFFL));
		byteBuffer.clear();

		if (length < byteBuffer.capacity()) {
			byteBuffer.limit(length);
		}

		offset = 0;
		state = AsyncCommand.COMMAND_READ_BODY;
		drainSingleBody();
	}

	private final void drainSingleBody() throws IOException {
		while (true) {
			if (! conn.read(byteBuffer)) {
				return;
			}

			if (checkReturnCode) {
				int resultCode = byteBuffer.get(1) & 0xFF;

				if (resultCode != 0) {
					// Authentication failed.
					//System.out.println("" + tranId + " invalid user/password:");
					abort(false);
					return;
				}
			}

			if (resetBuffer()) {
				continue;
			}

			recover();
			return;
		}
	}

	private final boolean drainMultiHeader() throws IOException {
		while (true) {
			if (! conn.read(byteBuffer)) {
				return false;
			}

			byteBuffer.position(0);
			long proto = byteBuffer.getLong();
			length = ((int)(proto & 0xFFFFFFFFFFFFL));

			if (length <= 0) {
				// Received zero length block. Read next header.
				byteBuffer.clear();
				byteBuffer.limit(8);
				continue;
			}

			boolean compressed = ((proto >> 48) & 0xff) == Command.MSG_TYPE_COMPRESSED;

			if (compressed) {
				// Do not recover connections with compressed data because that would
				// require saving large buffers with associated state and performing decompression
				// just to drain the connection.
				throw new AerospikeException("Recovering connections with compressed multi-record data is not supported");
			}
			break;
		}

		byteBuffer.clear();

		if (length < byteBuffer.capacity()) {
			byteBuffer.limit(length);
		}

		offset = 0;
		state = AsyncCommand.COMMAND_READ_BODY;
		return true;
	}

	private final void drainMultiBody() throws IOException {
		while (true) {
			if (! conn.read(byteBuffer)) {
				return;
			}

			// Only check for last group on the header of the
			// first record in the group.
			if (offset < 4) {
				byte info3 = byteBuffer.get(3);

				if ((info3 & Command.INFO3_LAST) != 0) {
					isLastGroup = true;
				}
			}

			if (resetBuffer()) {
				continue;
			}

			if (isLastGroup) {
				recover();
				return;
			}

			byteBuffer.clear();
			byteBuffer.limit(8);
			state = AsyncCommand.COMMAND_READ_HEADER;

			if (! drainMultiHeader()) {
				return;
			}
		}
	}

	private final boolean resetBuffer() {
		offset += byteBuffer.limit();

		if (offset < length) {
			byteBuffer.clear();

			int remaining = length - offset;

			if (remaining < byteBuffer.capacity()) {
				byteBuffer.limit(remaining);
			}
			return true;
		}
		return false;
	}

	private final void recover() {
		//System.out.println("" + tranId + " connection drained");
		if (state == AsyncCommand.COMPLETE) {
			return;
		}
		state = AsyncCommand.COMPLETE;

		try {
			conn.unregister();
			conn.updateLastUsed();
			node.putAsyncConnection(conn, eventLoop.index);
			close(true);
		}
		catch (Throwable e) {
			if (! eventState.closed) {
				Log.error("NioRecover recover failed: " + Util.getStackTrace(e));
			}
		}
	}

	private final void abort(boolean cancelTimeout) {
		if (state == AsyncCommand.COMPLETE) {
			return;
		}
		state = AsyncCommand.COMPLETE;

		try {
			node.closeAsyncConnection(conn, eventLoop.index);
			close(cancelTimeout);
		}
		catch (Throwable e) {
			if (! eventState.closed) {
				Log.error("NioRecover abort failed: " + Util.getStackTrace(e));
			}
		}
	}

	private final void close(boolean cancelTimeout) {
		if (cancelTimeout) {
			timeoutTask.cancel();
		}

		if (byteBuffer != null) {
			eventLoop.putByteBuffer(byteBuffer);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy