Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2012-2018 Chronicle Map Contributors
*
* 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 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.io.Closeable;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
/**
* 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);
private static final int OPEN = 0;
private static final int PARTIALLY_CLOSED = 1;
private static final int COMPLETELY_CLOSED = 2;
/**
* This field is volatile and {@link #closed()} accessor is not synchronized, because it
* is called on each {@link VanillaChronicleHash} access from all threads, synchronization would
* make {@code closed()} a bottleneck.
*/
private volatile int state = OPEN;
private List memoryResources = new ArrayList<>();
private List closeables = new ArrayList<>(1);
private List> contexts = new ArrayList<>();
/**
* Identity String of the ChronicleHash, for which this ChronicleHashResources is created.
* ChronicleHash couldn't be directly referenced, because {@code ChronicleHashResources} is a
* hunk for {@link sun.misc.Cleaner}, and it would prevent the chronicleHash from ever becoming
* unreachable.
*/
private String chronicleHashIdentityString;
List> contexts() {
return contexts;
}
final synchronized long totalMemory() {
if (closed())
return 0L;
long totalMemory = 0L;
//noinspection ForLoopReplaceableByForEach -- allocation-free looping
for (int i = 0; i < memoryResources.size(); i++) {
totalMemory += memoryResources.get(i).size;
}
return totalMemory;
}
final boolean closed() {
return state != OPEN;
}
private void checkOpen() {
if (closed())
throw new ChronicleHashClosedException(chronicleHashIdentityString);
}
/**
* Need to set {@link #chronicleHashIdentityString} via this setter rather than once in the
* constructor, because {@code ChronicleHashResources} instance is created before it's
* {@link net.openhft.chronicle.hash.ChronicleHash}.
*/
public final synchronized void setChronicleHashIdentityString(
String chronicleHashIdentityString) {
checkOpen();
this.chronicleHashIdentityString = chronicleHashIdentityString;
}
final synchronized void addMemoryResource(long address, long size) {
checkOpen();
memoryResources.add(new MemoryResource(address, size));
}
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 {
if (state == COMPLETELY_CLOSED)
return;
try {
LOG.error("{} is not closed manually, cleaned up from Cleaner",
chronicleHashIdentityString);
} catch (Throwable t) {
thrown = t;
} finally {
synchronized (this) {
if (state == COMPLETELY_CLOSED) {
LOG.error("Somebody closed {} while it is processed by Cleaner, " +
"this should be impossible", chronicleHashIdentityString);
} else {
thrown = Throwables.returnOrSuppress(thrown, releaseEverything(true));
}
}
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. We will have another attempt to close this ChronicleMap from
// ChronicleHashCloseOnExitHook.
}
}
abstract void releaseMemoryResource(MemoryResource memoryResource) throws IOException;
Throwable releaseExtraSystemResources() {
// nothing to release
return null;
}
public final boolean releaseManually() {
if (state == COMPLETELY_CLOSED)
return false;
synchronized (this) {
if (state == COMPLETELY_CLOSED)
return false;
Throwable thrown = releaseEverything(false);
if (thrown != null)
throw Throwables.propagate(thrown);
return true;
}
}
private Throwable releaseEverything(boolean releaseFromCleaner) {
// It's important to set state = PARTIALLY_CLOSED 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(). Also, it allows to not care about null elements of
// memoryResources, contexts and closeables lists in addMemoryResource(), addContext() and
// addCloseable() respectively.
state = PARTIALLY_CLOSED;
// Paranoiac mode: methods closeContexts(), closeCloseables(), releaseMemoryResources(),
// releaseExtraSystemResources() 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.
Throwable thrown = null;
try {
if (contexts != null)
thrown = closeContexts();
} catch (Throwable t) {
thrown = t;
} finally {
try {
if (closeables != null)
thrown = Throwables.returnOrSuppress(thrown, closeCloseables());
} catch (Throwable t) {
try {
thrown = Throwables.returnOrSuppress(thrown, t);
} catch (Throwable stackOverflowError) {
// It seems that only StackOverflowError could be thrown from
// Throwables.returnOrSuppress(), but in this case `thrown` and `t` are likely
// StackOverflowErrors too, so just return one of them without trying to call
// any more methods, including Throwable.addSuppressed().
thrown = thrown != null ? thrown : t;
}
}
}
// It's important to close the system resources only after contexts and closeables are
// closed successfully, because the latter may use those system resources. However if
// releaseEverything() is called from Cleaner, the ChronicleMap object is already
// unreachable, that means no contexts are accessing it anymore and the problem with closing
// contexts could probably be StackOverflowError or other exotic Error, so in this case we
// try to close the system resources anyway.
if (thrown == null || releaseFromCleaner) {
try {
thrown = releaseSystemResources(thrown);
} catch (Throwable stackOverflowError) {
// It seems that only StackOverflowError could be thrown (rather than returned!)
// from releaseSystemResources(), so just return any Throwable from
// releaseEverything() without trying to call any more methods.
thrown = thrown != null ? thrown : stackOverflowError;
}
}
if (thrown == null) {
state = COMPLETELY_CLOSED;
}
return thrown;
}
private Throwable releaseSystemResources(Throwable thrown) {
try {
if (memoryResources != null)
thrown = Throwables.returnOrSuppress(thrown, releaseMemoryResources());
} catch (Throwable t) {
thrown = Throwables.returnOrSuppress(thrown, t);
} finally {
try {
thrown = Throwables.returnOrSuppress(thrown, releaseExtraSystemResources());
} catch (Throwable t) {
try {
thrown = Throwables.returnOrSuppress(thrown, t);
} catch (Throwable stackOverflowError) {
// It seems that only StackOverflowError could be thrown from
// Throwables.returnOrSuppress(), but in this case `thrown` and `t` are likely
// StackOverflowErrors too, so just return one of them without trying to call
// any more methods, including Throwable.addSuppressed().
thrown = thrown != null ? thrown : t;
}
}
}
return thrown;
}
private Throwable closeContexts() {
Throwable thrown = null;
List> contexts = this.contexts;
// Indexed loop instead of using Iterator 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.
for (int i = 0; i < contexts.size(); i++) {
WeakReference contextHolderRef = contexts.get(i);
// The context reference could have already been set to null in (*). See comments in
// closeContext() with more explanations.
if (contextHolderRef != null) {
try {
ContextHolder contextHolder = contextHolderRef.get();
if (contextHolder != null) {
closeContext(contextHolder);
}
// (*) Make GC life easier
contexts.set(i, null);
} catch (Throwable t) {
thrown = Throwables.returnOrSuppress(thrown, t);
}
}
}
// Forget about contexts only if all of them are successfully closed, i. e. no throwables
// were thrown in the above loop.
if (thrown == null) {
// Make GC life easier
this.contexts = null;
}
return thrown;
}
private void closeContext(ContextHolder contextHolder) {
ChainingInterface context = contextHolder.get();
// The context could have already been cleared, if this is the second attempt to close
// contexts, the first one failed e. g. with IllegalStateException on one of the contexts
// (see comment (*) below in this method), it could have succeed for some contexts and
// contextHolder.clear() is performed.
if (context != null) {
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);
}
// (*) Don't execute contextHolder.clear() from a finally section of a try block
// wrapping context.closeContext(), because if context.closeContext() fails e. g. with
// IllegalStateException because the context is currently used (this happens if
// ChronicleMap.close() is called within try-with-resources block of it's own operation,
// see MapCloseTest.closeInContextTest()), we may want to try to close this context
// again from ChronicleHashCloseOnExitHook.
// See ContextHolder's class-level Javadoc comment that explains the motivation for
// calling contextHolder.clear().
contextHolder.clear();
}
}
private Throwable closeCloseables() {
Throwable thrown = null;
List closeables = this.closeables;
// Indexed loop instead of using Iterator 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.
for (int i = 0; i < closeables.size(); i++) {
Closeable closeable = closeables.get(i);
// The closeable could have already been set to null in (*), see comments in
// closeContexts() method which has similar logic.
if (closeable != null) {
try {
closeable.close();
// (*) Make GC life easier
closeables.set(i, null);
} catch (Throwable t) {
thrown = Throwables.returnOrSuppress(thrown, t);
}
}
}
// Forget about closeables only if all of them are successfully closed, i. e. no throwables
// were thrown in the above loop.
if (thrown == null) {
// Make GC life easier
this.closeables = null;
}
return thrown;
}
private Throwable releaseMemoryResources() {
Throwable thrown = null;
List memoryResources = this.memoryResources;
// Indexed loop instead of using Iterator 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.
for (int i = 0; i < memoryResources.size(); i++) {
MemoryResource memoryResource = memoryResources.get(i);
// The memory resource could have already been nulled out in (*), see comments in
// closeCloseables() method which has similar logic.
if (memoryResource != null) {
try {
releaseMemoryResource(memoryResource);
// (*) Make GC life easier
memoryResources.set(i, null);
} catch (Throwable t) {
thrown = Throwables.returnOrSuppress(thrown, t);
}
}
}
// Forget about memory resources only if all of them are successfully released, i. e. no
// throwables were thrown in the above loop.
if (thrown == null) {
this.memoryResources = null;
}
return thrown;
}
}