ScopedValue API

Peter Firmstone peter.firmstone at zeus.net.au
Thu Jan 23 12:14:19 UTC 2025


Having had the opportunity to use ScopedValue to replace ThreadLocal, it 
was suggested I might provide some feedback here.

Background: I'm maintaining an OpenJDK fork that retains SM, I merge 
weekly from upstream, this isn't for untrusted code, it's just ensuring 
permissions are only granted to authenticated users using trusted code 
(checked using a SHA256 message digest, or CodeSigner).  I have good 
fortune; I'm not constrained by strict backward compatibility, nor am I 
attempting to create a Sandbox (Graal would be a better place to 
quarantine untrusted code). Permission is no longer Serializable, 
allowing obsolete serial form to be dropped and fields made final.

AccessControlContext is immutable now too, this was necessary, as I'm 
replacing AccessController functionality using ScopedValue to decorate 
doPrivileged calls, with AccessControlContext and the caller Class, and 
StackWalker is used to determine the stack context.

AccessController is initialized very early during the vm initialization 
phase, so initially it just runs privileged calls and returns privileged 
context, until init phase 3 is reached. ScopedValue, Stackwalker and 
other classes depend on by them are initialized prior to init phase 3 
from a static method in threads.cpp.

The second use of ScopedValue is to prevent StackOverflowError occuring 
in a SecurityManager implementation, called CombinerSecurityManager, so 
called because it uses a DomainCombiner to execute permission checks on 
all ProtectionDomain's in a stack concurrently, but more importantly it 
contains a non-blocking timed and weakly reference cache, that uses Doug 
Lee's ConcurrentSkipListSet to cache the results of permission checks 
for AccessControlContext, in a ConcurrentMap. In a system running many 
tasks with the same context, this makes a big performance improvement, 
by avoiding repeat permission checks.   Previously StackOverflowError 
was prevented using a ThreadLocal variable, with a counter that was 
incremented each time recursion occurs, in try - finally blocks, to 
ensure that it was decremented each time any recursion completed, once 
the counter reached a limit, an exception was thrown.   However 
ScopedValue is a much better fit since it uses recursion, it doesn't 
require any try - finally blocks.

CombinerSecurityManager is also using virtual threads to perform 
permission checks on each ProtectionDomain, to avoid blocking on 
platform threads when SocketPermission, FilePermission, are inevitably 
checked.   There are also a non-caching Policy provider and policy 
parser implementations, that parse and stores policy grants in an 
immutable form following safe publication, it uses thread isolation to 
prevent contention on PermissionCollection instances.  There is a policy 
writing tool, that generates polp policy files.

ProtectionDomain now implements equals and hashcode methods, the 
hashcode is final and calculated during construction.  The reason for 
this is many ProtectionDomain instances are created for a Subject's 
Principal[]'s and caching their result improves performance.  Similarly 
AccessControlContext also now has a final hashcode calculated during 
construction.

I've also added support for privileged context to virtual threads, to 
allow virtual threads to be used with privileges, just like platform 
threads.

There are two test failures which occur due to the new AccessController 
functionality, with both platform threads and virtual threads.

I looked into the values of the Thread's inherited and Snapshot's 
Carrier, neither contain the ScopedValue used by AccessController.   It 
may have something to do with earlier class initialization, I'm still 
trying to determine the cause.

|ThreadFlockTest::testThreadExitWithOpenFlock '[1] 
java.lang.ThreadBuilders$VirtualThreadFactory at 21edf8f8' 
java.lang.Exception: Stack trace at 
java.base/java.lang.Thread.dumpStack(Thread.java:2155) at 
ThreadFlockTest.lambda$testThreadExitWithOpenFlock$0(ThreadFlockTest.java:1018) 
at java.base/java.lang.VirtualThread.run(VirtualThread.java:470) 
Exception in thread "" java.util.concurrent.StructureViolationException: 
Scoped value bindings have changed at 
java.base/java.lang.Thread.inheritScopedValueBindings(Thread.java:324)|

-- 
Regards,
  
Peter

On 22/01/2025 8:39 pm, Volkan Yazıcı wrote:
> Hello Peter,
>
> Thanks so much for the feedback. AFAIK, that work is delivered by the 
> Loom crew and they use the `loom-dev` mailing list 
> <https://mail.openjdk.org/mailman/listinfo/loom-dev> for discussions. 
> I think they would really appreciate hearing your feedback: What is 
> the real-world use case you used SVs for? How was that particular 
> logic implemented before? What are the performance/code/semantic 
> changes you observed during migration? Did you encounter any problems? 
> Did you find the API intuitive? etc.
>
> Kind regards.
>
> On Wed, Jan 22, 2025 at 4:11 AM Peter Firmstone 
> <peter.firmstone at zeus.net.au> wrote:
>
>     Just wanted to say, I've been experimenting with replacing
>     ThreadLocal
>     with ScopedValue, this is a great new API, I love the way ScopedValue
>     uses recursion, we can have multiple immutable instances
>     representing a
>     scoped value for a short period and we're not worried about managing
>     state as it goes out of scope as soon as it's no longer in use.
>
>     Cheers,
>
>     Peter.
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/loom-dev/attachments/20250123/9ec42659/attachment.htm>


More information about the loom-dev mailing list