Skip to content
Permalink
Browse files
8263482: Make access to the ICC color profiles data multithread-friendly
Reviewed-by: azvegint
  • Loading branch information
mrserb committed Mar 19, 2021
1 parent d185655 commit 1a21f77971c010ec1ab6780d887f469f4a70ea06
@@ -91,7 +91,19 @@
@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 static ICC_Profile getInstance(InputStream s) throws IOException {
}

/**
* 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 int getColorSpaceType() {
if (info != null) {
return info.colorSpaceType;
}
activate();
return getColorSpaceType(cmmProfile);
return getColorSpaceType(cmmProfile());
}

private static int getColorSpaceType(Profile p) {
@@ -1030,8 +1046,7 @@ private static int getColorSpaceType(Profile p) {
* {@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 void write(OutputStream s) throws IOException {
* @see #setData(int, byte[])
*/
public byte[] getData() {
activate();
return CMSManager.getModule().getProfileData(cmmProfile);
return CMSManager.getModule().getProfileData(cmmProfile());
}

/**
@@ -1085,8 +1099,8 @@ public void write(OutputStream s) throws IOException {
* @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 void write(OutputStream s) throws IOException {
* @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 int getNumComponents() {
* 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 */

@@ -33,9 +33,14 @@

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;
}
@@ -31,9 +31,8 @@
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 Profile loadProfile(byte[] data) {
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 synchronized void setTagData(Profile p, int tagSignature, byte[] data) {
* 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);

@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);
}

public static synchronized native LCMSProfile getProfileID(ICC_Profile profile);
static synchronized native LCMSProfile getProfileID(ICC_Profile profile);

/* Helper method used from LCMSColorTransfrom */
static long createTransform(
@@ -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();
}

TagData getTag(int sig) {
return tagCache.getTag(sig);
return getNativePtr();
}

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);
}
}
}
Loading

1 comment on commit 1a21f77

@openjdk-notifier

This comment has been minimized.

Copy link

@openjdk-notifier openjdk-notifier bot commented on 1a21f77 Mar 19, 2021

Please sign in to comment.