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

com.simiacryptus.mindseye.lang.ReferenceCountingBase Maven / Gradle / Ivy

There is a newer version: 2.1.0
Show newest version
/*
 * Copyright (c) 2018 by Andrew Charneski.
 *
 * The author 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 com.simiacryptus.mindseye.lang;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.ByteArrayOutputStream;
import java.io.ObjectStreamException;
import java.io.PrintStream;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static java.util.concurrent.Executors.newFixedThreadPool;

/**
 * The base implementation for ReferenceCounting objects. Provides state management and debugging facilities. If
 * assertions are enabled, stack traces are recorded to provide detailed logs for debugging LifecycleExceptions.
 */
public abstract class ReferenceCountingBase implements ReferenceCounting {

  private static final Logger logger = LoggerFactory.getLogger(ReferenceCountingBase.class);
  private static final long LOAD_TIME = System.nanoTime();
  private static final UUID jvmId = UUID.randomUUID();
  private static final ExecutorService gcPool = newFixedThreadPool(1, new ThreadFactoryBuilder()
      .setDaemon(true).build());
  /**
   * The constant supressLog.
   */
  public static boolean supressLog = false;

  static {
    if (CoreSettings.INSTANCE() == null) throw new RuntimeException();
  }

  private transient final UUID objectId = CoreSettings.INSTANCE().isLifecycleDebug() ? UUID.randomUUID() : jvmId;
  private transient final AtomicInteger references = new AtomicInteger(1);
  private transient final AtomicBoolean isFreed = new AtomicBoolean(false);
  @Nullable
  private transient final StackTraceElement[] createdBy = CoreSettings.INSTANCE().isLifecycleDebug() ? Thread.currentThread().getStackTrace() : null;
  private transient final LinkedList addRefs = new LinkedList<>();
  private transient final LinkedList freeRefs = new LinkedList<>();
  private transient final LinkedList addRefObjs = new LinkedList<>();
  private transient final LinkedList freeRefObjs = new LinkedList<>();
  private transient volatile boolean isFinalized = false;
  private transient boolean detached = false;

  @Nonnull
  private static String getString(@Nullable StackTraceElement[] trace) {
    return null == trace ? "" : Arrays.stream(trace).map(x -> "at " + x).skip(2).reduce((a, b) -> a + "\n" + b).orElse("");
  }

  protected final Object readResolve() throws ObjectStreamException {
    return detach();
  }

  /**
   * Detail string string.
   *
   * @param obj           the obj
   * @param includeCaller the include caller
   * @return the string
   */
  public static CharSequence referenceReport(@Nonnull ReferenceCountingBase obj, boolean includeCaller) {
    return obj.referenceReport(includeCaller, obj.isFinalized());
  }

  /**
   * Remove suffix stack trace element [ ].
   *
   * @param stack  the stack
   * @param prefix the prefix
   * @return the stack trace element [ ]
   */
  public static StackTraceElement[] removeSuffix(final StackTraceElement[] stack, final Collection prefix) {
    return Arrays.stream(stack).limit(stack.length - prefix.size()).toArray(i -> new StackTraceElement[i]);
  }

  /**
   * Find common prefix list.
   *
   * @param reversedStacks the reversed stacks
   * @return the list
   */
  public static List findCommonPrefix(final List> reversedStacks) {
    if (0 == reversedStacks.size()) return null;
    List protoprefix = reversedStacks.get(0);
    for (int i = 0; i < protoprefix.size(); i++) {
      final int finalI = i;
      if (!reversedStacks.stream().allMatch(x -> x.size() > finalI && x.get(finalI).equals(protoprefix.get(finalI)))) {
        return protoprefix.subList(0, i);
      }
    }
    return protoprefix;
  }

  /**
   * Reverse copy list.
   *
   * @param  the type parameter
   * @param x   the x
   * @return the list
   */
  public static  List reverseCopy(final List x) {
    if (null == x) return Arrays.asList();
    return IntStream.range(0, x.size()).map(i -> (x.size() - 1) - i).mapToObj(i -> x.get(i)).collect(Collectors.toList());
  }

  /**
   * Reverse copy list.
   *
   * @param  the type parameter
   * @param x   the x
   * @return the list
   */
  public static  List reverseCopy(final T[] x) {
    return IntStream.range(0, x.length).map(i -> (x.length - 1) - i).mapToObj(i -> x[i]).collect(Collectors.toList());
  }

  @Override
  public int currentRefCount() {
    return references.get();
  }

  @Override
  public ReferenceCountingBase addRef() {
    addRef(this);
    return this;
  }

  @Override
  public void claimRef(ReferenceCounting obj) {
    assertAlive();
    synchronized (addRefObjs) {
      for (Iterator iterator = addRefObjs.iterator(); iterator.hasNext(); ) {
        final UUID addRefObj = iterator.next();
        if (addRefObj.equals(this.objectId)) {
          iterator.remove();
          addRefObjs.add(obj.getObjectId());
          return;
        }
      }
    }
    throw new IllegalStateException("No reference to claim found");
  }

  @Override
  public void addRef(ReferenceCounting obj) {
    assertAlive();
    if (references.incrementAndGet() <= 1) throw new IllegalStateException(referenceReport(true, isFinalized()));
    if (CoreSettings.INSTANCE().isLifecycleDebug()) {
      addRefs.add(Thread.currentThread().getStackTrace());
    }
    synchronized (addRefObjs) {
      addRefObjs.add(obj.getObjectId());
    }
  }

  public final boolean isFinalized() {
    return isFreed.get();
  }

