8263482: Make access to the ICC color profiles data multithread-friendly

Reviewed-by: azvegint
This commit is contained in:
Sergey Bylokhov 2021-03-19 02:48:30 +00:00
parent d185655c27
commit 1a21f77971
5 changed files with 129 additions and 160 deletions

View File

@ -91,7 +91,19 @@ public class ICC_Profile implements Serializable {
@Serial
private static final long serialVersionUID = -3938515861990936766L;
/**
* The implementation specific CMM profile, {@code null} if this
* {@code ICC_Profile} is not activated by the {@link #cmmProfile()} method.
* This field must not be used directly and only via {@link #cmmProfile()}.
*/
private transient volatile Profile cmmProfile;
/**
* Stores some information about {@code ICC_Profile} without causing a
* deferred profile to be loaded. Note that we can defer the loading of
* standard profiles only. If this field is null, then {@link #cmmProfile}
* should be used to access profile information.
*/
private transient volatile ProfileDeferralInfo deferralInfo;
/**
@ -917,32 +929,37 @@ public class ICC_Profile implements Serializable {
}
/**
* Activates the deferred standard profiles. Implementation of this method
* mimics the old behaviour when the CMMException and IOException were
* wrapped by the ProfileDataException, and the ProfileDataException itself
* was ignored during activation.
* Activates and returns the deferred standard profiles. Implementation of
* this method mimics the old behaviour when the {@code CMMException} and
* {@code IOException} were wrapped by the {@code ProfileDataException}, and
* the {@code ProfileDataException} itself was ignored during activation.
*
* @return the implementation specific CMM profile, or {@code null}
*/
private void activate() {
if (cmmProfile == null) {
synchronized (this) {
if (cmmProfile != null) {
return;
}
var is = getStandardProfileInputStream(deferralInfo.filename);
if (is == null) {
return;
}
try (is) {
byte[] data = getProfileDataFromStream(is);
if (data != null) {
cmmProfile = CMSManager.getModule().loadProfile(data);
// from now we cannot use the deferred value, drop it
deferralInfo = null;
}
} catch (CMMException | IOException ignore) {
private Profile cmmProfile() {
Profile p = cmmProfile;
if (p != null) {
return p; // one volatile read on common path
}
synchronized (this) {
if (cmmProfile != null) {
return cmmProfile;
}
var is = getStandardProfileInputStream(deferralInfo.filename);
if (is == null) {
return null;
}
try (is) {
byte[] data = getProfileDataFromStream(is);
if (data != null) {
p = cmmProfile = CMSManager.getModule().loadProfile(data);
// from now we cannot use the deferred value, drop it
deferralInfo = null;
}
} catch (CMMException | IOException ignore) {
}
}
return p;
}
/**
@ -1006,8 +1023,7 @@ public class ICC_Profile implements Serializable {
if (info != null) {
return info.colorSpaceType;
}
activate();
return getColorSpaceType(cmmProfile);
return getColorSpaceType(cmmProfile());
}
private static int getColorSpaceType(Profile p) {
@ -1030,8 +1046,7 @@ public class ICC_Profile implements Serializable {
* {@code ColorSpace} class
*/
public int getPCSType() {
activate();
byte[] theHeader = getData(cmmProfile, icSigHead);
byte[] theHeader = getData(icSigHead);
int thePCSSig = intFromBigEndian(theHeader, icHdrPcs);
return iccCStoJCS(thePCSSig);
}
@ -1067,8 +1082,7 @@ public class ICC_Profile implements Serializable {
* @see #setData(int, byte[])
*/
public byte[] getData() {
activate();
return CMSManager.getModule().getProfileData(cmmProfile);
return CMSManager.getModule().getProfileData(cmmProfile());
}
/**
@ -1085,8 +1099,8 @@ public class ICC_Profile implements Serializable {
* @see #setData(int, byte[])
*/
public byte[] getData(int tagSignature) {
activate();
return getData(cmmProfile, tagSignature);
byte[] t = getData(cmmProfile(), tagSignature);
return t != null ? t.clone() : null;
}
private static byte[] getData(Profile p, int tagSignature) {
@ -1115,8 +1129,7 @@ public class ICC_Profile implements Serializable {
* @see #getData
*/
public void setData(int tagSignature, byte[] tagData) {
activate();
CMSManager.getModule().setTagData(cmmProfile, tagSignature, tagData);
CMSManager.getModule().setTagData(cmmProfile(), tagSignature, tagData);
}
/**
@ -1170,7 +1183,7 @@ public class ICC_Profile implements Serializable {
* Returns a float array of length 3 containing the X, Y, and Z components
* encoded in an XYZType tag.
*/
float[] getXYZTag(int tagSignature) {
final float[] getXYZTag(int tagSignature) {
byte[] theData = getData(tagSignature);
float[] theXYZNumber = new float[3]; /* array to return */

View File

@ -33,9 +33,14 @@ import sun.security.action.GetPropertyAction;
public final class CMSManager {
private static PCMM cmmImpl = null;
private static volatile PCMM cmmImpl;
public static synchronized PCMM getModule() {
public static PCMM getModule() {
PCMM loc = cmmImpl;
return loc != null ? loc : createModule();
}
private static synchronized PCMM createModule() {
if (cmmImpl != null) {
return cmmImpl;
}

View File

@ -31,9 +31,8 @@ import java.awt.color.ICC_Profile;
import sun.java2d.cmm.ColorTransform;
import sun.java2d.cmm.PCMM;
import sun.java2d.cmm.Profile;
import sun.java2d.cmm.lcms.LCMSProfile.TagData;
public class LCMS implements PCMM {
final class LCMS implements PCMM {
/* methods invoked from ICC_Profile */
@Override
@ -48,54 +47,13 @@ public class LCMS implements PCMM {
return null;
}
private native long loadProfileNative(byte[] data, Object ref);
private LCMSProfile getLcmsProfile(Profile p) {
private static LCMSProfile getLcmsProfile(Profile p) {
if (p instanceof LCMSProfile) {
return (LCMSProfile)p;
}
throw new CMMException("Invalid profile: " + p);
}
@Override
public byte[] getProfileData(final Profile p) {
LCMSProfile lcmsProfile = getLcmsProfile(p);
synchronized (lcmsProfile) {
return getProfileDataNative(lcmsProfile.getLcmsPtr());
}
}
private native byte[] getProfileDataNative(long ptr);
static native byte[] getTagNative(long profileID, int signature);
@Override
public byte[] getTagData(Profile p, int tagSignature) {
final LCMSProfile lcmsProfile = getLcmsProfile(p);
synchronized (lcmsProfile) {
TagData t = lcmsProfile.getTag(tagSignature);
return t != null ? t.getData() : null;
}
}
@Override
public synchronized void setTagData(Profile p, int tagSignature, byte[] data) {
final LCMSProfile profile = getLcmsProfile(p);
synchronized (profile) {
profile.clearTagCache();
// Now we are going to update the profile with new tag data
// In some cases, we may change the pointer to the native
// profile.
//
// If we fail to write tag data for any reason, the old pointer
// should be used.
setTagDataNative(profile.getLcmsPtr(),
tagSignature, data);
}
}
/**
* Writes supplied data as a tag into the profile.
* Destroys old profile, if new one was successfully
@ -106,10 +64,27 @@ public class LCMS implements PCMM {
* Throws CMMException if operation fails, preserve old profile from
* destruction.
*/
private native void setTagDataNative(long ptr, int tagSignature,
byte[] data);
static native void setTagDataNative(long ptr, int tagSignature, byte[] data);
static native byte[] getProfileDataNative(long ptr);
static native byte[] getTagNative(long profileID, int signature);
private static native long loadProfileNative(byte[] data, Object ref);
public static synchronized native LCMSProfile getProfileID(ICC_Profile profile);
@Override
public byte[] getProfileData(Profile p) {
return getLcmsProfile(p).getProfileData();
}
@Override
public byte[] getTagData(Profile p, int tagSignature) {
return getLcmsProfile(p).getTag(tagSignature);
}
@Override
public synchronized void setTagData(Profile p, int tagSignature, byte[] data) {
getLcmsProfile(p).setTag(tagSignature, data);
}
static synchronized native LCMSProfile getProfileID(ICC_Profile profile);
/* Helper method used from LCMSColorTransfrom */
static long createTransform(

View File

@ -25,69 +25,63 @@
package sun.java2d.cmm.lcms;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.StampedLock;
import sun.java2d.cmm.Profile;
final class LCMSProfile extends Profile {
private final TagCache tagCache;
private final Object disposerReferent;
private final Map<Integer, byte[]> tags = new ConcurrentHashMap<>();
private final StampedLock lock = new StampedLock();
LCMSProfile(long ptr, Object ref) {
super(ptr);
disposerReferent = ref;
tagCache = new TagCache(this);
}
long getLcmsPtr() {
return this.getNativePtr();
return getNativePtr();
}
TagData getTag(int sig) {
return tagCache.getTag(sig);
}
void clearTagCache() {
tagCache.clear();
}
private static final class TagCache {
private final LCMSProfile profile;
private final HashMap<Integer, TagData> tags = new HashMap<>();
private TagCache(LCMSProfile p) {
profile = p;
byte[] getProfileData() {
long stamp = lock.readLock();
try {
return LCMS.getProfileDataNative(getNativePtr());
} finally {
lock.unlockRead(stamp);
}
}
private TagData getTag(int sig) {
TagData t = tags.get(sig);
if (t == null) {
byte[] tagData = LCMS.getTagNative(profile.getNativePtr(), sig);
if (tagData != null) {
t = new TagData(tagData);
tags.put(sig, t);
}
}
byte[] getTag(int sig) {
byte[] t = tags.get(sig);
if (t != null) {
return t;
}
private void clear() {
tags.clear();
long stamp = lock.readLock();
try {
return tags.computeIfAbsent(sig, (key) -> {
return LCMS.getTagNative(getNativePtr(), key);
});
} finally {
lock.unlockRead(stamp);
}
}
static final class TagData {
private final byte[] data;
TagData(byte[] data) {
this.data = data;
}
byte[] getData() {
return data.clone();
void setTag(int tagSignature, byte[] data) {
long stamp = lock.writeLock();
try {
tags.clear();
// Now we are going to update the profile with new tag data
// In some cases, we may change the pointer to the native profile.
//
// If we fail to write tag data for any reason, the old pointer
// should be used.
LCMS.setTagDataNative(getNativePtr(), tagSignature, data);
} finally {
lock.unlockWrite(stamp);
}
}
}

View File

@ -128,7 +128,7 @@ void LCMS_freeTransform(JNIEnv *env, jlong ID)
/*
* Class: sun_java2d_cmm_lcms_LCMS
* Method: createNativeTransform
* Signature: ([JI)J
* Signature: ([JIIZIZLjava/lang/Object;)J
*/
JNIEXPORT jlong JNICALL Java_sun_java2d_cmm_lcms_LCMS_createNativeTransform
(JNIEnv *env, jclass cls, jlongArray profileIDs, jint renderType,
@ -214,11 +214,11 @@ JNIEXPORT jlong JNICALL Java_sun_java2d_cmm_lcms_LCMS_createNativeTransform
/*
* Class: sun_java2d_cmm_lcms_LCMS
* Method: loadProfile
* Signature: ([B,Lsun/java2d/cmm/lcms/LCMSProfile;)V
* Method: loadProfileNative
* Signature: ([BLjava/lang/Object;)J
*/
JNIEXPORT jlong JNICALL Java_sun_java2d_cmm_lcms_LCMS_loadProfileNative
(JNIEnv *env, jobject obj, jbyteArray data, jobject disposerRef)
(JNIEnv *env, jclass cls, jbyteArray data, jobject disposerRef)
{
jbyte* dataArray;
jint dataSize;
@ -278,10 +278,10 @@ JNIEXPORT jlong JNICALL Java_sun_java2d_cmm_lcms_LCMS_loadProfileNative
/*
* Class: sun_java2d_cmm_lcms_LCMS
* Method: getProfileDataNative
* Signature: (J[B)V
* Signature: (J)[B
*/
JNIEXPORT jbyteArray JNICALL Java_sun_java2d_cmm_lcms_LCMS_getProfileDataNative
(JNIEnv *env, jobject obj, jlong id)
(JNIEnv *env, jclass cls, jlong id)
{
lcmsProfile_p sProf = (lcmsProfile_p)jlong_to_ptr(id);
cmsUInt32Number pfSize = 0;
@ -325,11 +325,11 @@ static cmsHPROFILE _writeCookedTag(cmsHPROFILE pfTarget, cmsTagSignature sig, jb
/*
* Class: sun_java2d_cmm_lcms_LCMS
* Method: getTagData
* Signature: (JI[B)V
* Method: getTagNative
* Signature: (JI)[B
*/
JNIEXPORT jbyteArray JNICALL Java_sun_java2d_cmm_lcms_LCMS_getTagNative
(JNIEnv *env, jobject obj, jlong id, jint tagSig)
(JNIEnv *env, jclass cls, jlong id, jint tagSig)
{
lcmsProfile_p sProf = (lcmsProfile_p)jlong_to_ptr(id);
TagSignature_t sig;
@ -410,11 +410,11 @@ JNIEXPORT jbyteArray JNICALL Java_sun_java2d_cmm_lcms_LCMS_getTagNative
/*
* Class: sun_java2d_cmm_lcms_LCMS
* Method: setTagData
* Method: setTagDataNative
* Signature: (JI[B)V
*/
JNIEXPORT void JNICALL Java_sun_java2d_cmm_lcms_LCMS_setTagDataNative
(JNIEnv *env, jobject obj, jlong id, jint tagSig, jbyteArray data)
(JNIEnv *env, jclass cls, jlong id, jint tagSig, jbyteArray data)
{
lcmsProfile_p sProf = (lcmsProfile_p)jlong_to_ptr(id);
cmsHPROFILE pfReplace = NULL;
@ -510,7 +510,7 @@ void releaseILData (JNIEnv *env, void* pData, jint dataType,
* Signature: (Lsun/java2d/cmm/lcms/LCMSTransform;Lsun/java2d/cmm/lcms/LCMSImageLayout;Lsun/java2d/cmm/lcms/LCMSImageLayout;)V
*/
JNIEXPORT void JNICALL Java_sun_java2d_cmm_lcms_LCMS_colorConvert
(JNIEnv *env, jclass obj, jobject trans, jobject src, jobject dst)
(JNIEnv *env, jclass cls, jobject trans, jobject src, jobject dst)
{
cmsHTRANSFORM sTrans = NULL;
int srcDType, dstDType;
@ -579,50 +579,32 @@ JNIEXPORT void JNICALL Java_sun_java2d_cmm_lcms_LCMS_colorConvert
/*
* Class: sun_java2d_cmm_lcms_LCMS
* Method: getProfileID
* Signature: (Ljava/awt/color/ICC_Profile;)Lsun/java2d/cmm/lcms/LCMSProfile
* Signature: (Ljava/awt/color/ICC_Profile;)Lsun/java2d/cmm/lcms/LCMSProfile;
*/
JNIEXPORT jobject JNICALL Java_sun_java2d_cmm_lcms_LCMS_getProfileID
(JNIEnv *env, jclass cls, jobject pf)
{
jclass clsLcmsProfile;
jobject cmmProfile;
jfieldID fid;
if (pf == NULL) {
return NULL;
}
jclass pcls = (*env)->GetObjectClass(env, pf);
if (pcls == NULL) {
return NULL;
}
jmethodID mid = (*env)->GetMethodID(env, pcls, "activate", "()V");
jmethodID mid = (*env)->GetMethodID(env, pcls, "cmmProfile",
"()Lsun/java2d/cmm/Profile;");
if (mid == NULL) {
return NULL;
}
(*env)->CallVoidMethod(env, pf, mid);
jobject cmmProfile = (*env)->CallObjectMethod(env, pf, mid);
if ((*env)->ExceptionOccurred(env)) {
return NULL;
}
fid = (*env)->GetFieldID(env, pcls, "cmmProfile",
"Lsun/java2d/cmm/Profile;");
if (fid == NULL) {
jclass lcmsPCls = (*env)->FindClass(env, "sun/java2d/cmm/lcms/LCMSProfile");
if (lcmsPCls == NULL) {
return NULL;
}
clsLcmsProfile = (*env)->FindClass(env,
"sun/java2d/cmm/lcms/LCMSProfile");
if (clsLcmsProfile == NULL) {
return NULL;
}
cmmProfile = (*env)->GetObjectField (env, pf, fid);
if (JNU_IsNull(env, cmmProfile)) {
return NULL;
}
if ((*env)->IsInstanceOf(env, cmmProfile, clsLcmsProfile)) {
if ((*env)->IsInstanceOf(env, cmmProfile, lcmsPCls)) {
return cmmProfile;
}
return NULL;