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

com.kolibrifx.plovercrest.server.internal.engine.DeferredTableMapper Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2010-2017, KolibriFX AS. Licensed under the Apache License, version 2.0.
 */

package com.kolibrifx.plovercrest.server.internal.engine;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import com.kolibrifx.plovercrest.client.TableClosedException;
import com.kolibrifx.plovercrest.server.Table;

public class DeferredTableMapper implements CacheEntry {
    private TableMapper mapper = null;
    private final Object mapperLock = new Object();
    private final AtomicBoolean closed = new AtomicBoolean(false);

    private final Table table;
    private volatile long age;
    private final LeastRecentlyUsedCache cache;
    private final AtomicLong lastValidTimestamp = new AtomicLong(-1);

    public DeferredTableMapper(final Table table, final LeastRecentlyUsedCache cache) {
        this.table = table;
        this.cache = cache;
    }

    TableMapper getMapper() {
        // Here, the synchronization might become a problem... find a way to avoid it?
        // Volatile + double-checked locking might work, except that we also need close() + getMapper() to be thread-safe.
        synchronized (mapperLock) {
            if (mapper == null) {
                throwIfClosed();
                mapper = new TableMapper(table, lastValidTimestamp);
                cache.add(this);
            }
            recordUse();
            return mapper;
        }
    }

    void recordUse() {
        age = cache.nextAge();
    }

    void closeMapper(final boolean flush) {
        // potentially slow synchronization here, but this is not called often, so probably okay
        synchronized (mapperLock) {
            if (mapper != null) {
                mapper.close(flush);
                mapper = null;
            }
        }
    }

    private void throwIfClosed() {
        if (closed.get()) {
            throw new TableClosedException(table.getName());
        }
    }

    public void close(final boolean flush) {
        if (!closed.compareAndSet(false, true)) {
            return;
        }
        closeMapper(flush);
        cache.entryClosed(this);
    }

    public boolean isClosed() {
        return closed.get();
    }

    private void handleTableClosed(final TableClosedException e) {
        if (closed.get()) {
            // the table really is closed, so rethrow
            throw e;
        }
        // the table mapper has been evicted, so do not rethrow, the caller should recreate the TableMapper
    }

    public void write(final long timestamp, final ByteBuffer buffer) throws IOException {
        // Synchronize while writing, to avoid eviction during write, which could cause an incomplete element to be written.
        // Thus, we should not have to catch TableLockedException here.
        synchronized (mapperLock) {
            getMapper().write(timestamp, buffer);
        }
    }

    public void force() {
        synchronized (mapperLock) {
            getMapper().force();
        }
    }

    public long getFirstTimestamp() {
        while (true) {
            try {
                return getMapper().getFirstTimestamp();
            } catch (final TableClosedException e) {
                handleTableClosed(e);
            }
        }
    }

    public long getLastTimestamp() {
        while (true) {
            try {
                return getMapper().getLastTimestamp();
            } catch (final TableClosedException e) {
                handleTableClosed(e);
            }
        }
    }

    public long getDataLength() {
        while (true) {
            try {
                return getMapper().getDataLength();
            } catch (final TableClosedException e) {
                handleTableClosed(e);
            }
        }
    }

    public long getEntryCount() {
        while (true) {
            try {
                return getMapper().getEntryCount();
            } catch (final TableClosedException e) {
                handleTableClosed(e);
            }
        }
    }

    public long getLastValidTimestamp() {
        return Math.max(getLastTimestamp(), lastValidTimestamp.get());
    }

    public boolean updateLastValidTimestamp(final long timestamp) {
        final boolean changed = lastValidTimestamp.get() != timestamp;
        lastValidTimestamp.set(timestamp);
        return changed;
    }

    public boolean isFrozen() {
        while (true) {
            try {
                return getMapper().isFrozen();
            } catch (final TableClosedException e) {
                handleTableClosed(e);
            }
        }
    }

    public boolean markAsFrozen() {
        while (true) {
            try {
                return getMapper().markAsFrozen();
            } catch (final TableClosedException e) {
                handleTableClosed(e);
            }
        }
    }

    public void unfreeze() {
        while (true) {
            try {
                getMapper().unfreeze();
                return;
            } catch (final TableClosedException e) {
                handleTableClosed(e);
            }
        }
    }

    @Override
    public String getKey() {
        return table.getName();
    }

    @Override
    public long getAge() {
        return age;
    }

    @Override
    public void invalidate() {
        closeMapper(true);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy