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

com.sun.prism.impl.BaseResourcePool Maven / Gradle / Ivy

There is a newer version: 23-ea+3
Show newest version
/*
 * Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.prism.impl;

import java.lang.ref.WeakReference;

/**
 * The base implementation of the {@link ResourcePool} interface, providing
 * bookkeeping for the {@link managed()} method and support for sharing
 * resources amongst multiple pools.
 * @param  the type of objects stored in this resource pool
 */
public abstract class BaseResourcePool implements ResourcePool {
    // Number of calls to freeDisposalRequestedAndCheckResources() before we
    // consider a resource to have not been used in a hypothetical "FOREVER".
    private static final int FOREVER = 1024;
    // Number of calls to freeDisposalRequestedAndCheckResources() before we
    // consider a resource to have not been used "RECENTLY", with different
    // cutoffs for useful and unuseful textures.
    private static final int RECENTLY_USEFUL = 100;
    private static final int RECENT = 10;

    static interface Predicate {
        boolean test(ManagedResource mr);
    }

    private static final Predicate stageTesters[];
    private static final String    stageReasons[];
    static {
        stageTesters = new Predicate[6];
        stageReasons = new String[6];
        stageTesters[0] = (mr) -> { return !mr.isInteresting() && mr.getAge() > FOREVER; };
        stageReasons[0] = "Pruning unuseful older than "+FOREVER;
        stageTesters[1] = (mr) -> { return !mr.isInteresting() && mr.getAge() > FOREVER/2; };
        stageReasons[1] = "Pruning unuseful older than "+FOREVER/2;
        stageTesters[2] = (mr) -> { return !mr.isInteresting() && mr.getAge() > RECENT; };
        stageReasons[2] = "Pruning unuseful older than "+RECENT;
        stageTesters[3] = (mr) -> { return mr.getAge() > FOREVER; };
        stageReasons[3] = "Pruning all older than "+FOREVER;
        stageTesters[4] = (mr) -> { return mr.getAge() > FOREVER/2; };
        stageReasons[4] = "Pruning all older than "+FOREVER/2;
        stageTesters[5] = (mr) -> { return mr.getAge() > RECENTLY_USEFUL; };
        stageReasons[5] = "Pruning all older than "+RECENTLY_USEFUL;
    }

    long managedSize;
    final long origTarget;
    long curTarget;
    final long maxSize;
    final ResourcePool sharedParent;
    private final Thread managerThread;
    private WeakLinkedList resourceHead;

    protected BaseResourcePool(long target, long max) {
        this(null, target, max);
    }

    protected BaseResourcePool(ResourcePool parent) {
        this(parent, parent.target(), parent.max());
    }

    protected BaseResourcePool(ResourcePool parent, long target, long max) {
        this.resourceHead = new WeakLinkedList<>();
        this.sharedParent = parent;
        this.origTarget = this.curTarget = target;
        this.maxSize = ((parent == null)
                        ? max
                        : Math.min(parent.max(), max));
        managerThread = Thread.currentThread();
    }

