/* * Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved. * * 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.hazelcast.durableexecutor.impl; import com.hazelcast.durableexecutor.StaleTaskIdException; import com.hazelcast.nio.ObjectDataInput; import com.hazelcast.nio.ObjectDataOutput; import java.io.IOException; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.concurrent.Callable; import java.util.concurrent.RejectedExecutionException; /** * A RingBuffer implementation to store tasks and results of those task * Only a single thread (partition-operation-thread) accesses the instance */ public class TaskRingBuffer { private Object[] ringItems; private int[] sequences; private boolean[] isTask; private int head = -1; private int callableCounter; public TaskRingBuffer() { } public TaskRingBuffer(int capacity) { this.ringItems = new Object[capacity]; this.isTask = new boolean[capacity]; this.sequences = new int[capacity]; } /** * Adds the task to next available spot and returns the sequence corresponding to that spot. * throws exception if there is no available spot * * @param task The task * @return the sequence * @throws RejectedExecutionException if there is not available spot for the task */ public int add(Callable task) { int index = findEmptySpot(); callableCounter++; ringItems[index] = task; isTask[index] = true; sequences[index] = head; return head; } private int findEmptySpot() { if (callableCounter == ringItems.length) { throw new RejectedExecutionException("Capacity[" + ringItems.length + "] is reached! "); } for (Object ringItem : ringItems) { head++; int index = toIndex(head); if (!isTask[index]) { return index; } } throw new IllegalStateException(); } /** * Removes the task with the given sequence * * @param sequence the sequence */ public void remove(int sequence) { int index = toIndex(sequence); ringItems[index] = null; isTask[index] = false; head--; callableCounter--; } /** * Puts the task for the given sequence * * @param sequence The sequence * @param task The task */ void putBackup(int sequence, Callable task) { head = Math.max(head, sequence); callableCounter++; int index = toIndex(sequence); ringItems[index] = task; isTask[index] = true; sequences[index] = sequence; } /** * Replaces the task with its response * If the sequence does not correspond to a task then the call is ignored * * @param sequence The sequence * @param response The response */ void replaceTaskWithResult(int sequence, Object response) { int index = toIndex(sequence); // If sequence is not equal then it is disposed externally if (sequences[index] != sequence) { return; } ringItems[index] = response; isTask[index] = false; callableCounter--; } /** * Gets the response and disposes the sequence * * @param sequence The sequence * @return The response */ Object retrieveAndDispose(int sequence) { int index = toIndex(sequence); checkSequence(index, sequence); try { return ringItems[index]; } finally { ringItems[index] = null; isTask[index] = false; head--; } } /** * Disposes the sequence * * @param sequence The sequence */ public void dispose(int sequence) { int index = toIndex(sequence); checkSequence(index, sequence); if (isTask[index]) { callableCounter--; } ringItems[index] = null; isTask[index] = false; } /** * Gets the response * * @param sequence The sequence * @return The response */ public Object retrieve(int sequence) { int index = toIndex(sequence); checkSequence(index, sequence); return ringItems[index]; } /** * Check if the sequence corresponds to a task * * @param sequence The sequence * @return <tt>true</tt> if the sequence corresponds to a task, <tt>false</tt> otherwise * @throws StaleTaskIdException if the solt overwritten */ boolean isTask(int sequence) { int index = toIndex(sequence); checkSequence(index, sequence); return isTask[index]; } private void checkSequence(int index, int sequence) { if (sequences[index] != sequence) { throw new StaleTaskIdException("The sequence has been overwritten"); } } private int toIndex(int sequence) { return Math.abs(sequence % ringItems.length); } public void write(ObjectDataOutput out) throws IOException { out.writeInt(head); out.writeInt(ringItems.length); for (int i = 0; i < ringItems.length; i++) { out.writeBoolean(isTask[i]); out.writeInt(sequences[i]); out.writeObject(ringItems[i]); } } public void read(ObjectDataInput in) throws IOException { head = in.readInt(); int length = in.readInt(); ringItems = new Object[length]; isTask = new boolean[length]; sequences = new int[length]; for (int i = 0; i < length; i++) { isTask[i] = in.readBoolean(); sequences[i] = in.readInt(); ringItems[i] = in.readObject(); } } public DurableIterator iterator() { return new DurableIterator(); } public class DurableIterator implements Iterator { int index = -1; @Override public boolean hasNext() { return index + 1 < ringItems.length; } @Override public Object next() { if (++index == ringItems.length) { throw new NoSuchElementException(); } return ringItems[index]; } @Override public void remove() { throw new UnsupportedOperationException(); } public int getSequence() { return sequences[index]; } public boolean isTask() { return isTask[index]; } } }