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 @Serial
private static final long serialVersionUID = -3938515861990936766L; 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; 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; private transient volatile ProfileDeferralInfo deferralInfo;
/** /**
@ -917,32 +929,37 @@ public class ICC_Profile implements Serializable {
} }
/** /**
* Activates the deferred standard profiles. Implementation of this method * Activates and returns the deferred standard profiles. Implementation of
* mimics the old behaviour when the CMMException and IOException were * this method mimics the old behaviour when the {@code CMMException} and
* wrapped by the ProfileDataException, and the ProfileDataException itself * {@code IOException} were wrapped by the {@code ProfileDataException}, and
* was ignored during activation. * the {@code ProfileDataException} itself was ignored during activation.
*
* @return the implementation specific CMM profile, or {@code null}
*/ */
private void activate() { private Profile cmmProfile() {
if (cmmProfile == null) { Profile p = cmmProfile;
if (p != null) {
return p; // one volatile read on common path
}
synchronized (this) { synchronized (this) {
if (cmmProfile != null) { if (cmmProfile != null) {
return; return cmmProfile;
} }
var is = getStandardProfileInputStream(deferralInfo.filename); var is = getStandardProfileInputStream(deferralInfo.filename);
if (is == null) { if (is == null) {
return; return null;
} }
try (is) { try (is) {
byte[] data = getProfileDataFromStream(is); byte[] data = getProfileDataFromStream(is);
if (data != null) { if (data != null) {
cmmProfile = CMSManager.getModule().loadProfile(data); p = cmmProfile = CMSManager.getModule().loadProfile(data);
// from now we cannot use the deferred value, drop it // from now we cannot use the deferred value, drop it
deferralInfo = null; deferralInfo = null;
} }
} catch (CMMException | IOException ignore) { } catch (CMMException | IOException ignore) {
} }
} }
} return p;
} }
/** /**
@ -1006,8 +1023,7 @@ public class ICC_Profile implements Serializable {
if (info != null) { if (info != null) {
return info.colorSpaceType; return info.colorSpaceType;
} }
activate(); return getColorSpaceType(cmmProfile());
return getColorSpaceType(cmmProfile);
} }
private static int getColorSpaceType(Profile p) { private static int getColorSpaceType(Profile p) {
@ -1030,8 +1046,7 @@ public class ICC_Profile implements Serializable {
* {@code ColorSpace} class * {@code ColorSpace} class
*/ */
public int getPCSType() { public int getPCSType() {
activate(); byte[] theHeader = getData(icSigHead);
byte[] theHeader = getData(cmmProfile, icSigHead);
int thePCSSig = intFromBigEndian(theHeader, icHdrPcs); int thePCSSig = intFromBigEndian(theHeader, icHdrPcs);
return iccCStoJCS(thePCSSig); return iccCStoJCS(thePCSSig);
} }
@ -1067,8 +1082,7 @@ public class ICC_Profile implements Serializable {
* @see #setData(int, byte[]) * @see #setData(int, byte[])
*/ */
public byte[] getData() { 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[]) * @see #setData(int, byte[])
*/ */
public byte[] getData(int tagSignature) { public byte[] getData(int tagSignature) {
activate(); byte[] t = getData(cmmProfile(), tagSignature);
return getData(cmmProfile, tagSignature); return t != null ? t.clone() : null;
} }
private static byte[] getData(Profile p, int tagSignature) { private static byte[] getData(Profile p, int tagSignature) {
@ -1115,8 +1129,7 @@ public class ICC_Profile implements Serializable {
* @see #getData * @see #getData
*/ */
public void setData(int tagSignature, byte[] tagData) { 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 * Returns a float array of length 3 containing the X, Y, and Z components
* encoded in an XYZType tag. * encoded in an XYZType tag.
*/ */
float[] getXYZTag(int tagSignature) { final float[] getXYZTag(int tagSignature) {
byte[] theData = getData(tagSignature); byte[] theData = getData(tagSignature);
float[] theXYZNumber = new float[3]; /* array to return */ float[] theXYZNumber = new float[3]; /* array to return */

View File

@ -33,9 +33,14 @@ import sun.security.action.GetPropertyAction;
public final class CMSManager { 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) { if (cmmImpl != null) {
return cmmImpl; return cmmImpl;
} }

View File

@ -31,9 +31,8 @@ import java.awt.color.ICC_Profile;
import sun.java2d.cmm.ColorTransform; import sun.java2d.cmm.ColorTransform;
import sun.java2d.cmm.PCMM; import sun.java2d.cmm.PCMM;
import sun.java2d.cmm.Profile; 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 */ /* methods invoked from ICC_Profile */
@Override @Override
@ -48,54 +47,13 @@ public class LCMS implements PCMM {
return null; return null;
} }
private native long loadProfileNative(byte[] data, Object ref); private static LCMSProfile getLcmsProfile(Profile p) {
private LCMSProfile getLcmsProfile(Profile p) {
if (p instanceof LCMSProfile) { if (p instanceof LCMSProfile) {
return (LCMSProfile)p; return (LCMSProfile)p;
} }
throw new CMMException("Invalid profile: " + 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. * Writes supplied data as a tag into the profile.
* Destroys old profile, if new one was successfully * 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 * Throws CMMException if operation fails, preserve old profile from
* destruction. * destruction.
*/ */
private native void setTagDataNative(long ptr, int tagSignature, static native void setTagDataNative(long ptr, int tagSignature, byte[] data);
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 */ /* Helper method used from LCMSColorTransfrom */
static long createTransform( static long createTransform(

View File

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

View File

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