com.kolibrifx.plovercrest.server.internal.engine.DeferredTableMapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of plovercrest-server Show documentation
Show all versions of plovercrest-server Show documentation
Plovercrest server library.
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);
}
}