  /**
   * Reference report string.
   *
   * @param includeCaller the include caller
   * @param isFinalized   the is finalized
   * @return the string
   */
  public String referenceReport(boolean includeCaller, boolean isFinalized) {
    @Nonnull ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    @Nonnull PrintStream out = new PrintStream(buffer);
    out.print(String.format("Object %s %s (%d refs, %d frees) ",
        getClass().getName(), getObjectId().toString(), 1 + addRefObjs.size(), freeRefObjs.size()));
    List prefix = reverseCopy(findCommonPrefix(Stream.concat(
        Stream.of(createdBy),
        Stream.concat(
            addRefs.stream(),
            freeRefs.stream()
        )
    ).filter(x -> x != null).map(x -> reverseCopy(x)).collect(Collectors.toList())));

    if (null != createdBy) {
      StackTraceElement[] trace = this.createdBy;
      //trace = removeSuffix(trace, prefix);
      out.println(String.format("created by \n\t%s",
          getString(trace).replaceAll("\n", "\n\t")));
    }
    synchronized (addRefObjs) {

      for (int i = 0; i < addRefObjs.size(); i++) {
        StackTraceElement[] stack = i < addRefs.size() ? addRefs.get(i) : new StackTraceElement[]{};
        stack = removeSuffix(stack, prefix);
        UUID linkObj = addRefObjs.get(i);
        CharSequence linkStr = this.equals(linkObj) ? "" : linkObj.toString();
        out.println(String.format("reference added by %s\n\t%s", linkStr,
            getString(stack).replaceAll("\n", "\n\t")));
      }
    }
    synchronized (freeRefObjs) {
      for (int i = 0; i < freeRefObjs.size() - (isFinalized ? 1 : 0); i++) {
        StackTraceElement[] stack = i < freeRefs.size() ? freeRefs.get(i) : new StackTraceElement[]{};
        stack = removeSuffix(stack, prefix);
        UUID linkObj = freeRefObjs.get(i);
        CharSequence linkStr = objectId == linkObj ? "" : linkObj.toString();
        out.println(String.format("reference removed by %s\n\t%s", linkStr,
            getString(stack).replaceAll("\n", "\n\t")));
      }
      if (isFinalized && 0 < freeRefs.size()) {
        UUID linkObj = freeRefObjs.isEmpty() ? objectId : freeRefObjs.get(freeRefObjs.size() - 1);
        CharSequence linkStr = objectId.equals(linkObj) ? "" : linkObj.toString();
        StackTraceElement[] stack = freeRefs.get(freeRefs.size() - 1);
        stack = removeSuffix(stack, prefix);
        out.println(String.format("freed by %s\n\t%s", linkStr,
            (0 == freeRefs.size() ? "" : getString(stack)).replaceAll("\n", "\n\t")));
      }
    }
    if (includeCaller) out.println(String.format("apply current stack \n\t%s",
        getString(Thread.currentThread().getStackTrace()).replaceAll("\n", "\n\t")));
    out.close();
    return buffer.toString();
  }

  /**
   * Assert alive.
   */
  public boolean assertAlive() {
    if (isFinalized) {
      throw new LifecycleException(this);
    }
    if (isFinalized()) {
      logger.warn(String.format("Using freed reference for %s", getClass().getSimpleName()));
      logger.warn(referenceReport(true, isFinalized()));
      throw new LifecycleException(this);
    }
    return true;
  }

  @Override
  public void freeRef() {
    freeRef(this);
  }

  @Override
  public void freeRefAsync() {
    gcPool.submit((Runnable) this::freeRef);
  }

  @Override
  public void freeRef(ReferenceCounting obj) {
    if (isFinalized) {
      //logger.debug("Object has been finalized");
      return;
    }
    int refs = references.decrementAndGet();
    if (refs < 0 && !detached) {
      logger.warn(String.format("Error freeing reference for %s", getClass().getSimpleName()));
      logger.warn(referenceReport(true, isFinalized()));
      throw new LifecycleException(this);
    }

    synchronized (freeRefObjs) {
      if (CoreSettings.INSTANCE().isLifecycleDebug()) freeRefs.add(Thread.currentThread().getStackTrace());
      freeRefObjs.add(obj.getObjectId());
    }
    if (refs == 0 && !detached) {
      if (!isFreed.getAndSet(true)) {
        try {
          _free();
        } catch (LifecycleException e) {
          logger.info("Error freeing resources: " + referenceReport(true, isFinalized()));
          throw e;
        }
      }
    }
  }

  /**
   * Free.
   */
  protected void _free() {
  }

  @Override
  protected final void finalize() {
    isFinalized = true;
    if (!isFreed.getAndSet(true)) {
      if (!isDetached() && !supressLog) {
        if (logger.isDebugEnabled()) {
          logger.debug(String.format("Instance Reclaimed by GC at %.9f: %s", (System.nanoTime() - LOAD_TIME) / 1e9, referenceReport(false, false)));
        }
      }
      synchronized (freeRefObjs) {
        if (CoreSettings.INSTANCE().isLifecycleDebug()) freeRefs.add(Thread.currentThread().getStackTrace());
        freeRefObjs.add(this.objectId);
      }
      _free();
    }
  }

  /**
   * Is floating boolean.
   *
   * @return the boolean
   */
  public boolean isDetached() {
    return detached;
  }

  /**
   * Sets floating.
   */
  public ReferenceCountingBase detach() {
    this.detached = true;
    return this;
  }

  @Override
  public UUID getObjectId() {
    return objectId;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy