/**
* This file is part of ObjectFabric (http://objectfabric.org).
*
* ObjectFabric is licensed under the Apache License, Version 2.0, the terms
* of which may be found at http://www.apache.org/licenses/LICENSE-2.0.html.
*
* Copyright ObjectFabric Inc.
*
* This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
* WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
package org.objectfabric;
import java.io.File;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.concurrent.ConcurrentHashMap;
import org.objectfabric.CloseCounter.Callback;
final class FileSystemQueue extends BlockQueue implements Runnable {
// TODO limit pending reads too?
private static final int MAX_ONGOING = 100;
private final Location _location;
private final ConcurrentHashMap<String, Object> _ongoing = new ConcurrentHashMap<String, Object>();
FileSystemQueue(Location location) {
_location = location;
if (Debug.THREADS)
ThreadAssert.exchangeGive(this, this);
onStarted();
}
@Override
void onClose(Callback callback) {
Object key;
if (Debug.ENABLED) {
ThreadAssert.suspend(key = new Object());
ThreadAssert.resume(this, false);
}
if (Debug.THREADS) {
ThreadAssert.exchangeTake(this);
ThreadAssert.removePrivate(this);
}
if (Debug.ENABLED)
ThreadAssert.resume(key);
super.onClose(callback);
}
final ConcurrentHashMap<String, Object> ongoing() {
return _ongoing;
}
@Override
protected void enqueue() {
Platform.get().execute(this);
}
@Override
public void run() {
if (onRunStarting()) {
if (Debug.ENABLED)
ThreadAssert.resume(this, false);
if (Debug.THREADS)
ThreadAssert.exchangeTake(this);
runMessages(false);
while (_ongoing.size() < MAX_ONGOING) {
final Block block = nextBlock();
if (block == null)
break;
final FileSystemView view = (FileSystemView) block.URI.getOrCreate(_location);
final File file = new File(view.folder(), Utils.getTickHex(block.Tick));
final Object write = new Object();
_ongoing.put(file.getPath(), write);
if (Debug.THREADS)
for (int i = 0; i < block.Buffs.length; i++)
ThreadAssert.exchangeGive(block, block.Buffs[i]);
ThreadPool.getInstance().execute(new Runnable() {
@Override
public void run() {
if (Debug.THREADS)
ThreadAssert.exchangeTake(block);
if (Debug.ENABLED) {
Debug.assertion(block.Buffs.length > 0);
Debug.assertion(block.Buffs[0].remaining() > 0);
}
if (Debug.THREADS)
ThreadAssert.exchangeTake(block.Buffs);
boolean ok = write(view, file, block.Buffs, block.Removals);
if (ok) {
block.URI.onAck(view, block.Tick);
view.add(block.Tick, block.Removals);
}
_ongoing.remove(file.getPath(), write);
// In case blocks left in queue
requestRun();
}
});
}
if (Debug.ENABLED)
ThreadAssert.suspend(this);
onRunEnded(false);
}
}
private boolean write(FileSystemView view, File file, Buff[] buffs, long[] removals) {
if (Debug.PERSISTENCE_LOG)
Log.write("File write " + file.getPath());
if (Stats.ENABLED)
Stats.Instance.BlockWriteCount.incrementAndGet();
RandomAccessFile raf = null;
boolean ok = false;
try {
// TODO do earlier?
file.getParentFile().mkdirs();
raf = new RandomAccessFile(file, "rw");
FileChannel channel = raf.getChannel();
// TODO lock file for multi-process?
// if (channel.tryLock() != null) {
ByteBuffer[] buffers = new ByteBuffer[buffs.length];
for (int i = 0; i < buffs.length; i++)
buffers[i] = ((JVMBuff) buffs[i]).getByteBuffer();
channel.write(buffers);
// TODO remove? Maybe delay removals 1 minute instead?
channel.force(false);
if (removals != null)
for (int i = 0; i < removals.length; i++)
if (!Tick.isNull(removals[i]))
delete(view.folder(), Utils.getTickHex(removals[i]), 0);
ok = true;
// }
} catch (Exception ex) {
Log.write(ex);
}
try {
// Closes channel & lock
if (raf != null)
raf.close();
} catch (Exception _) {
// Ignore
}
for (int i = 0; i < buffs.length; i++)
buffs[i].recycle();
return ok;
}
private static void delete(final File folder, final String name, final int attempt) {
File file = new File(folder, name);
if (file.exists()) {
if (Debug.PERSISTENCE_LOG)
Log.write("File delete " + file);
try {
if (!file.delete()) {
if (attempt < 3) {
ThreadPool.scheduleOnce(new Runnable() {
@Override
public void run() {
delete(folder, name, attempt + 1);
}
}, 100);
} else
Log.write("Could not delete " + file);
}
} catch (Exception e) {
Log.write(e);
}
}
}
}