mirror of
https://github.com/OpenHFT/Chronicle-Map.git
synced 2021-09-13 01:26:12 +00:00
Move all ChronicleMap closing logic into independent ChronicleHashResources. Reference contexts from ThreadLocals via ContextHolders and clear references to contexts on closing ChronicleMap. Make some closing procedures more paranoid than they were. Cache ChronicleMap's identityString and always return the same String from toIdentityString(). Add Throwables.returnOrSuppress().
This commit is contained in:
@@ -17,26 +17,48 @@
|
||||
|
||||
package net.openhft.chronicle.hash.impl;
|
||||
|
||||
import net.openhft.chronicle.hash.ChronicleHashClosedException;
|
||||
import net.openhft.chronicle.hash.impl.stage.hash.ChainingInterface;
|
||||
import net.openhft.chronicle.hash.impl.util.Throwables;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.io.Closeable;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* ChronicleHashResources is Runnable to be passed as "hunk" to {@link sun.misc.Cleaner}.
|
||||
*/
|
||||
public abstract class ChronicleHashResources implements Runnable {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ChronicleHashResources.class);
|
||||
|
||||
List<MemoryResource> memoryResources = new ArrayList<>(1);
|
||||
/**
|
||||
* closed field is volatile and {@link #closed()} accessor is not synchronized, because it
|
||||
* is called on each ChronicleMap access from all threads, synchronization would make closed()
|
||||
* a bottleneck.
|
||||
*/
|
||||
private volatile boolean closed = false;
|
||||
private List<MemoryResource> memoryResources = new ArrayList<>();
|
||||
private List<Closeable> closeables = new ArrayList<>(1);
|
||||
private ArrayList<WeakReference<ContextHolder>> contexts = new ArrayList<>();
|
||||
/**
|
||||
* Identity String of the ChronicleHash, for which this ChronicleHashResources is created.
|
||||
* ChronicleHash couldn't be directly referenced, because it's a hunk for {@link
|
||||
* sun.misc.Cleaner}, and it would prevent the chronicleHash from ever becoming unreachable.
|
||||
*/
|
||||
private String chronicleHashIdentityString;
|
||||
|
||||
public final synchronized void addMemoryResource(long address, long size) {
|
||||
if (memoryResources == null)
|
||||
throw new IllegalStateException("Already released");
|
||||
memoryResources.add(new MemoryResource(address, size));
|
||||
List<MemoryResource> memoryResources() {
|
||||
return memoryResources;
|
||||
}
|
||||
|
||||
public final synchronized long totalMemory() {
|
||||
if (memoryResources == null)
|
||||
List<WeakReference<ContextHolder>> contexts() {
|
||||
return contexts;
|
||||
}
|
||||
|
||||
final synchronized long totalMemory() {
|
||||
if (closed)
|
||||
return 0L;
|
||||
long totalMemory = 0L;
|
||||
for (MemoryResource memoryResource : memoryResources) {
|
||||
@@ -45,18 +67,183 @@ public abstract class ChronicleHashResources implements Runnable {
|
||||
return totalMemory;
|
||||
}
|
||||
|
||||
abstract Throwable doRelease();
|
||||
final boolean closed() {
|
||||
return closed;
|
||||
}
|
||||
|
||||
public final synchronized void releaseManually() {
|
||||
if (memoryResources == null)
|
||||
return; // Already released
|
||||
private void checkOpen() {
|
||||
if (closed)
|
||||
throw new ChronicleHashClosedException(chronicleHashIdentityString);
|
||||
}
|
||||
|
||||
public final synchronized void setChronicleHashIdentityString(
|
||||
String chronicleHashIdentityString) {
|
||||
this.chronicleHashIdentityString = chronicleHashIdentityString;
|
||||
}
|
||||
|
||||
final synchronized void addMemoryResource(long address, long size) {
|
||||
checkOpen();
|
||||
memoryResources.add(new MemoryResource(address, size));
|
||||
}
|
||||
|
||||
public final synchronized void addCloseable(Closeable closeable) {
|
||||
checkOpen();
|
||||
closeables.add(closeable);
|
||||
}
|
||||
|
||||
final synchronized void addContext(ContextHolder contextHolder) {
|
||||
checkOpen();
|
||||
expungeStateContexts();
|
||||
contexts.add(new WeakReference<>(contextHolder));
|
||||
}
|
||||
|
||||
private void expungeStateContexts() {
|
||||
contexts.removeIf(ref -> {
|
||||
ContextHolder contextHolder = ref.get();
|
||||
return contextHolder == null || !contextHolder.get().owner().isAlive();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This run() method is called from {@link sun.misc.Cleaner}, hence must not fail
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
Throwable thrown = null;
|
||||
try {
|
||||
thrown = doRelease();
|
||||
if (closed)
|
||||
return;
|
||||
try {
|
||||
LOG.error("{} not closed manually, cleaned up from Cleaner",
|
||||
chronicleHashIdentityString);
|
||||
} catch (Throwable t) {
|
||||
thrown = t;
|
||||
} finally {
|
||||
synchronized (this) {
|
||||
if (closed) {
|
||||
LOG.error("Somebody closed {} while it is processed by Cleaner, " +
|
||||
"this should be impossible", chronicleHashIdentityString);
|
||||
} else {
|
||||
thrown = Throwables.returnOrSuppress(thrown, releaseEverything());
|
||||
}
|
||||
}
|
||||
if (thrown != null) {
|
||||
try {
|
||||
LOG.error("Error on releasing resources of " + chronicleHashIdentityString,
|
||||
thrown);
|
||||
} catch (Throwable t) {
|
||||
// This may occur if we are in shutdown hooks, and the log service has
|
||||
// already been shut down. Try to fall back to printStackTrace().
|
||||
thrown.addSuppressed(t);
|
||||
thrown.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignore) {
|
||||
// just don't fail anyway
|
||||
}
|
||||
}
|
||||
|
||||
abstract Throwable releaseSystemResources();
|
||||
|
||||
public final boolean releaseManually() {
|
||||
if (closed)
|
||||
return false;
|
||||
synchronized (this) {
|
||||
if (closed)
|
||||
return false;
|
||||
Throwable thrown = releaseEverything();
|
||||
if (thrown != null)
|
||||
throw Throwables.propagate(thrown);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private Throwable releaseEverything() {
|
||||
// It's important to set closed = true before calling closeContexts(), to ensure that
|
||||
// threads which access this chronicleHash for the first time concurrently with
|
||||
// chronicleHash.close() will either fail to register the context via addContext() (because
|
||||
// checkOpen() is called there), or either the registered contexts will be visible and
|
||||
// closed in closeContexts()
|
||||
closed = true;
|
||||
|
||||
// Paranoiac mode: methods closeContexts(), closeCloseables(), releaseSystemResources() and
|
||||
// Throwables.returnOrSuppress() should never throw any throwables (the first three should
|
||||
// return them instead of throwing), but we don't trust ourselves and wrap every call into
|
||||
// it's own try-finally block
|
||||
try {
|
||||
Throwable thrown = null;
|
||||
try {
|
||||
thrown = closeContexts();
|
||||
} finally {
|
||||
try {
|
||||
thrown = Throwables.returnOrSuppress(thrown, closeCloseables());
|
||||
} finally {
|
||||
// It's important to close the system resources after contexts and closeables
|
||||
// because the latter may use those system resources
|
||||
thrown = Throwables.returnOrSuppress(thrown, releaseSystemResources());
|
||||
}
|
||||
}
|
||||
return thrown;
|
||||
} finally {
|
||||
// Make GC life easier
|
||||
closeables = null;
|
||||
contexts = null;
|
||||
memoryResources = null;
|
||||
}
|
||||
if (thrown != null)
|
||||
throw Throwables.propagate(thrown);
|
||||
}
|
||||
|
||||
private Throwable closeContexts() {
|
||||
Throwable thrown = null;
|
||||
// Indexed loop instead of for-each because we may be in the context of cleaning up after
|
||||
// OutOfMemoryError, and allocating Iterator object may lead to another OutOfMemoryError, so
|
||||
// we try to avoid any allocations
|
||||
//noinspection ForLoopReplaceableByForEach
|
||||
for (int i = 0; i < contexts.size(); i++) {
|
||||
WeakReference<ContextHolder> contextHolderRef = contexts.get(i);
|
||||
try {
|
||||
ContextHolder contextHolder = contextHolderRef.get();
|
||||
if (contextHolder != null) {
|
||||
closeContext(contextHolder);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
thrown = Throwables.returnOrSuppress(thrown, t);
|
||||
}
|
||||
}
|
||||
return thrown;
|
||||
}
|
||||
|
||||
private void closeContext(ContextHolder contextHolder) {
|
||||
ChainingInterface context = contextHolder.get();
|
||||
try {
|
||||
if (context.owner().isAlive()) {
|
||||
// Ensures that if the thread owning this context will come to access chronicleHash
|
||||
// concurrently with resource releasing operation, it will fail due to the check in
|
||||
// context.lockContextLocally() method. If the thread owning this context is
|
||||
// currently accessing chronicleHash, closeContext() will spin-wait until the end of
|
||||
// this access session.
|
||||
context.closeContext(chronicleHashIdentityString);
|
||||
}
|
||||
} finally {
|
||||
// See ContextHolder's class-level Javadoc comment which explains motivation for this
|
||||
contextHolder.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private Throwable closeCloseables() {
|
||||
Throwable thrown = null;
|
||||
// Indexed loop instead of for-each because we may be in the context of cleaning up after
|
||||
// OutOfMemoryError, and allocating Iterator object may lead to another OutOfMemoryError, so
|
||||
// we try to avoid any allocations
|
||||
//noinspection ForLoopReplaceableByForEach
|
||||
for (int i = 0; i < closeables.size(); i++) {
|
||||
Closeable closeable = closeables.get(i);
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (Throwable t) {
|
||||
thrown = Throwables.returnOrSuppress(thrown, t);
|
||||
}
|
||||
}
|
||||
return thrown;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user