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

io.questdb.cairo.TxnScoreboard Maven / Gradle / Ivy

/*******************************************************************************
 *     ___                  _   ____  ____
 *    / _ \ _   _  ___  ___| |_|  _ \| __ )
 *   | | | | | | |/ _ \/ __| __| | | |  _ \
 *   | |_| | |_| |  __/\__ \ |_| |_| | |_) |
 *    \__\_\\__,_|\___||___/\__|____/|____/
 *
 *  Copyright (c) 2014-2019 Appsicle
 *  Copyright (c) 2019-2022 QuestDB
 *
 *  Licensed 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 io.questdb.cairo;

import io.questdb.log.Log;
import io.questdb.log.LogFactory;
import io.questdb.std.*;
import io.questdb.std.str.LPSZ;
import io.questdb.std.str.Path;

import java.io.Closeable;

public class TxnScoreboard implements Closeable, Mutable {

    private static final Log LOG = LogFactory.getLog(TxnScoreboard.class);
    private final int pow2EntryCount;
    private final long size;
    private final FilesFacade ff;
    private long fd = -1;
    private long mem;

    public TxnScoreboard(FilesFacade ff, int entryCount) {
        this.ff = ff;
        this.pow2EntryCount = Numbers.ceilPow2(entryCount);
        this.size = TxnScoreboard.getScoreboardSize(pow2EntryCount);
    }

    public static native long getScoreboardSize(int entryCount);

    public boolean acquireTxn(long txn) {
        assert txn > -1;
        final long internalTxn = toInternalTxn(txn);
        final long response = acquireTxn(mem, internalTxn);
        if (response == 0) {
            // all good
            return true;
        }
        if (response == -1) {
            // retry
            return false;
        }
        final long min = fromInternalTxn(-response - 2);
        throw CairoException.instance(0).put("max txn-inflight limit reached [txn=").put(txn).put(", min=").put(min).put(", size=").put(pow2EntryCount).put(']');
    }

    @Override
    public void clear() {
        // Do full close, all memory used is native but instance will be reusable
        close();
    }

    @Override
    public void close() {
        if (mem != 0) {
            ff.munmap(mem, size, MemoryTag.MMAP_DEFAULT);
            mem = 0;
        }

        if (fd != -1) {
            ff.close(fd);
            fd = -1;
        }
    }

    public long getActiveReaderCount(long txn) {
        return getCount(mem, toInternalTxn(txn));
    }

    public int getEntryCount() {
        return pow2EntryCount;
    }

    public long getMin() {
        final long min = getMin(mem);
        // min can be 0 on empty scoreboard, so we simply treat it as txn 0.
        if (min == 0) {
            return 0;
        }
        return fromInternalTxn(min);
    }

    public boolean isRangeAvailable(long fromTxn, long toTxn) {
        return isRangeAvailable0(mem, toInternalTxn(fromTxn), toInternalTxn(toTxn));
    }

    public boolean isTxnAvailable(long txn) {
        return getActiveReaderCount(txn) == 0;
    }

    public TxnScoreboard ofRO(@Transient Path root) {
        clear();
        int rootLen = root.length();
        root.concat(TableUtils.TXN_SCOREBOARD_FILE_NAME).$();
        this.fd = openCleanRW(ff, root, this.size);
        try {
            this.mem = TableUtils.mapRO(ff, fd, this.size, MemoryTag.MMAP_DEFAULT);
        } catch (Throwable e) {
            ff.close(fd);
            root.trimTo(rootLen);
            fd = -1;
            throw e;
        }
        return this;
    }

    public TxnScoreboard ofRW(@Transient Path root) {
        clear();
        root.concat(TableUtils.TXN_SCOREBOARD_FILE_NAME).$();
        this.fd = openCleanRW(ff, root, this.size);

        // truncate is required to give file a size
        // allocate above does not seem to update file system's size entry
        ff.truncate(fd, this.size);
        try {
            this.mem = TableUtils.mapRW(ff, fd, this.size, MemoryTag.MMAP_DEFAULT);
            init(mem, pow2EntryCount);
        } catch (Throwable e) {
            ff.close(fd);
            fd = -1;
            throw e;
        }
        return this;
    }

    public long releaseTxn(long txn) {
        long released = releaseTxn(mem, txn);
        assert released > -1 : "released count " + txn + " must be positive: " + (released + 1);
        return released;
    }

    /**
     * Table readers use 0 txn as the empty table transaction number.
     * The scoreboard only supports txn > 0, so we have to patch the value
     * to avoid races in the scoreboard initialization.
     */
    private static long toInternalTxn(long txn) {
        return txn + 1;
    }

    /**
     * Reverts toInternalTxn() value.
     */
    private static long fromInternalTxn(long txn) {
        return txn - 1;
    }

    private static long acquireTxn(long pTxnScoreboard, long txn) {
        assert pTxnScoreboard > 0;
        LOG.debug().$("acquire [p=").$(pTxnScoreboard).$(", txn=").$(fromInternalTxn(txn)).$(']').$();
        return acquireTxn0(pTxnScoreboard, txn);
    }

    private static long releaseTxn(long pTxnScoreboard, long txn) {
        assert pTxnScoreboard > 0;
        LOG.debug().$("release  [p=").$(pTxnScoreboard).$(", txn=").$(txn).$(']').$();
        final long internalTxn = toInternalTxn(txn);
        return releaseTxn0(pTxnScoreboard, internalTxn);
    }

    private native static long acquireTxn0(long pTxnScoreboard, long txn);

    private native static long releaseTxn0(long pTxnScoreboard, long txn);

    private native static boolean isRangeAvailable0(long pTxnScoreboard, long txnFrom, long txnTo);

    private static native long getCount(long pTxnScoreboard, long txn);

    private static native long getMin(long pTxnScoreboard);

    private static native void init(long pTxnScoreboard, int entryCount);

    static long openCleanRW(FilesFacade ff, LPSZ path, long size) {
        final long fd = ff.openCleanRW(path, size);
        if (fd > -1) {
            LOG.debug().$("open clean [file=").$(path).$(", fd=").$(fd).$(']').$();
            return fd;
        }
        throw CairoException.instance(ff.errno()).put("could not open read-write with clean allocation [file=").put(path).put(']');
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy