FilePermission Canonical path optimization
Peter Levart
peter.levart at gmail.com
Fri Dec 5 13:00:30 UTC 2014
Hi Deven,
Similar to lazy initialization of initial list of drivers in
java.sql.DriverManager that was done recently:
http://mail.openjdk.java.net/pipermail/core-libs-dev/2014-December/029944.html
It would be nice to replace the following pattern:
if (cpath == null) initCannonicalpath();
... use cpath ...
with:
... use getCpath() ...
where the getCpath() contains lazy initialization logic. Unfortunately
initCannonicalPath() does not initialize only the 'cpath', but also
dependent 'directory' and 'recursive' flags, so perhaps you could rename
it to initCpathDirectoryAndRecursive() ?
Your pattern:
if (cpath == null) initCannonicalpath();
... use cpath ...
also contains data races. The 'cpath', 'directory' and 'recursive'
instance variables are not volatile. You set them in synchronized
initCannonicalPath(), but you read them without synchronization. Another
thread could see the 'cpath' variable already initialized, but still
read the 'directory' and 'recursive' flags as uninitialized values (in
equals and impliesIgnoreMask). These data races were present before,
when those fields were initialized in constructor, but with lazy
initialization you extend the possibilities of exposing them in
situations to which they were not subjected before.
One way to fix this would be:
Make 'cpath', 'directory' and 'recursive' fields volatile and carefully
arrange ininitCpathDirectoryAndRecursive () to be assigned in the
following order:
- 'directory' and 'recursive' first
- 'cpath' last
The following pattern:
if (cpath == null) initCpathDirectoryAndRecursive();
... use 'cpath' or ''directory' or 'recursive' ...
Is then safe. And you don't need synchronized keyword on
initCpathDirectoryAndRecursive().
Other than that, I think that the following check:
191 if (getName() == null)
192 throw new NullPointerException("name can't be null");
...does not belong to initMask() method. But it has to be performed
non-lazily in constructors / deserialization. So I suggest the following:
private static int checkMask(int mask) {
if ((mask & ALL) != mask || mask == NONE)
throw new IllegalArgumentException("invalid actions mask");
return mask;
}
private static String checkPath(String path) {
if (path == null)
throw new NullPointerException("path can't be null");
return path;
}
and use those in both constructors and deserialization as identity
functions that pass the parameters unchanged, but validate them.
The question is what to do with the remaining data race that was present
before. The 'mask' field. The best would be to make it final, but
deserialization needs to set it. If you're willing to introduce ugly
reflection with doPrivileged() monster block of code just to set the
field in readObject(), you can do it like that:
// instead of
this.mask = checkMask(getMask(actions));
// you would have to do do:
final int mask = checkMask(getMask(actions));
try {
AccessController.doPrivileged(new
PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
Field maskField =
FilePermission.class.getDeclaredField("mask");
maskField.setAccessible(true);
maskField.set(FilePermission.this, mask);
return null;
}
});
} catch (PrivilegedActionException e) {
throw new IOException(e.getException());
}
Regards, Peter
On 12/05/2014 08:55 AM, deven you wrote:
> Hi All,
>
> I have updated the patch[3] to reflect all of your suggestions.
>
> [3] http://cr.openjdk.java.net/~youdwei/ojdk-912/webrev.02/
>
> Thanks a lot!
>
> 2014-12-05 10:39 GMT+08:00 deven you <ydwchina at gmail.com>:
>
>> Hi Alan,
>>
>> I am not clear whether canonicalization cache enabled or disabled, however
>> I think even the cache is enable, it may not help much for the start up
>> stage especially if the app uses many different files.
>>
>> The cache should speed up applications for common case especially after
>> start up and this patch will specifically solve the problem for
>> FilePermssion.
>>
>> I will update the patch soon with your suggestion and Wenjian's.
>>
>> Thanks a lot!
>>
>>
>>
>> 2014-12-01 20:50 GMT+08:00 Alan Bateman <Alan.Bateman at oracle.com>:
>>
>>> On 01/12/2014 08:06, deven you wrote:
>>>
>>>> Hi All,
>>>> File.getCanonicalPath() is a very time-consuming method, we observed
>>>> significant performance degradation from some application's startup stage
>>>> with java.io.FilePermission. However, lazying load the calls to
>>>> getCanonicalPath() from java.ioFilePermission is straightforward and
>>>> solve
>>>> this problem effectively. Openjdk bug[1] tracks this bug and here is the
>>>> patch [2]. Could anyone take a look?
>>>>
>>>> [1] https://bugs.openjdk.java.net/browse/JDK-8066211
>>>> [2] http://cr.openjdk.java.net/~youdwei/ojdk-912/webrev.00/
>>>>
>>> Do you run with canonicalization cache enabled or disabled? While I don't
>>> agree with this cache, it seem to mostly eliminate complaints in this area
>>> after it was added (in JDK 1.4 I think).
>>>
>>> As regards the patch then it is a bit ugly. Could you use cpath == null
>>> instead of introducing a flag? If a flag is required then I think it will
>>> need a better name. Also if init is split then it might be cleaner to have
>>> initMask and initCanonicalPath.
>>>
>>> -Alan
>>>
>>>
>>>
More information about the core-libs-dev
mailing list