    /**
     * Clean up the resources in the indicated pool using a standard
     * algorithm until at least the specified amount of resource units
     * have been reclaimed.
     * The standard algorithm uses the following stages until it obtains
     * enough room in the pool:
     * 
    *
  1. Prune any resources which are already free, but have not been * accounted for yet. *
  2. Go through a few passes cleaning out any non-interesting resources * that have not been used in a long time with decreasing cutoff * limits for the maximum age of the resource. *
  3. Go through more passes cleaning out even interesting resources that * have not been used in a fairly long time with decreasing age limits. *
  4. Attempt to grow the target to accommodate the new request. *
  5. Finally, prune any resources that are not currently in the process * of being used (i.e. locked or permanent). *
* * @param needed * @return boolean indicating if the requested space is now available */ public boolean cleanup(long needed) { if (used() + needed <= target()) return true; long wasused = used(); long wanted = target() / 16; if (wanted < needed) { wanted = needed; } if (PrismSettings.poolDebug) { System.err.printf("Need %,d (hoping for %,d) from pool: %s\n", needed, wanted, this); printSummary(false); } try { // First cleanup pass is just for previously freed resources that // are in the Disposer queue already or were manually freed by // mechanisms and are still in the accounting list. // The pruner predicate choose no additional resources to free. Disposer.cleanUp(); if (PrismSettings.poolDebug) System.err.println("Pruning obsolete in pool: "+this); cleanup((mr) -> { return false; }); if (used() + wanted <= target()) return true; // Multiple stages of pruning useful and unuseful resources of // various ages as determined by the static initializer above. for (int stage = 0; stage < stageTesters.length; stage++) { if (PrismSettings.poolDebug) { System.err.println(stageReasons[stage]+" in pool: "+this); } cleanup(stageTesters[stage]); if (used() + wanted <= target()) return true; } // Now look to grow the target if we can satisfy this allocation at // less than max(). long rem = max() - used(); if (wanted > rem) { wanted = needed; } if (wanted <= rem) { long grow = (max() - origTarget()) / 32; if (grow < wanted) { grow = wanted; } else if (grow > rem) { grow = rem; } setTarget(used() + grow); if (PrismSettings.poolDebug || PrismSettings.verbose) { System.err.printf("Growing pool %s target to %,d\n", this, target()); } return true; } // Finally, look to the garbage collector to dislodge some unreferenced // resources that we can free with a very aggressive age set of (0, 0) // which will target all unlocked/non-permanent textures. // Two tries, one with just a gc(), and a desperate one with a sleep... for (int i = 0; i < 2; i++) { pruneLastChance(i > 0); if (used() + needed <= max()) { if (used() + needed > target()) { setTarget(used() + needed); if (PrismSettings.poolDebug || PrismSettings.verbose) { System.err.printf("Growing pool %s target to %,d\n", this, target()); } } return true; } } // That was our last gasp, we either succeeded in making room under // the max() amount or we failed and need to return false. return false; } finally { if (PrismSettings.poolDebug) { System.err.printf("cleaned up %,d from pool: %s\n", wasused - used(), this); printSummary(false); System.err.println(); } } } private void pruneLastChance(boolean desperate) { System.gc(); if (desperate) { // Our alternative is to return false here and cause an allocation // failure which is usually bad news for any SG, so it is worth // sleeping on the second time around to give one last GC some time // to find a dead resource that was dropped on the floor... try { Thread.sleep(20); } catch (InterruptedException e) { } } Disposer.cleanUp(); if (PrismSettings.poolDebug) { if (desperate) { System.err.print("Last chance pruning"); } else { System.err.print("Pruning everything"); } System.err.println(" in pool: "+this); } cleanup((mr) -> { return true; }); } private void cleanup(Predicate predicate) { WeakLinkedList prev = resourceHead; WeakLinkedList cur = prev.next; while (cur != null) { ManagedResource mr = cur.getResource(); if (ManagedResource._isgone(mr)) { if (PrismSettings.poolDebug) showLink("unlinking", cur, false); recordFree(cur.size); cur = cur.next; prev.next = cur; } else if (!mr.isPermanent() && !mr.isLocked() && predicate.test(mr)) { if (PrismSettings.poolDebug) showLink("pruning", cur, true); mr.free(); mr.resource = null; recordFree(cur.size); cur = cur.next; prev.next = cur; } else { prev = cur; cur = cur.next; } } } static void showLink(String label, WeakLinkedList cur, boolean showAge) { ManagedResource mr = cur.getResource(); System.err.printf("%s: %s (size=%,d)", label, mr, cur.size); if (mr != null) { if (showAge) { System.err.printf(" (age=%d)", mr.getAge()); } if (mr.isPermanent()) System.err.print(" perm"); if (mr.isLocked()) System.err.print(" lock"); if (mr.isInteresting()) System.err.print(" int"); } System.err.println(); } /** * Check that all resources are in the correct state for an idle condition * and free any resources which were disposed from a non-resource thread. * This method must be called on a thread that is appropriate for disposing * and managing resources for the resource pools. * The boolean {@code forgiveStaleLocks} parameter is used to indicate that * an exceptional condition occurred which caused the caller to abort a * cycle of resource usage, potentially with outstanding resource locks. * This method will unlock all non-permanent resources that have outstanding * locks if {@code forgiveStaleLocks} is {@code true}, or it will print out * a warning and a resource summary if that parameter is {@code false}. * * @param forgiveStaleLocks {@code true} if the caller wishes to forgive * and unlock all outstanding locks on non-permanent resources */ @Override public void freeDisposalRequestedAndCheckResources(boolean forgiveStaleLocks) { boolean anyLockedResources = false; WeakLinkedList prev = resourceHead; WeakLinkedList cur = prev.next; while (cur != null) { ManagedResource mr = cur.getResource(); if (ManagedResource._isgone(mr)) { recordFree(cur.size); cur = cur.next; prev.next = cur; } else { if (!mr.isPermanent()) { if (mr.isLocked() && !mr.wasMismatched()) { if (forgiveStaleLocks) { mr.unlockall(); } else { mr.setMismatched(); anyLockedResources = true; } } mr.bumpAge(FOREVER); } prev = cur; cur = cur.next; } } if (PrismSettings.poolStats || anyLockedResources) { if (anyLockedResources) { System.err.println("Outstanding resource locks detected:"); } printSummary(true); System.err.println(); } } static String commas(long v) { return String.format("%,d", v); } public void printSummary(boolean printlocksources) { int numgone = 0; int numlocked = 0; int numpermanent = 0; int numinteresting = 0; int nummismatched = 0; int numancient = 0; long total_age = 0; int total = 0; boolean trackLockSources = ManagedResource.trackLockSources; double percentUsed = used() * 100.0 / max(); double percentTarget = target() * 100.0 / max(); System.err.printf("%s: %,d used (%.1f%%), %,d target (%.1f%%), %,d max\n", this, used(), percentUsed, target(), percentTarget, max()); for (WeakLinkedList cur = resourceHead.next; cur != null; cur = cur.next) { ManagedResource mr = cur.getResource(); total++; if (mr == null || !mr.isValid() || mr.isDisposalRequested()) { numgone++; } else { int a = mr.getAge(); total_age += a; if (a >= FOREVER) { numancient++; } if (mr.wasMismatched()) { nummismatched++; } if (mr.isPermanent()) { numpermanent++; } else if (mr.isLocked()) { numlocked++; if (trackLockSources && printlocksources) { for (Throwable th : mr.lockedFrom) { th.printStackTrace(System.err); } mr.lockedFrom.clear(); } } if (mr.isInteresting()) { numinteresting++; } } } double avg_age = ((double) total_age) / total; System.err.println(total+" total resources being managed"); System.err.printf("average resource age is %.1f frames\n", avg_age); printpoolpercent(numancient, total, "at maximum supported age"); printpoolpercent(numpermanent, total, "marked permanent"); printpoolpercent(nummismatched, total, "have had mismatched locks"); printpoolpercent(numlocked, total, "locked"); printpoolpercent(numinteresting, total, "contain interesting data"); printpoolpercent(numgone, total, "disappeared"); } private static void printpoolpercent(int stat, int total, String desc) { double percent = stat * 100.0 / total; System.err.printf("%,d resources %s (%.1f%%)\n", stat, desc, percent); } @Override public boolean isManagerThread() { return Thread.currentThread() == managerThread; } @Override public final long managed() { return managedSize; } @Override public long used() { if (sharedParent != null) { return sharedParent.used(); } return managedSize; } @Override public final long max() { return maxSize; } @Override public final long origTarget() { return origTarget; } @Override public final long target() { return curTarget; } @Override public final void setTarget(long newTarget) { if (newTarget > maxSize) { throw new IllegalArgumentException("New target "+newTarget+ " larger than max "+maxSize); } if (newTarget < origTarget) { throw new IllegalArgumentException("New target "+newTarget+ " smaller than initial target "+origTarget); } curTarget = newTarget; } @Override public boolean prepareForAllocation(long size) { return cleanup(size); } @Override public final void recordAllocated(long size) { managedSize += size; } @Override public final void resourceManaged(ManagedResource mr) { long size = size(mr.resource); resourceHead.insert(mr, size); recordAllocated(size); } @Override public final void resourceFreed(ManagedResource freed) { WeakLinkedList prev = resourceHead; WeakLinkedList cur = prev.next; while (cur != null) { ManagedResource res = cur.getResource(); if (res == null || res == freed) { recordFree(cur.size); cur = cur.next; prev.next = cur; if (res == freed) { return; } } else { prev = cur; cur = cur.next; } } // If we get here, the resource is not currently being managed. This // can happen when the Disposer processes its queue of disposed records // and encounters a record that was previously reclaimed via // cleanup() or freeDisposalRequestedAndCheckResources(), when the // device is lost or removed. We can safely ignore it. if (PrismSettings.poolDebug) { System.err.println("Warning: unmanaged resource " + freed + " freed from pool: " + this); } } @Override public final void recordFree(long size) { managedSize -= size; if (managedSize < 0) { throw new IllegalStateException("Negative resource amount"); } } static class WeakLinkedList { final WeakReference> theResourceRef; final long size; WeakLinkedList next; WeakLinkedList() { this.theResourceRef = null; this.size = 0L; } WeakLinkedList(ManagedResource mresource, long size, WeakLinkedList next) { this.theResourceRef = new WeakReference<>(mresource); this.size = size; this.next = next; } void insert(ManagedResource mresource, long size) { this.next = new WeakLinkedList<>(mresource, size, next); } ManagedResource getResource() { return theResourceRef.get(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy