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