On 26/02/2013 9:05 PM, Dan Creswell wrote:
On 26 February 2013 10:30, Peter Firmstone<[email protected]> wrote:
Thanks Dan& Gregg,
I've treated Permission object's as immutable, but that's clearly a poor
assumption, despite the Permission javadoc suggesting implementations should
be immutable, just shows you can't be too careful with other people's code.
To make an object effectively immutable (without final fields), it must be
safely published after construction, which means writing to a concurrent
collection, queue, final field, volatile field or a field protected by
synchronization for the hand over from one thread to the next, it cannot be
modified after publication either.
A truely immutable object has final fields, safe construction and can't be
modified, it's safely published after the constructor returns, so is more
robust as Gregg mentioned.
Mmm observation, there are two immutables:
(1) Immutable as perceived by the user of the object.
(2) Immutable internal implementation.
In this case, I've broken this rule, I've added SocketPermission to an
unsynchronized collection (protected from external modification) and that
collection is published to a final field in a constructor.
Final of course meaning nothing other than "value won't change" - a
compile/language level guarantee.
It doesn't interact in any meaningful way with the JVM concurrency
mechanics. i.e. it doesn't have any relationship to a synchronization
action (or barrier as I prefer). Thus final provides no multi-thread
guarantee explicitly. What one can say is that once the value becomes
visible to a set of threads it won't change.
Now this is where things get a little uncertain, this final field only
applies to the safe publication of the Collection, the Permission objects it
contains are not referenced by final fields nor synchronized so they're not
safely published, even though Permission objects are written to this
collection prior to the final field being initialised. (At least that's
what I gather from reading Concurrency in practise).
I agree with that assessment.
So I'm going to have to take a minor performance hit.
There are two solutions:
1. Use a concurrent collection instead (ConcurrentSkipListSet, which
is a ConcurrentSkipListMap discuised as a Set)
2. Wrap each Permission in an Object and safely publish the
Permission to a final field, then add the wrapper to the
collection, the other thread then accesses the final field
ensuring safe publication. The collection is still an
unsynchronized collection, but now contains immutable objects and
hasn't been changed since it was published.
Option 2 requires the creation of more temporary objects, but should still
avoid cache misses, while option 1 uses volatile variables to safely publish
the Permission's. Since this collection isn't modified after construction
volatile reads will be almost as cheap as nonvolatile reads and I can use
the existing PermissionComparator. I think I'll go with option 1.
+1 for option 1.
If you look at any security debugging output, remembering that many
duplicate checks are eliminated by CombinerSecurityManager, the security
architecture in a network environment gets absolutely hammered.
As an aside, do you know what the typical overall cost on CPU this
represents? Or another dimension: What additional latency might this
add to your "average" (loose, I know) method invocation?
Firstly pretend you've got a standard SecurityManager and Sun's PolicyFile.
It really depends on the permission being checked, if it's
AllPermission, it's going to be very fast, but if it's a blocking
Permission, like SocketPermission and there are 10 ProtectionDomain's on
the call stack, (one for each jar file) and some of those
ProtectionDomain's are granted more than one SocketPermission, (most
admins have probably learned to keep this simple, but the proxy server
origin has already been granted at ClassLoader & ProtectionDomain
creation time, and the order's never how you want it, you might be
waiting minutes for a DNS lookup for two dns hostnames, just to see if
they've got identical IP addresses and that's just for one PD. So
you're now blocking other security checks for that ProtectionDomain too,
no AccessControlContext containing that PD is able to proceed until
SocketPermission completes on that ProtectionDomain, even if they're
trying to check other unrelated permissions.
To make matters worse, if the Policy fails, the ProtectionDomain is
checked as well, so the same SocketPermission can be checked twice,
although at least i won't lookup dns again.
To build your cache Sun's PolicyFile, uses CodeSource.implies, which
also blocks on dns, because it uses URL.
Once it enters the dns cache you've still got contention for that (which
is why we've included dnsjava).
With CombinerSecurityManager and ConcurrentPolicyFile, your
SocketPermission's will be ordered dynamically for every permission
check, so if you have a wildcard as well as dns names, the wild card
will be checked first, unless you have an exact match, in which case
SocketPermission.implies won't even be called.
If you've got 10 ProtectionDomain's on the stack, and the
AccessControlContext doesn't already exist in the cache, a number of
Runnable tasks are created and submitted to an Executor to perform
checks in Parallel on each ProtectionDomain in that
AccessControlContext. Some of these will return immediately, while
those that block will probably be suspended by the OS allowing other non
blocking threads to continue. DNS lookups will proceed in parallel
instead of series when required, allowing other permission checks to
avoid blocking.
A big advantage of a non blocking policy and security manager is
deadlock elimination.
If you have multiple threads with identical AccessControlContext's
calling SecurityMangager.checkPermission, then after a permission has
been checked by the policy it's added to the SecurityManager's non
blocking cache until the policy is refreshed. So other threads making
the same call will perform a very fast hash lookup for their
AccessControlContext, followed by a PermissionComparator based lookup on
all the Permissions checked for that AccessControlContext. When
AccessControlContext's are due to be garbage collected, they're removed
from the cache by a background thread, Permission's that aren't used for
5 to 10 minutes are removed also by the same background thread.
When there are only a few ProtectionDomain's in an AccessControlContext,
checks are executed in series. If there are no available Executor
threads, the permission check is performed by the calling thread (to
avoid deadlock).
CodeSource.implies is not utilised by ConcurrentPolicyFile, instead,
CodeSource URL's are converted to URI, normalised and compared, this
avoids dns lookups and disk access so is much faster.
So the bigger the system, the better it will perform, because the
likelyhood of blocking increases.
This is an old although interesting paper on SecurityManager
performance: http://rewerse.eu/publications/download/REWERSE-RP-2005-141.pdf
Creating the AccessControlContext from stack inspection is about half
the cost.
Startup cost for ConcurrentPolicyFile and CombinerSecurityManager is
greater.
I'd be interested to see what the results are like in a real djinn.
Cheers,
Peter.
Cheers,
Peter.