RFR: 8209437: Mechanism for decoupling consumers and providers of behaviours

Erik Österlund erik.osterlund at oracle.com
Wed Aug 15 10:36:09 UTC 2018


Hi Andrew,

Thank you for the entertaining reply. I do see your point, and agree.

This is not a mechanism I propose using in general. Just like any other 
mechanism, you can abuse it but probably shouldn't just because you can. 
This is similar to how we wouldn't want to abuse the use of templates. 
Uhhhhm... actually a better example would be how we use static members 
like Universe::heap() instead of passing around the heap everywhere.

There are times indeed when the passing around of context fluff, that is 
not used and does not mean anything along its way to its destination, 
becomes so invasive to the code, that using a mechanism like this one 
makes me happier. For me that is especially in cases where the code that 
uses a behaviour may have the provider of it originate either from a 
global context, a local context, or an inherited context (e.g. a worker 
thread passing on the context of the thread that kicked it off). Or when 
annoying things like keeping track of what GC phase some time is tracked 
for (done in a slightly different way for different GCs, yet sharing 
code), causes so much trouble that we repeatedly run into unnecessarily 
painful refactorings and even give up.

In the concrete scenario I am currently looking at, I find myself with 
this issue:

Unloading nmethods by the GC is currently called by the GC. It passes 
around is_alive closures all over the unloading code, which is performed 
first, and then nmethods are walked again and its IC caches referring to 
said unloaded nmethods are cleaned. That is a bit messy passing around 
the is_alive closure, but it is okay. The closure is passed in by the GC 
and gets passed around a bit.

But now we are working on unloading nmethods concurrently. To do this 
concurrently, we have to be able to unload nmethods in a single pass 
instead of two. That means that the nmethod cleaning code will have to 
know how to (lazily) find out if referenced nmethods from IC caches are 
being concurrently unloaded. And to do that, those is_alive closures are 
needed even in the nmethod cleaning contexts. And this cleaning code is 
now also called from JavaThreads and the sweeper (i.e. no longer only 
invoked from GC code in a safepoint).

With this mechanism, the nmethod unloading and cleaning code doesn't 
need to bother knowing how GC code provides it with an is_alive closure. 
allowing it to check if an oop is broken or not, in its given context 
(serial/parallel/concurrent unloading (from GC and JavaThreads) or 
sweeper invoked IC cleaning).

So while I have been wanting to have a similar mechanism for a long time 
(I have used similar mechanisms privately in most languages I came 
across), this nmethod cleaning was what made me propose doing this. I'm 
okay with not doing this to solve the problem, and finding workarounds 
yet again, but it would make me feel a bit like this :C

Curious to hear if you agree that my use case sounds painful enough to 
motivate a mechanism like this, or not.

Thanks,
/Erik

On 2018-08-14 12:27, Andrew Dinn wrote:
> On 13/08/18 18:05, Erik Österlund wrote:
>> Sometimes we find ourselves passing around context information about how
>> to perform a certain operation (e.g. check if an oop is alive, logging
>> or tracing, etc). Sometimes such behaviours are provided globally by a
>> GC, and sometimes only in local scopes known by the GC. Sometimes it is
>> even accessed from mutators.
>>
>> It would be great to have a general mechanism for decoupling how
>> behaviours are provided, from the code that uses them.
>>
>> In particular, I will need this mechanism to build a new nmethod
>> unloading mechanism for concurrent class unloading. Today we have a
>> single threaded and a parallel nmethod unloading mechanism. Rather than
>> introducing a third concurrent way of doing this, I would like to unify
>> these mechanism into one mechanism that can be used in all three
>> contexts. In order to get there, I need these utilities in order to not
>> make a mess. I have a bunch of other use cases down the road as well.
>>
>> The ideas behind this mechanism are pretty straight forward. Behaviours
>> are provided in different ways by "behaviour providers". The providers
>> may be global and local, but come in a strict layering (each provider
>> has a parent). So from a given callsite, there is a chain of
>> responsibility with behaviour providers. You can use BehaviourMark to
>> provide a behaviour locally in a certain scope. There are also global
>> behaviours to which a GC at bootstrapping time can add behaviours. If no
>> local behaviour was found, the global behaviours are checked as plan B.
>> In order to speed up the walk, the scoped behaviour providers also come
>> with a lazily populated behaviour provider cache that will take you
>> straight to a given provider, effectively skipping through the search
>> through the chain of responsibility.
> The really nice thing about this is that it makes context values appear
> out of thin air without having to explicitly plumb dataflow paths
> through call parameter chains.
>
> The really nasty thing about this is that it makes context values appear
> out of thin air without having to explicitly plumb dataflow paths
> through call parameter chains.
>
> So, is this nice or nasty? I automatically veer towards making plumbing
> visible/explicit rather than hidden. Some context overrides using this
> mechanism may clearly be local and highly visible but for any use
> without an immediately local override it will be very hard to track back
> from the point where a context value is used to /every/ point where it
> might be configured/set. It is critical not to render code opaque to
> those who did not write it (or even to those who did and then took a 2
> month break from looking at it).
>
> Of course, sometimes -- in complex cases -- you can't see the plumbing
> for the pipes (plumbing /is/ pipes just as a wood /is/ trees) and I have
> observed that the GC code does already include such cases (all those
> families of closures that are passed around). So, yes, the current
> alternative is not without its problems. However, at least there is a
> clear marker for what to look at i.e. visible parameterization of the
> call chains.
>
> So, my default response is 'ooooh, nasty' but I can see your point.
>
> regards,
>
>
> Andrew Dinn
> -----------
> Senior Principal Software Engineer
> Red Hat UK Ltd
> Registered in England and Wales under Company Registration No. 03798903
> Directors: Michael Cunningham, Michael ("Mike") O'Neill, Eric Shander



More information about the hotspot-runtime-dev mailing list