/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.ignite.internal.util.offheap.unsafe; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import org.apache.ignite.internal.util.tostring.GridToStringExclude; import org.apache.ignite.internal.util.tostring.GridToStringInclude; import org.apache.ignite.internal.util.typedef.internal.S; /** * Guards concurrent operations on offheap memory to make sure that no thread will access already deallocated pointer. * Typical usage is: * <pre> * guard.begin(); * * try { * guard.releaseLater(x); * } * finally { * guard.end(); * } * </pre> * * while another thread can safely read the memory being deallocated: * <pre> * guard.begin(); * * try { * mem.readLong(x.getPointer()); * } * finally { * guard.end(); * } * </pre> */ public class GridUnsafeGuard { /** */ @GridToStringInclude private final AtomicReference<Operation> head = new AtomicReference<>(); /** */ @GridToStringInclude private final AtomicReference<Operation> tail = new AtomicReference<>(); /** */ @GridToStringExclude private final ThreadLocal<Operation> currOp = new ThreadLocal<>(); /** * Initialize head and tail with fake operation to avoid {@code null} handling. */ { Operation fake = new Operation(); fake.allowDeallocate(); head.set(fake); tail.set(fake); } /** * Begins concurrent memory operation. */ public void begin() { Operation op = currOp.get(); if (op != null) { op.reentries++; return; } op = new Operation(); currOp.set(op); for (;;) { Operation prev = head.get(); op.previous(prev); if (head.compareAndSet(prev, op)) { prev.next(op); break; } } } /** * Ends concurrent memory operation and releases resources. */ public void end() { Operation op = currOp.get(); assert op != null : "must be called after begin in the same thread"; if (op.reentries != 0) { assert op.reentries > 0 : op.reentries; op.reentries--; return; } currOp.remove(); op.allowDeallocate(); // Start deallocating from tail. op = tail.get(); int state; // Go through the inactive ops and try to deallocate. while ((state = op.state) != Operation.STATE_ACTIVE) { if (state == Operation.STATE_MAY_DEALLOCATE) op.finish(); // We need to keep op non-null, so don't use op = op.next; Operation next = op.next; if (next == null) break; op = next; } // Move tail forward. for (;;) { Operation t = tail.get(); if (op.id <= t.id || tail.compareAndSet(t, op)) break; } } /** * Releases memory in the future when it will be safe to do that. * Gives no guarantees in which thread it will be executed. * * @param compound Compound memory. */ public void releaseLater(GridUnsafeCompoundMemory compound) { assert currOp.get() != null : "must be called in begin-end block"; head.get().add(compound); } /** * Does finalization when it will be safe to deallocate offheap memory. * Gives no guarantees in which thread it will be executed. Gives * no guarantees about execution order of multiple passed finalizers as well. * * @param finalizer Finalizer. */ public void finalizeLater(Runnable finalizer) { assert currOp.get() != null : "must be called in begin-end block"; head.get().add(new Finalizer(finalizer)); } /** {@inheritDoc} */ @Override public String toString() { return S.toString(GridUnsafeGuard.class, this, "currOp", currOp.get()); } /** * Memory operation which can be executed in parallel with other memory operations. */ @SuppressWarnings("UnusedDeclaration") private static class Operation { /** */ private static final int STATE_ACTIVE = 0; /** */ private static final int STATE_MAY_DEALLOCATE = 1; /** */ private static final int STATE_DEALLOCATED = 2; /** */ private static final AtomicReferenceFieldUpdater<Operation, Finalizer> finUpdater = AtomicReferenceFieldUpdater.newUpdater(Operation.class, Finalizer.class, "finHead"); /** */ private static final AtomicReferenceFieldUpdater<Operation, GridUnsafeCompoundMemory> compoundUpdater = AtomicReferenceFieldUpdater.newUpdater(Operation.class, GridUnsafeCompoundMemory.class, "compound"); /** */ private static final AtomicIntegerFieldUpdater<Operation> stateUpdater = AtomicIntegerFieldUpdater.newUpdater(Operation.class, "state"); /** */ private long id; /** Reentries of the owner thread. */ private int reentries; /** */ private volatile int state; /** */ private volatile Finalizer finHead; /** */ private volatile GridUnsafeCompoundMemory compound; /** */ @GridToStringExclude private volatile Operation next; /** * Adds runnable to the finalization queue. * * @param fin Finalizer. */ private void add(Finalizer fin) { for(;;) { Finalizer prev = finHead; fin.previous(prev); if (finUpdater.compareAndSet(this, prev, fin)) break; } } /** * Finish operation and release memory. */ private void finish() { if (!stateUpdater.compareAndSet(this, STATE_MAY_DEALLOCATE, STATE_DEALLOCATED)) return; GridUnsafeCompoundMemory c = compound; if (c != null) { c.deallocate(); compoundUpdater.lazySet(this, null); } Finalizer fin = finHead; if (fin != null) { // Need to nullify because last deallocated operation object is still kept in memory. finUpdater.lazySet(this, null); do { fin.run(); fin = fin.previous(); } while(fin != null); } } /** * @return {@code true} If memory for this operation was already deallocated. */ private boolean deallocated() { return state == STATE_DEALLOCATED; } /** * Adds compound memory for deallocation. * * @param c Compound memory. */ private void add(GridUnsafeCompoundMemory c) { GridUnsafeCompoundMemory existing = compound; if (existing == null) { if (compoundUpdater.compareAndSet(this, null, c)) return; existing = compound; } existing.merge(c); } /** * @param prev Previous operation. */ private void previous(Operation prev) { id = prev.id + 1; } /** * Sets flag indicating if memory may be deallocated for this operation. */ private void allowDeallocate() { stateUpdater.lazySet(this, STATE_MAY_DEALLOCATE); } /** * @param next Next operation. */ private void next(Operation next) { this.next = next; } /** {@inheritDoc} */ @Override public String toString() { Operation next0 = next; return S.toString(Operation.class, this, "identity", System.identityHashCode(this), "next", next0 == null ? null : Math.random() < 0.03 ? "other..." : next0); } } /** * Finalizer. */ private static class Finalizer { /** */ private Finalizer prev; /** */ private final Runnable delegate; /** * @param delegate Actual finalizer. */ private Finalizer(Runnable delegate) { assert delegate != null; this.delegate = delegate; } /** * @return Previous finalizer. */ private Finalizer previous() { return prev; } /** * @param prev Previous finalizer. */ private void previous(Finalizer prev) { this.prev = prev; } /** * Run finalization. */ private void run() { delegate.run(); } /** {@inheritDoc} */ @Override public String toString() { return S.toString(Finalizer.class, this); } } }