/* * 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.io; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.nio.ByteBuffer; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.apache.activemq.artemis.api.core.ActiveMQInterruptedException; import org.apache.activemq.artemis.core.io.buffer.TimedBuffer; import org.apache.activemq.artemis.journal.ActiveMQJournalLogger; import org.apache.activemq.artemis.utils.ActiveMQThreadFactory; /** * An abstract SequentialFileFactory containing basic functionality for both AIO and NIO SequentialFactories */ public abstract class AbstractSequentialFileFactory implements SequentialFileFactory { // Timeout used to wait executors to shutdown protected static final int EXECUTOR_TIMEOUT = 60; protected final File journalDir; protected final TimedBuffer timedBuffer; protected final int bufferSize; protected final long bufferTimeout; protected final int maxIO; protected boolean dataSync = true; protected volatile int alignment = -1; private final IOCriticalErrorListener critialErrorListener; /** * Asynchronous writes need to be done at another executor. * This needs to be done at NIO, or else we would have the callers thread blocking for the return. * At AIO this is necessary as context switches on writes would fire flushes at the kernel. */ protected ExecutorService writeExecutor; protected AbstractSequentialFileFactory(final File journalDir, final boolean buffered, final int bufferSize, final int bufferTimeout, final int maxIO, final boolean logRates, final IOCriticalErrorListener criticalErrorListener) { this.journalDir = journalDir; if (buffered && bufferTimeout > 0) { timedBuffer = new TimedBuffer(bufferSize, bufferTimeout, logRates); } else { timedBuffer = null; } this.bufferSize = bufferSize; this.bufferTimeout = bufferTimeout; this.critialErrorListener = criticalErrorListener; this.maxIO = maxIO; } @Override public int getAlignment() { if (alignment < 0) { alignment = 1; } return alignment; } @Override public AbstractSequentialFileFactory setAlignment(int alignment) { this.alignment = alignment; return this; } @Override public SequentialFileFactory setDatasync(boolean enabled) { this.dataSync = enabled; return this; } @Override public boolean isDatasync() { return dataSync; } @Override public void stop() { if (timedBuffer != null) { timedBuffer.stop(); } if (isSupportsCallbacks() && writeExecutor != null) { writeExecutor.shutdown(); try { if (!writeExecutor.awaitTermination(AbstractSequentialFileFactory.EXECUTOR_TIMEOUT, TimeUnit.SECONDS)) { ActiveMQJournalLogger.LOGGER.timeoutOnWriterShutdown(new Exception("trace")); } } catch (InterruptedException e) { throw new ActiveMQInterruptedException(e); } } } @Override public File getDirectory() { return journalDir; } @Override public void start() { if (timedBuffer != null) { timedBuffer.start(); } if (isSupportsCallbacks()) { writeExecutor = Executors.newSingleThreadExecutor(AccessController.doPrivileged(new PrivilegedAction<ActiveMQThreadFactory>() { @Override public ActiveMQThreadFactory run() { return new ActiveMQThreadFactory("ActiveMQ-Asynchronous-Persistent-Writes" + System.identityHashCode(this), true, AbstractSequentialFileFactory.class.getClassLoader()); } })); } } @Override public int getMaxIO() { return maxIO; } @Override public void onIOError(Exception exception, String message, SequentialFile file) { if (critialErrorListener != null) { critialErrorListener.onIOException(exception, message, file); } } @Override public void activateBuffer(final SequentialFile file) { if (timedBuffer != null) { file.setTimedBuffer(timedBuffer); } } @Override public void flush() { if (timedBuffer != null) { timedBuffer.flush(); } } @Override public void deactivateBuffer() { if (timedBuffer != null) { // When moving to a new file, we need to make sure any pending buffer will be transferred to the buffer timedBuffer.flush(); timedBuffer.setObserver(null); } } @Override public void releaseBuffer(final ByteBuffer buffer) { } /** * Create the directory if it doesn't exist yet */ @Override public void createDirs() throws Exception { boolean ok = journalDir.mkdirs(); if (!ok) { throw new IOException("Failed to create directory " + journalDir); } } @Override public List<String> listFiles(final String extension) throws Exception { FilenameFilter fnf = new FilenameFilter() { @Override public boolean accept(final File file, final String name) { return name.endsWith("." + extension); } }; String[] fileNames = journalDir.list(fnf); if (fileNames == null) { return Collections.EMPTY_LIST; } return Arrays.asList(fileNames); } }