8148886: SEGV in sun.java2d.marlin.Renderer._endRendering
Summary: handle reentrancy in both AAShapePipe and MarlinRenderingEngine using new sun.java2d.ReentrantContextProvider implementations
Reviewed-by: flar, prr
/*
* Copyright (c) 2015, 2016, 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 sun.java2d.marlin;
import java.awt.geom.Path2D;
import java.lang.ref.WeakReference;
import java.util.concurrent.atomic.AtomicInteger;
import sun.java2d.ReentrantContext;
import sun.java2d.ReentrantContextProvider;
import static sun.java2d.marlin.ArrayCache.*;
import sun.java2d.marlin.MarlinRenderingEngine.NormalizingPathIterator;
import static sun.java2d.marlin.MarlinUtils.logInfo;
/**
* This class is a renderer context dedicated to a single thread
*/
final class RendererContext extends ReentrantContext implements MarlinConst {
// RendererContext creation counter
private static final AtomicInteger contextCount = new AtomicInteger(1);
// RendererContext statistics
static final RendererStats stats = (doStats || doMonitors)
? RendererStats.getInstance(): null;
private static final boolean USE_CACHE_HARD_REF = doStats
|| (MarlinRenderingEngine.REF_TYPE == ReentrantContextProvider.REF_WEAK);
/**
* Create a new renderer context
*
* @return new RendererContext instance
*/
static RendererContext createContext() {
final RendererContext newCtx = new RendererContext("ctx"
+ Integer.toString(contextCount.getAndIncrement()));
if (RendererContext.stats != null) {
RendererContext.stats.allContexts.add(newCtx);
}
return newCtx;
}
// context name (debugging purposes)
final String name;
// Smallest object used as Cleaner's parent reference
final Object cleanerObj = new Object();
// dirty flag indicating an exception occured during pipeline in pathTo()
boolean dirty = false;
// dynamic array caches kept using weak reference (low memory footprint)
WeakReference<ArrayCachesHolder> refArrayCaches = null;
// hard reference to array caches (for statistics)
ArrayCachesHolder hardRefArrayCaches = null;
// shared data
final float[] float6 = new float[6];
// shared curve (dirty) (Renderer / Stroker)
final Curve curve = new Curve();
// MarlinRenderingEngine NormalizingPathIterator NearestPixelCenter:
final NormalizingPathIterator nPCPathIterator;
// MarlinRenderingEngine NearestPixelQuarter NormalizingPathIterator:
final NormalizingPathIterator nPQPathIterator;
// MarlinRenderingEngine.TransformingPathConsumer2D
final TransformingPathConsumer2D transformerPC2D;
// recycled Path2D instance
Path2D.Float p2d = null;
final Renderer renderer;
final Stroker stroker;
// Simplifies out collinear lines
final CollinearSimplifier simplifier = new CollinearSimplifier();
final Dasher dasher;
final MarlinTileGenerator ptg;
final MarlinCache cache;
// flag indicating the shape is stroked (1) or filled (0)
int stroking = 0;
/**
* Constructor
*
* @param name context name (debugging)
*/
RendererContext(final String name) {
if (logCreateContext) {
MarlinUtils.logInfo("new RendererContext = " + name);
}
this.name = name;
// NormalizingPathIterator instances:
nPCPathIterator = new NormalizingPathIterator.NearestPixelCenter(float6);
nPQPathIterator = new NormalizingPathIterator.NearestPixelQuarter(float6);
// MarlinRenderingEngine.TransformingPathConsumer2D
transformerPC2D = new TransformingPathConsumer2D();
// Renderer:
cache = new MarlinCache(this);
renderer = new Renderer(this); // needs MarlinCache from rdrCtx.cache
ptg = new MarlinTileGenerator(renderer);
stroker = new Stroker(this);
dasher = new Dasher(this);
}
/**
* Disposes this renderer context:
* clean up before reusing this context
*/
void dispose() {
stroking = 0;
// reset hard reference to array caches if needed:
if (!USE_CACHE_HARD_REF) {
hardRefArrayCaches = null;
}
// if context is maked as DIRTY:
if (dirty) {
// may happen if an exception if thrown in the pipeline processing:
// force cleanup of all possible pipelined blocks (except Renderer):
// NormalizingPathIterator instances:
this.nPCPathIterator.dispose();
this.nPQPathIterator.dispose();
// Dasher:
this.dasher.dispose();
// Stroker:
this.stroker.dispose();
// mark context as CLEAN:
dirty = false;
}
}
// Array caches
ArrayCachesHolder getArrayCachesHolder() {
// Use hard reference first (cached resolved weak reference):
ArrayCachesHolder holder = hardRefArrayCaches;
if (holder == null) {
// resolve reference:
holder = (refArrayCaches != null)
? refArrayCaches.get()
: null;
// create a new ArrayCachesHolder if none is available
if (holder == null) {
if (logCreateContext) {
MarlinUtils.logInfo("new ArrayCachesHolder for "
+ "RendererContext = " + name);
}
holder = new ArrayCachesHolder();
if (USE_CACHE_HARD_REF) {
// update hard reference:
hardRefArrayCaches = holder;
} else {
// update weak reference:
refArrayCaches = new WeakReference<ArrayCachesHolder>(holder);
}
}
}
return holder;
}
// dirty byte array cache
ByteArrayCache getDirtyByteArrayCache(final int length) {
final int bucket = ArrayCache.getBucketDirtyBytes(length);
return getArrayCachesHolder().dirtyByteArrayCaches[bucket];
}
byte[] getDirtyByteArray(final int length) {
if (length <= MAX_DIRTY_BYTE_ARRAY_SIZE) {
return getDirtyByteArrayCache(length).getArray();
}
if (doStats) {
incOversize();
}
if (doLogOverSize) {
logInfo("getDirtyByteArray[oversize]: length=\t" + length);
}
return new byte[length];
}
void putDirtyByteArray(final byte[] array) {
final int length = array.length;
// odd sized array are non-cached arrays (initial arrays)
// ensure to never store initial arrays in cache:
if (((length & 0x1) == 0) && (length <= MAX_DIRTY_BYTE_ARRAY_SIZE)) {
getDirtyByteArrayCache(length).putDirtyArray(array, length);
}
}
byte[] widenDirtyByteArray(final byte[] in,
final int usedSize, final int needSize)
{
final int length = in.length;
if (doChecks && length >= needSize) {
return in;
}
if (doStats) {
incResizeDirtyByte();
}
// maybe change bucket:
// ensure getNewSize() > newSize:
final byte[] res = getDirtyByteArray(getNewSize(usedSize, needSize));
System.arraycopy(in, 0, res, 0, usedSize); // copy only used elements
// maybe return current array:
// NO clean-up of array data = DIRTY ARRAY
putDirtyByteArray(in);
if (doLogWidenArray) {
logInfo("widenDirtyByteArray[" + res.length + "]: usedSize=\t"
+ usedSize + "\tlength=\t" + length + "\tneeded length=\t"
+ needSize);
}
return res;
}
// int array cache
IntArrayCache getIntArrayCache(final int length) {
final int bucket = ArrayCache.getBucket(length);
return getArrayCachesHolder().intArrayCaches[bucket];
}
int[] getIntArray(final int length) {
if (length <= MAX_ARRAY_SIZE) {
return getIntArrayCache(length).getArray();
}
if (doStats) {
incOversize();
}
if (doLogOverSize) {
logInfo("getIntArray[oversize]: length=\t" + length);
}
return new int[length];
}
// unused
int[] widenIntArray(final int[] in, final int usedSize,
final int needSize, final int clearTo)
{
final int length = in.length;
if (doChecks && length >= needSize) {
return in;
}
if (doStats) {
incResizeInt();
}
// maybe change bucket:
// ensure getNewSize() > newSize:
final int[] res = getIntArray(getNewSize(usedSize, needSize));
System.arraycopy(in, 0, res, 0, usedSize); // copy only used elements
// maybe return current array:
putIntArray(in, 0, clearTo); // ensure all array is cleared (grow-reduce algo)
if (doLogWidenArray) {
logInfo("widenIntArray[" + res.length + "]: usedSize=\t"
+ usedSize + "\tlength=\t" + length + "\tneeded length=\t"
+ needSize);
}
return res;
}
void putIntArray(final int[] array, final int fromIndex,
final int toIndex)
{
final int length = array.length;
// odd sized array are non-cached arrays (initial arrays)
// ensure to never store initial arrays in cache:
if (((length & 0x1) == 0) && (length <= MAX_ARRAY_SIZE)) {
getIntArrayCache(length).putArray(array, length, fromIndex, toIndex);
}
}
// dirty int array cache
IntArrayCache getDirtyIntArrayCache(final int length) {
final int bucket = ArrayCache.getBucket(length);
return getArrayCachesHolder().dirtyIntArrayCaches[bucket];
}
int[] getDirtyIntArray(final int length) {
if (length <= MAX_ARRAY_SIZE) {
return getDirtyIntArrayCache(length).getArray();
}
if (doStats) {
incOversize();
}
if (doLogOverSize) {
logInfo("getDirtyIntArray[oversize]: length=\t" + length);
}
return new int[length];
}
int[] widenDirtyIntArray(final int[] in,
final int usedSize, final int needSize)
{
final int length = in.length;
if (doChecks && length >= needSize) {
return in;
}
if (doStats) {
incResizeDirtyInt();
}
// maybe change bucket:
// ensure getNewSize() > newSize:
final int[] res = getDirtyIntArray(getNewSize(usedSize, needSize));
System.arraycopy(in, 0, res, 0, usedSize); // copy only used elements
// maybe return current array:
// NO clean-up of array data = DIRTY ARRAY
putDirtyIntArray(in);
if (doLogWidenArray) {
logInfo("widenDirtyIntArray[" + res.length + "]: usedSize=\t"
+ usedSize + "\tlength=\t" + length + "\tneeded length=\t"
+ needSize);
}
return res;
}
void putDirtyIntArray(final int[] array) {
final int length = array.length;
// odd sized array are non-cached arrays (initial arrays)
// ensure to never store initial arrays in cache:
if (((length & 0x1) == 0) && (length <= MAX_ARRAY_SIZE)) {
getDirtyIntArrayCache(length).putDirtyArray(array, length);
}
}
// dirty float array cache
FloatArrayCache getDirtyFloatArrayCache(final int length) {
final int bucket = ArrayCache.getBucket(length);
return getArrayCachesHolder().dirtyFloatArrayCaches[bucket];
}
float[] getDirtyFloatArray(final int length) {
if (length <= MAX_ARRAY_SIZE) {
return getDirtyFloatArrayCache(length).getArray();
}
if (doStats) {
incOversize();
}
if (doLogOverSize) {
logInfo("getDirtyFloatArray[oversize]: length=\t" + length);
}
return new float[length];
}
float[] widenDirtyFloatArray(final float[] in,
final int usedSize, final int needSize)
{
final int length = in.length;
if (doChecks && length >= needSize) {
return in;
}
if (doStats) {
incResizeDirtyFloat();
}
// maybe change bucket:
// ensure getNewSize() > newSize:
final float[] res = getDirtyFloatArray(getNewSize(usedSize, needSize));
System.arraycopy(in, 0, res, 0, usedSize); // copy only used elements
// maybe return current array:
// NO clean-up of array data = DIRTY ARRAY
putDirtyFloatArray(in);
if (doLogWidenArray) {
logInfo("widenDirtyFloatArray[" + res.length + "]: usedSize=\t"
+ usedSize + "\tlength=\t" + length + "\tneeded length=\t"
+ needSize);
}
return res;
}
void putDirtyFloatArray(final float[] array) {
final int length = array.length;
// odd sized array are non-cached arrays (initial arrays)
// ensure to never store initial arrays in cache:
if (((length & 0x1) == 0) && (length <= MAX_ARRAY_SIZE)) {
getDirtyFloatArrayCache(length).putDirtyArray(array, length);
}
}
/* class holding all array cache instances */
static final class ArrayCachesHolder {
// zero-filled int array cache:
final IntArrayCache[] intArrayCaches;
// dirty array caches:
final IntArrayCache[] dirtyIntArrayCaches;
final FloatArrayCache[] dirtyFloatArrayCaches;
final ByteArrayCache[] dirtyByteArrayCaches;
ArrayCachesHolder() {
intArrayCaches = new IntArrayCache[BUCKETS];
dirtyIntArrayCaches = new IntArrayCache[BUCKETS];
dirtyFloatArrayCaches = new FloatArrayCache[BUCKETS];
dirtyByteArrayCaches = new ByteArrayCache[BUCKETS];
for (int i = 0; i < BUCKETS; i++) {
intArrayCaches[i] = new IntArrayCache(ARRAY_SIZES[i]);
// dirty array caches:
dirtyIntArrayCaches[i] = new IntArrayCache(ARRAY_SIZES[i]);
dirtyFloatArrayCaches[i] = new FloatArrayCache(ARRAY_SIZES[i]);
dirtyByteArrayCaches[i] = new ByteArrayCache(DIRTY_BYTE_ARRAY_SIZES[i]);
}
}
}
}