All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xadisk.filesystem.workers.GatheringDiskWriter Maven / Gradle / Ivy

/*
Copyright © 2010-2011, Nitin Verma (project owner for XADisk https://xadisk.dev.java.net/). All rights reserved.

This source code is being made available to the public under the terms specified in the license
"Eclipse Public License 1.0" located at http://www.opensource.org/licenses/eclipse-1.0.php.
*/


package org.xadisk.filesystem.workers;

import org.xadisk.filesystem.pools.PooledBuffer;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import org.xadisk.connector.inbound.EndPointActivation;
import org.xadisk.filesystem.Buffer;
import org.xadisk.filesystem.FileSystemStateChangeEvent;
import org.xadisk.filesystem.NativeXAFileSystem;
import org.xadisk.filesystem.OnDiskInfo;
import org.xadisk.filesystem.TransactionLogEntry;
import org.xadisk.filesystem.TransactionInformation;
import org.xadisk.filesystem.utilities.TransactionLogsUtility;

public class GatheringDiskWriter extends EventWorker {

    private final int cumulativeBufferSizeForDiskWrite;
    private final AtomicInteger cumulativeBufferSize = new AtomicInteger(0);
    private FileChannel transactionLogChannel;
    private final ConcurrentHashMap> transactionSubmittedBuffers =
            new ConcurrentHashMap>(1000);
    private final NativeXAFileSystem xaFileSystem;
    private final long transactionLogFileMaxSize;
    private final ReentrantLock transactionLogLock = new ReentrantLock(false);
    private final String transactionLogBaseName;
    private int currentLogIndex;
    private final HashMap transactionLogsAndOpenTransactions = new HashMap(2);
    private final HashMap> transactionsAndLogsOccupied = new HashMap>(1000);
    private final long maxNonPooledBufferSize;

    public GatheringDiskWriter(int cumulativeBufferSizeForDiskWrite, long transactionLogFileMaxSize,
            long maxNonPooledBufferSize,
            String transactionLogBaseName, NativeXAFileSystem theXAFileSystem)
            throws IOException {
        this.cumulativeBufferSizeForDiskWrite = cumulativeBufferSizeForDiskWrite;
        this.xaFileSystem = theXAFileSystem;
        this.transactionLogFileMaxSize = transactionLogFileMaxSize;
        this.transactionLogBaseName = transactionLogBaseName;
        this.maxNonPooledBufferSize = maxNonPooledBufferSize;
    }

    public void initialize() throws IOException {
        File currentTransactionLog = null;
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            File temp = new File(transactionLogBaseName + "_" + i);
            if (temp.exists()) {
                continue;
            }
            currentTransactionLog = temp;
            this.currentLogIndex = i;
            break;
        }
        if (currentTransactionLog == null) {
            throw new IOException("System has reached its limit on number of transaction logs.");
        }
        this.transactionLogChannel = new FileOutputStream(currentTransactionLog, false).getChannel();
    }

    public void deInitialize() throws IOException {
        this.transactionLogChannel.close();
    }

    @Override
    void processEvent() {
        try {
            Buffer buffersArray[];
            TransactionInformation xids[];
            ArrayList allBuffersToWrite = new ArrayList(1000);
            ArrayList xidsList = new ArrayList(1000);
            Iterator>> entries = transactionSubmittedBuffers.entrySet().iterator();
            while (entries.hasNext()) {
                Map.Entry> entry = entries.next();
                TransactionInformation xid = entry.getKey();
                ArrayList txnBuffers = transactionSubmittedBuffers.put(xid, new ArrayList(10));
                for (Buffer buffer : txnBuffers) {
                    xidsList.add(xid);
                    allBuffersToWrite.add(buffer);
                }
            }
            xids = xidsList.toArray(new TransactionInformation[0]);
            buffersArray = allBuffersToWrite.toArray(new Buffer[0]);
            try {
                transactionLogLock.lock();
                writeBuffersToTransactionLog(buffersArray, xids, 0);
            } finally {
                transactionLogLock.unlock();
            }
        } catch (Throwable t) {
            xaFileSystem.notifySystemFailure(t);
        }
    }

    public void writeRemainingBuffersNow(TransactionInformation xid) throws IOException {
        ArrayList txnBuffers = transactionSubmittedBuffers.put(xid, new ArrayList(10));
        for (Buffer buffer : txnBuffers) {
            cumulativeBufferSize.getAndAdd(-buffer.getBuffer().remaining());
        }
        try {
            TransactionInformation xids[] = new TransactionInformation[txnBuffers.size()];
            for (int i = 0; i < xids.length; i++) {
                xids[i] = xid;
            }
            try {
                transactionLogLock.lock();
                writeBuffersToTransactionLog(txnBuffers.toArray(new Buffer[0]), xids, 0);
            } finally {
                transactionLogLock.unlock();
            }
        } catch (IOException ioe) {
            xaFileSystem.notifySystemFailure(ioe);
        }
    }

    private void writeBuffersToTransactionLog(Buffer buffersArray[], TransactionInformation xids[], int offset) throws IOException {
        ByteBuffer byteBufferArray[] = new ByteBuffer[buffersArray.length];
        long sizeToWriteNow = 0;
        int canProcessTill = buffersArray.length - 1;
        for (int i = offset; i < buffersArray.length; i++) {
            byteBufferArray[i] = buffersArray[i].getBuffer();
            if (sizeToWriteNow + byteBufferArray[i].remaining() > transactionLogFileMaxSize) {
                canProcessTill = i - 1;
                break;
            }
            sizeToWriteNow += byteBufferArray[i].remaining();
        }
        ensureLogFileCapacity(sizeToWriteNow);
        long entryPosition = transactionLogChannel.position();
        ArrayList buffersToMakeOnDisk = new ArrayList(1000);
        boolean makeCurrentOnDisk;
        for (int i = offset; i <= canProcessTill; i++) {
            byteBufferArray[i] = buffersArray[i].getBuffer();
            if (buffersArray[i] instanceof PooledBuffer) {
                makeCurrentOnDisk = false;
            } else {
                if (xaFileSystem.getTotalNonPooledBufferSize() < maxNonPooledBufferSize * 3 / 4) {
                    makeCurrentOnDisk = false;
                } else if (xaFileSystem.getTotalNonPooledBufferSize() < maxNonPooledBufferSize) {
                    if (byteBufferArray[i].remaining() < 1000) {
                        makeCurrentOnDisk = false;
                    } else {
                        makeCurrentOnDisk = true;
                    }
                } else {
                    makeCurrentOnDisk = true;
                }
            }
            if (makeCurrentOnDisk) {
                addLogPositionToTransaction(xids[i], currentLogIndex, entryPosition);
                buffersToMakeOnDisk.add(i);
                buffersArray[i].setOnDiskInfo(new OnDiskInfo(currentLogIndex, entryPosition));
            } else {
                addInMemoryBufferToTransaction(xids[i], buffersArray[i]);
            }
            entryPosition += byteBufferArray[i].remaining();
        }
        long n = 0;
        while (n < sizeToWriteNow) {
            n += transactionLogChannel.write(byteBufferArray, offset, canProcessTill - offset + 1);
        }

        for (Integer indices : buffersToMakeOnDisk) {
            Buffer temp = buffersArray[indices];
            temp.makeOnDisk(temp.getOnDiskInfo());
        }

        if (canProcessTill < buffersArray.length - 1) {
            writeBuffersToTransactionLog(buffersArray, xids, canProcessTill + 1);
        }
    }

    private void addLogPositionToTransaction(TransactionInformation xid, int logFileIndex, long localPosition) {
        xid.getOwningSession().addLogPositionToTransaction(logFileIndex, localPosition);
        TransactionLogsUtility.trackTransactionLogsUsage(xid, transactionsAndLogsOccupied, transactionLogsAndOpenTransactions, logFileIndex);
    }

    private void addInMemoryBufferToTransaction(TransactionInformation xid, Buffer buffer) {
        xid.getOwningSession().addInMemoryBufferToTransaction(buffer);
    }

    public void submitBuffer(Buffer logEntry, TransactionInformation xid) {
        logEntry.flushByteBufferChanges();
        ArrayList txnBuffers = transactionSubmittedBuffers.get(xid);
        if (txnBuffers == null) {
            txnBuffers = new ArrayList(10);
            txnBuffers.add(logEntry);
            transactionSubmittedBuffers.put(xid, txnBuffers);
        } else {
            txnBuffers.add(logEntry);
        }
        int currentCumulativeSize = cumulativeBufferSize.addAndGet(logEntry.getBuffer().remaining());
        raiseEventThreadSafely(currentCumulativeSize);
    }

    private void raiseEventThreadSafely(int currentCumulativeSize) {
        if (currentCumulativeSize >= cumulativeBufferSizeForDiskWrite) {
            while (!cumulativeBufferSize.compareAndSet(currentCumulativeSize, 0)) {
                currentCumulativeSize = cumulativeBufferSize.get();
                if (currentCumulativeSize < cumulativeBufferSizeForDiskWrite) {
                    return;
                }
            }
            raiseEvent();
        }
    }

    public void transactionCommitBegins(TransactionInformation xid) throws IOException {
        ByteBuffer temp = ByteBuffer.wrap(TransactionLogEntry.getLogEntry(xid,
                TransactionLogEntry.COMMIT_BEGINS));
        forceWrite(temp);
    }

    public void transactionCompletes(TransactionInformation xid, boolean isCommitted) throws IOException {
        ByteBuffer temp = ByteBuffer.wrap(TransactionLogEntry.getLogEntry(xid, isCommitted ? TransactionLogEntry.TXN_COMMIT_DONE : TransactionLogEntry.TXN_ROLLBACK_DONE));
        forceWrite(temp);
    }

    public void transactionPrepareCompletes(TransactionInformation xid) throws IOException {
        ByteBuffer temp = ByteBuffer.wrap(TransactionLogEntry.getLogEntry(xid,
                TransactionLogEntry.PREPARE_COMPLETES));
        forceWrite(temp);
    }

    public void transactionPrepareCompletesForEventDequeue(TransactionInformation xid, FileSystemStateChangeEvent event) throws IOException {
        ArrayList events = new ArrayList(1);
        events.add(event);
        ByteBuffer temp = ByteBuffer.wrap(TransactionLogEntry.getLogEntry(xid, events,
                TransactionLogEntry.PREPARE_COMPLETES_FOR_EVENT_DEQUEUE));
        forceWrite(temp);
    }

    public void recordEndPointActivation(EndPointActivation activation) throws IOException {
        ByteBuffer temp = ByteBuffer.wrap(TransactionLogEntry.getLogEntry(activation,
                TransactionLogEntry.REMOTE_ENDPOINT_ACTIVATES));
        forceWrite(temp);
    }

    public void recordEndPointDeActivation(EndPointActivation activation) throws IOException {
        ByteBuffer temp = ByteBuffer.wrap(TransactionLogEntry.getLogEntry(activation,
                TransactionLogEntry.REMOTE_ENDPOINT_DEACTIVATES));
        forceWrite(temp);
    }

    public void forceLog(ByteBuffer logEntryHeader) throws IOException {
        try {
            transactionLogLock.lock();
            long n = 0;
            long sizeToWrite = logEntryHeader.remaining();
            ensureLogFileCapacity(sizeToWrite);
            while (n < sizeToWrite) {
                n += transactionLogChannel.write(logEntryHeader);
            }
            transactionLogChannel.force(false);
        } finally {
            transactionLogLock.unlock();
        }
    }

    public long[] forceUndoLogAndData(TransactionInformation xid, ByteBuffer logEntryHeader, FileChannel contents, long contentPosition,
            long contentLength)
            throws IOException {
        long logPosition[] = new long[2];
        try {
            transactionLogLock.lock();
            long n = 0;
            long headerSize = logEntryHeader.remaining();
            long totalLogSizeRequiredForThisRequest = headerSize;
            if (contentLength > 0) {
                totalLogSizeRequiredForThisRequest += contentLength;
            }
            ensureLogFileCapacity(totalLogSizeRequiredForThisRequest);
            logPosition[0] = currentLogIndex;
            logPosition[1] = transactionLogChannel.position();
            while (n < headerSize) {
                n += transactionLogChannel.write(logEntryHeader);
            }
            if (contentLength > 0) {
                n = 0;
                long startingPosition = transactionLogChannel.position();
                contents.position(contentPosition);
                while (n < contentLength) {
                    n += transactionLogChannel.transferFrom(contents, startingPosition + n, 
                            NativeXAFileSystem.maxTransferToChannel(contentLength - n));
                }
                transactionLogChannel.position(startingPosition + n);
            }
            transactionLogChannel.force(false);
            addLogPositionToTransaction(xid, (int) logPosition[0], logPosition[1]);
            return logPosition;
        } finally {
            transactionLogLock.unlock();
        }
    }

    private void forceWrite(ByteBuffer buffer) throws IOException {
        try {
            transactionLogLock.lock();
            long n = 0;
            long sizeToWrite = buffer.remaining();
            ensureLogFileCapacity(sizeToWrite);
            while (n < sizeToWrite) {
                n += transactionLogChannel.write(buffer);
            }
            transactionLogChannel.force(false);
        } finally {
            transactionLogLock.unlock();
        }
    }

    public void cleanupTransactionInfo(TransactionInformation xid) throws IOException {
        try {
            transactionLogLock.lock();
            TransactionLogsUtility.deleteLogsIfPossible(xid, transactionsAndLogsOccupied, transactionLogsAndOpenTransactions, currentLogIndex, transactionLogBaseName,
                    xaFileSystem.createDurableDiskSession());
            transactionsAndLogsOccupied.remove(xid);
        } finally {
            transactionLogLock.unlock();
        }
        transactionSubmittedBuffers.remove(xid);
    }

    private void ensureLogFileCapacity(long sizeToWriteNow) throws IOException {
        if (transactionLogChannel.size() + sizeToWriteNow > transactionLogFileMaxSize) {
            File nextTransactionLog = null;
            for (int i = currentLogIndex + 1; i < Integer.MAX_VALUE; i++) {
                File f = new File(transactionLogBaseName + "_" + i);
                if (f.exists()) {
                    continue;
                }
                nextTransactionLog = f;
                transactionLogChannel.close();
                transactionLogChannel =
                        new FileOutputStream(nextTransactionLog, true).getChannel();
                currentLogIndex = i;
                recordAllActivationsInNewLog();
                break;
            }
            if (nextTransactionLog == null) {
                throw new IOException("Transaction logs seems to be over...cannot proceed.");
            }
        }
    }

    private void recordAllActivationsInNewLog() throws IOException {
        for (EndPointActivation activation : xaFileSystem.getAllActivations()) {
            //note that this solution means duplicate entries for a single activation; eg one entry
            //in log 3 and other in 4 when log 4 is allocated. The other solution where we
            //keep entries as such and carry-them-over during log deletion is not full-proof
            //because it also means a crash can happen during transfer of entries from log-be-deleted
            //to current log and this implies duplicate entries or absent entries.
            //With the current solution though, we will have to be careful during activations
            //after recovery to avoid duplicates.
            //Current solution is safe because it works even for a case when a log get deleted
            //because we never delete the "current" log and we can see that
            //during current log creation we save all activation "during" that time, implying
            //that it also includes activations in the log we are deleting because
            //once the current log got created all activation onwards get anyway persisted in the
            //current log.
            recordEndPointActivation(activation);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy