/* * 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.persistence.impl.journal; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.ActiveMQExceptionType; import org.apache.activemq.artemis.core.io.IOCallback; import org.apache.activemq.artemis.core.journal.impl.SimpleWaitIOCallback; import org.apache.activemq.artemis.core.persistence.OperationContext; import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; import org.apache.activemq.artemis.utils.ExecutorFactory; /** * Each instance of OperationContextImpl is associated with an executor (usually an ordered Executor). * * Tasks are hold until the operations are complete and executed in the natural order as soon as the operations are returned * from replication and storage. * * If there are no pending IO operations, the tasks are just executed at the callers thread without any context switch. * * So, if you are doing operations that are not dependent on IO (e.g NonPersistentMessages) you wouldn't have any context switch. */ public class OperationContextImpl implements OperationContext { private static final ThreadLocal<OperationContext> threadLocalContext = new ThreadLocal<>(); public static void clearContext() { OperationContextImpl.threadLocalContext.set(null); } public static final OperationContext getContext() { return getContext(null); } public static OperationContext getContext(final ExecutorFactory executorFactory) { OperationContext token = OperationContextImpl.threadLocalContext.get(); if (token == null) { if (executorFactory == null) { return null; } else { token = new OperationContextImpl(executorFactory.getExecutor()); OperationContextImpl.threadLocalContext.set(token); } } return token; } public static void setContext(final OperationContext context) { OperationContextImpl.threadLocalContext.set(context); } private List<TaskHolder> tasks; private List<TaskHolder> storeOnlyTasks; private long minimalStore = Long.MAX_VALUE; private long minimalReplicated = Long.MAX_VALUE; private long minimalPage = Long.MAX_VALUE; private final AtomicLong storeLineUp = new AtomicLong(0); private final AtomicLong replicationLineUp = new AtomicLong(0); private final AtomicLong pageLineUp = new AtomicLong(0); private long stored = 0; private long replicated = 0; private long paged = 0; private int errorCode = -1; private String errorMessage = null; private final Executor executor; private final AtomicInteger executorsPending = new AtomicInteger(0); public OperationContextImpl(final Executor executor) { super(); this.executor = executor; } @Override public void pageSyncLineUp() { pageLineUp.incrementAndGet(); } @Override public synchronized void pageSyncDone() { paged++; checkTasks(); } @Override public void storeLineUp() { storeLineUp.incrementAndGet(); } @Override public void replicationLineUp() { replicationLineUp.incrementAndGet(); } @Override public synchronized void replicationDone() { replicated++; checkTasks(); } @Override public void executeOnCompletion(IOCallback runnable) { executeOnCompletion(runnable, false); } @Override public void executeOnCompletion(final IOCallback completion, final boolean storeOnly) { if (errorCode != -1) { completion.onError(errorCode, errorMessage); return; } boolean executeNow = false; synchronized (this) { if (storeOnly) { if (storeOnlyTasks == null) { storeOnlyTasks = new LinkedList<>(); } } else { if (tasks == null) { tasks = new LinkedList<>(); minimalReplicated = replicationLineUp.intValue(); minimalStore = storeLineUp.intValue(); minimalPage = pageLineUp.intValue(); } } // On this case, we can just execute the context directly if (replicationLineUp.intValue() == replicated && storeLineUp.intValue() == stored && pageLineUp.intValue() == paged) { // We want to avoid the executor if everything is complete... // However, we can't execute the context if there are executions pending // We need to use the executor on this case if (executorsPending.get() == 0) { // No need to use an executor here or a context switch // there are no actions pending.. hence we can just execute the task directly on the same thread executeNow = true; } else { execute(completion); } } else { if (storeOnly) { storeOnlyTasks.add(new TaskHolder(completion)); } else { tasks.add(new TaskHolder(completion)); } } } if (executeNow) { // Executing outside of any locks completion.done(); } } @Override public synchronized void done() { stored++; checkTasks(); } private void checkTasks() { if (storeOnlyTasks != null) { Iterator<TaskHolder> iter = storeOnlyTasks.iterator(); while (iter.hasNext()) { TaskHolder holder = iter.next(); if (stored >= holder.storeLined) { // If set, we use an executor to avoid the server being single threaded execute(holder.task); iter.remove(); } } } if (stored >= minimalStore && replicated >= minimalReplicated && paged >= minimalPage) { Iterator<TaskHolder> iter = tasks.iterator(); while (iter.hasNext()) { TaskHolder holder = iter.next(); if (stored >= holder.storeLined && replicated >= holder.replicationLined && paged >= holder.pageLined) { // If set, we use an executor to avoid the server being single threaded execute(holder.task); iter.remove(); } else { // End of list here. No other task will be completed after this break; } } } } /** * @param task */ private void execute(final IOCallback task) { executorsPending.incrementAndGet(); try { executor.execute(new Runnable() { @Override public void run() { try { // If any IO is done inside the callback, it needs to be done on a new context OperationContextImpl.clearContext(); task.done(); } finally { executorsPending.decrementAndGet(); } } }); } catch (Throwable e) { ActiveMQServerLogger.LOGGER.errorExecutingAIOCallback(e); executorsPending.decrementAndGet(); task.onError(ActiveMQExceptionType.INTERNAL_ERROR.getCode(), "It wasn't possible to complete IO operation - " + e.getMessage()); } } /* * (non-Javadoc) * @see org.apache.activemq.artemis.core.replication.ReplicationToken#complete() */ public void complete() { } @Override public synchronized void onError(final int errorCode, final String errorMessage) { this.errorCode = errorCode; this.errorMessage = errorMessage; if (tasks != null) { Iterator<TaskHolder> iter = tasks.iterator(); while (iter.hasNext()) { TaskHolder holder = iter.next(); holder.task.onError(errorCode, errorMessage); iter.remove(); } } } final class TaskHolder { @Override public String toString() { return "TaskHolder [storeLined=" + storeLined + ", replicationLined=" + replicationLined + ", pageLined=" + pageLined + ", task=" + task + "]"; } final int storeLined; final int replicationLined; final int pageLined; final IOCallback task; TaskHolder(final IOCallback task) { storeLined = storeLineUp.intValue(); replicationLined = replicationLineUp.intValue(); pageLined = pageLineUp.intValue(); this.task = task; } } @Override public void waitCompletion() throws Exception { waitCompletion(0); } @Override public boolean waitCompletion(final long timeout) throws InterruptedException, ActiveMQException { SimpleWaitIOCallback waitCallback = new SimpleWaitIOCallback(); executeOnCompletion(waitCallback); complete(); if (timeout == 0) { waitCallback.waitCompletion(); return true; } else { return waitCallback.waitCompletion(timeout); } } @Override public String toString() { StringBuffer buffer = new StringBuffer(); if (tasks != null) { for (TaskHolder hold : tasks) { buffer.append("Task = " + hold + "\n"); } } return "OperationContextImpl [" + hashCode() + "] [minimalStore=" + minimalStore + ", storeLineUp=" + storeLineUp + ", stored=" + stored + ", minimalReplicated=" + minimalReplicated + ", replicationLineUp=" + replicationLineUp + ", replicated=" + replicated + ", paged=" + paged + ", minimalPage=" + minimalPage + ", pageLineUp=" + pageLineUp + ", errorCode=" + errorCode + ", errorMessage=" + errorMessage + ", executorsPending=" + executorsPending + ", executor=" + this.executor + "]" + buffer.toString(); } }