/**
* 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.mapped;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
import org.apache.activemq.artemis.core.io.DummyCallback;
import org.apache.activemq.artemis.core.io.IOCallback;
import org.apache.activemq.artemis.core.io.SequentialFile;
import org.apache.activemq.artemis.core.io.SequentialFileFactory;
import org.apache.activemq.artemis.core.io.buffer.TimedBuffer;
import org.apache.activemq.artemis.core.io.buffer.TimedBufferObserver;
import org.apache.activemq.artemis.core.journal.EncodingSupport;
import org.apache.activemq.artemis.journal.ActiveMQJournalLogger;
final class TimedSequentialFile implements SequentialFile {
private final SequentialFileFactory factory;
private final SequentialFile sequentialFile;
private final LocalBufferObserver observer;
private final ThreadLocal<ResettableIOCallback> callbackPool;
private TimedBuffer timedBuffer;
TimedSequentialFile(SequentialFileFactory factory, SequentialFile sequentialFile) {
this.sequentialFile = sequentialFile;
this.factory = factory;
this.observer = new LocalBufferObserver();
this.callbackPool = ThreadLocal.withInitial(ResettableIOCallback::new);
}
@Override
public boolean isOpen() {
return this.sequentialFile.isOpen();
}
@Override
public boolean exists() {
return this.sequentialFile.exists();
}
@Override
public void open() throws Exception {
this.sequentialFile.open();
}
@Override
public void open(int maxIO, boolean useExecutor) throws Exception {
this.sequentialFile.open(maxIO, useExecutor);
}
@Override
public boolean fits(int size) {
if (timedBuffer == null) {
return this.sequentialFile.fits(size);
} else {
return timedBuffer.checkSize(size);
}
}
@Override
public int calculateBlockStart(int position) throws Exception {
return this.sequentialFile.calculateBlockStart(position);
}
@Override
public String getFileName() {
return this.sequentialFile.getFileName();
}
@Override
public void fill(int size) throws Exception {
this.sequentialFile.fill(size);
}
@Override
public void delete() throws IOException, InterruptedException, ActiveMQException {
this.sequentialFile.delete();
}
@Override
public void write(ActiveMQBuffer bytes, boolean sync, IOCallback callback) throws Exception {
if (this.timedBuffer != null) {
this.timedBuffer.addBytes(bytes, sync, callback);
} else {
this.sequentialFile.write(bytes, sync, callback);
}
}
@Override
public void write(ActiveMQBuffer bytes, boolean sync) throws Exception {
if (sync) {
if (this.timedBuffer != null) {
final ResettableIOCallback callback = callbackPool.get();
try {
this.timedBuffer.addBytes(bytes, true, callback);
callback.waitCompletion();
} finally {
callback.reset();
}
} else {
this.sequentialFile.write(bytes, true);
}
} else {
if (this.timedBuffer != null) {
this.timedBuffer.addBytes(bytes, false, DummyCallback.getInstance());
} else {
this.sequentialFile.write(bytes, false);
}
}
}
@Override
public void write(EncodingSupport bytes, boolean sync, IOCallback callback) throws Exception {
if (this.timedBuffer != null) {
this.timedBuffer.addBytes(bytes, sync, callback);
} else {
this.sequentialFile.write(bytes, sync, callback);
}
}
@Override
public void write(EncodingSupport bytes, boolean sync) throws Exception {
if (sync) {
if (this.timedBuffer != null) {
final ResettableIOCallback callback = callbackPool.get();
try {
this.timedBuffer.addBytes(bytes, true, callback);
callback.waitCompletion();
} finally {
callback.reset();
}
} else {
this.sequentialFile.write(bytes, true);
}
} else {
if (this.timedBuffer != null) {
this.timedBuffer.addBytes(bytes, false, DummyCallback.getInstance());
} else {
this.sequentialFile.write(bytes, false);
}
}
}
@Override
public void writeDirect(ByteBuffer bytes, boolean sync, IOCallback callback) {
this.sequentialFile.writeDirect(bytes, sync, callback);
}
@Override
public void writeDirect(ByteBuffer bytes, boolean sync) throws Exception {
this.sequentialFile.writeDirect(bytes, sync);
}
@Override
public int read(ByteBuffer bytes, IOCallback callback) throws Exception {
return this.sequentialFile.read(bytes, callback);
}
@Override
public int read(ByteBuffer bytes) throws Exception {
return this.sequentialFile.read(bytes);
}
@Override
public void position(long pos) throws IOException {
this.sequentialFile.position(pos);
}
@Override
public long position() {
return this.sequentialFile.position();
}
@Override
public void close() throws Exception {
this.sequentialFile.close();
}
@Override
public void sync() throws IOException {
this.sequentialFile.sync();
}
@Override
public long size() throws Exception {
return this.sequentialFile.size();
}
@Override
public void renameTo(String newFileName) throws Exception {
this.sequentialFile.renameTo(newFileName);
}
@Override
public SequentialFile cloneFile() {
return new TimedSequentialFile(factory, this.sequentialFile.cloneFile());
}
@Override
public void copyTo(SequentialFile newFileName) throws Exception {
this.sequentialFile.copyTo(newFileName);
}
@Override
public void setTimedBuffer(TimedBuffer buffer) {
if (this.timedBuffer != null) {
this.timedBuffer.setObserver(null);
}
this.timedBuffer = buffer;
if (buffer != null) {
buffer.setObserver(this.observer);
}
}
@Override
public File getJavaFile() {
return this.sequentialFile.getJavaFile();
}
private static final class ResettableIOCallback implements IOCallback {
private final CyclicBarrier cyclicBarrier;
private int errorCode;
private String errorMessage;
ResettableIOCallback() {
this.cyclicBarrier = new CyclicBarrier(2);
}
public void waitCompletion() throws InterruptedException, ActiveMQException, BrokenBarrierException {
this.cyclicBarrier.await();
if (this.errorMessage != null) {
throw ActiveMQExceptionType.createException(this.errorCode, this.errorMessage);
}
}
public void reset() {
this.errorCode = 0;
this.errorMessage = null;
}
@Override
public void done() {
try {
this.cyclicBarrier.await();
} catch (BrokenBarrierException | InterruptedException e) {
throw new IllegalStateException(e);
}
}
@Override
public void onError(int errorCode, String errorMessage) {
try {
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.cyclicBarrier.await();
} catch (BrokenBarrierException | InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
private static final class DelegateCallback implements IOCallback {
final List<IOCallback> delegates;
private DelegateCallback() {
this.delegates = new ArrayList<>();
}
public List<IOCallback> delegates() {
return this.delegates;
}
@Override
public void done() {
final int size = delegates.size();
for (int i = 0; i < size; i++) {
try {
final IOCallback callback = delegates.get(i);
callback.done();
} catch (Throwable e) {
ActiveMQJournalLogger.LOGGER.errorCompletingCallback(e);
}
}
}
@Override
public void onError(final int errorCode, final String errorMessage) {
for (IOCallback callback : delegates) {
try {
callback.onError(errorCode, errorMessage);
} catch (Throwable e) {
ActiveMQJournalLogger.LOGGER.errorCallingErrorCallback(e);
}
}
}
}
private final class LocalBufferObserver implements TimedBufferObserver {
private final ThreadLocal<DelegateCallback> callbacksPool = ThreadLocal.withInitial(DelegateCallback::new);
@Override
public void flushBuffer(final ByteBuffer buffer, final boolean requestedSync, final List<IOCallback> callbacks) {
buffer.flip();
if (buffer.limit() == 0) {
//if there are no bytes to flush, can release the callbacks
final int size = callbacks.size();
for (int i = 0; i < size; i++) {
callbacks.get(i).done();
}
} else {
final DelegateCallback delegateCallback = callbacksPool.get();
final int size = callbacks.size();
final List<IOCallback> delegates = delegateCallback.delegates();
for (int i = 0; i < size; i++) {
delegates.add(callbacks.get(i));
}
try {
sequentialFile.writeDirect(buffer, requestedSync, delegateCallback);
} finally {
delegates.clear();
}
}
}
@Override
public ByteBuffer newBuffer(final int size, final int limit) {
final int alignedSize = factory.calculateBlockSize(size);
final int alignedLimit = factory.calculateBlockSize(limit);
final ByteBuffer buffer = factory.newBuffer(alignedSize);
buffer.limit(alignedLimit);
return buffer;
}
@Override
public int getRemainingBytes() {
try {
final int remaining = (int) Math.min(sequentialFile.size() - sequentialFile.position(), Integer.MAX_VALUE);
return remaining;
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
@Override
public String toString() {
return "TimedBufferObserver on file (" + getFileName() + ")";
}
}
}