Finalization and dead references: another proposal

classic Classic list List threaded Threaded
13 messages Options
Reply | Threaded
Open this post in threaded view
|

Finalization and dead references: another proposal

Hans Boehm-2
We're still trying to deal with a fair amount of code that implicitly
assumes that finalization or similar clean-up will not occur while a
pointer to the affected object is in scope. Which is of course not true.

As a reminder, the canonical offending usage (anti-)pattern with
(deprecated, but easier to write) finalizers is

class Foo {
    private long mPtrToNativeFoo;

    private static native void nativeDeallocate(long nativePtr);
    private static native void nativeDoSomething(long nativePtr, long
anotherNativePtr);

    protected void finalize() { ... nativeDeallocate(mPtrToNativeFoo); ... }

    public void doSomething(Foo another) { ...
nativeDoSomething(mPtrToNativeFoo, another.mPtrToNativeFoo) ... }
    ...
}

This is subtly incorrect in that, while executing the final call to
doSomething() on a particular object, just after retrieving mPtrToNativeFoo
and another.mPtrToNativeFoo, but before invoking nativeDoSomething(), the
garbage collector may run, and "this" and "another" may be finalized,
deallocating the native objects their mPtrToNativeFoos refer to.
When nativeDoSomething() finally does run, it may see dangling pointers.

Examples using java.lang.ref or Cleaners (or even WeakHashMap, if you must)
instead of finalize() are as easy to construct, but a bit longer. (The
finalizer version is also arguably incorrect in other ways that are not
relevant here. Pretend this were written in terms of PhantomReferences.)

It is easily possible to construct 100% Java code with the same problem.
Instead of mPtrToNativeFoo, each object stores an integer handle that is
used to access additional Java state logically associated with the object.
But the native pointer case seems to dominate in practice.

Various solutions to this have been proposed, but none seem quite
attractive enough that I actually feel comfortable asking people to update
their code to use them. Noteworthy proposals include:

0) Explicitly call close() on such objects. Great idea when it works. In
general it doesn't, since the code needs to know when the enclosing Java
object is no longer needed. If we always knew that we wouldn't need a GC.
1) Various hacks to keep variables live, e.g. the one based on synchronized
blocks I advocated in my 2004 JavaOne talk. These are slow and ugly, as
we've always admitted. Nobody used them. Incorrect won over slow, ugly, and
complicated ~100% of the time.
2) Java 9's reachabilityFence(). This is better. It can be implemented so
that it's no longer slow. But in many common cases, it's still quite ugly.
For example, the call to nativeDoSomething() above must be followed by two
reachabilityFences, one on this and one on another. And to do this really
correctly, the whole thing would often need to be in a try...finally block.
And in reality code following this pattern usually doesn't have just a
single doSomething method that needs this treatment, but may easily have
dozens. And the rules for placing reachabilityFences can become quite
subtle if there are e.g. locally allocated objects involved. My assessment
is that this isn't good enough. People may no longer write incorrect code
100% of the time, but I'd bet on 70%+.
3) JNI functions can be rewritten, so that the containing Java object is
passed in addition to the native pointers. Somewhat accidentally, this
happens to be roughly free for single argument functions. (Delete
"static".) It adds overhead in other cases, like the one above, and the
rewriting can get somewhat convoluted. In some cases, it doesn't work at
all. AFAIK, it's never actually guaranteed to be correct; it works because
standard implementations don't optimize across the language boundary.
That's not too likely to change. Maybe.
4) We could change the language spec to always prohibit premature
finalization/cleaning in cases like the above. I could personally live with
that solution, and proposed it internally here in the past. But it doesn't
seem to go over well among implementers. And AFAICT, doing it well requires
significant tooling changes, in that we do want to reliably treat local
variables as dead once they go out of scope, a piece of information that
doesn't seem to be reliably preserved in class files. One could argue that
the current class file design implicitly assumes that we can do dead
variable elimination.

After going back and forth on this, my conclusion is that we need a
linguistic mechanism for identifying the case in which the garbage
collector is being used to managed external resources associated with a
field. A (still slowly evolving) proposal to add an annotation to do so is
at

https://docs.google.com/document/d/1yMC4VzZobMQTrVAraMy7xBdWPCJ0p7G5r_43yaqsj-s/edit?usp=sharing

In many ways, this is inherently a compromise. But in the vast majority of
cases, it greatly reduces the required source code changes over all but (4)
above. And I think I could usually explain to an author of currently broken
code in under 5 minutes exactly what they need to do to fix it. And I
wouldn't have to lie much to do so. I don't think (0)-(3) above share that
property.

This has already benefited from comments provided by a few people. (Thanks
to Doug, Jeremy, and Martin, among others.) But I would really like more
feedback, including suggestions about how to proceed.

Hans
Reply | Threaded
Open this post in threaded view
|

Re: Finalization and dead references: another proposal

Vitaly Davidovich
On Wed, Dec 6, 2017 at 7:38 PM Hans Boehm <[hidden email]> wrote:

> We're still trying to deal with a fair amount of code that implicitly
> assumes that finalization or similar clean-up will not occur while a
> pointer to the affected object is in scope. Which is of course not true.
>
> As a reminder, the canonical offending usage (anti-)pattern with
> (deprecated, but easier to write) finalizers is
>
> class Foo {
>     private long mPtrToNativeFoo;
>
>     private static native void nativeDeallocate(long nativePtr);
>     private static native void nativeDoSomething(long nativePtr, long
> anotherNativePtr);
>
>     protected void finalize() { ... nativeDeallocate(mPtrToNativeFoo); ...
> }
>
>     public void doSomething(Foo another) { ...
> nativeDoSomething(mPtrToNativeFoo, another.mPtrToNativeFoo) ... }
>     ...
> }
>
> This is subtly incorrect in that, while executing the final call to
> doSomething() on a particular object, just after retrieving mPtrToNativeFoo
> and another.mPtrToNativeFoo, but before invoking nativeDoSomething(), the
> garbage collector may run, and "this" and "another" may be finalized,
> deallocating the native objects their mPtrToNativeFoos refer to.
> When nativeDoSomething() finally does run, it may see dangling pointers.
>
> Examples using java.lang.ref or Cleaners (or even WeakHashMap, if you must)
> instead of finalize() are as easy to construct, but a bit longer. (The
> finalizer version is also arguably incorrect in other ways that are not
> relevant here. Pretend this were written in terms of PhantomReferences.)
>
> It is easily possible to construct 100% Java code with the same problem.
> Instead of mPtrToNativeFoo, each object stores an integer handle that is
> used to access additional Java state logically associated with the object.
> But the native pointer case seems to dominate in practice.
>
> Various solutions to this have been proposed, but none seem quite
> attractive enough that I actually feel comfortable asking people to update
> their code to use them. Noteworthy proposals include:
>
> 0) Explicitly call close() on such objects. Great idea when it works. In
> general it doesn't, since the code needs to know when the enclosing Java
> object is no longer needed. If we always knew that we wouldn't need a GC.
> 1) Various hacks to keep variables live, e.g. the one based on synchronized
> blocks I advocated in my 2004 JavaOne talk. These are slow and ugly, as
> we've always admitted. Nobody used them. Incorrect won over slow, ugly, and
> complicated ~100% of the time.
> 2) Java 9's reachabilityFence(). This is better. It can be implemented so
> that it's no longer slow. But in many common cases, it's still quite ugly.
> For example, the call to nativeDoSomething() above must be followed by two
> reachabilityFences, one on this and one on another. And to do this really
> correctly, the whole thing would often need to be in a try...finally block.
> And in reality code following this pattern usually doesn't have just a
> single doSomething method that needs this treatment, but may easily have
> dozens. And the rules for placing reachabilityFences can become quite
> subtle if there are e.g. locally allocated objects involved. My assessment
> is that this isn't good enough. People may no longer write incorrect code
> 100% of the time, but I'd bet on 70%+.
> 3) JNI functions can be rewritten, so that the containing Java object is
> passed in addition to the native pointers. Somewhat accidentally, this
> happens to be roughly free for single argument functions. (Delete
> "static".) It adds overhead in other cases, like the one above, and the
> rewriting can get somewhat convoluted. In some cases, it doesn't work at
> all. AFAIK, it's never actually guaranteed to be correct; it works because
> standard implementations don't optimize across the language boundary.
> That's not too likely to change. Maybe.
> 4) We could change the language spec to always prohibit premature
> finalization/cleaning in cases like the above. I could personally live with
> that solution, and proposed it internally here in the past. But it doesn't
> seem to go over well among implementers. And AFAICT, doing it well requires
> significant tooling changes, in that we do want to reliably treat local
> variables as dead once they go out of scope, a piece of information that
> doesn't seem to be reliably preserved in class files. One could argue that
> the current class file design implicitly assumes that we can do dead
> variable elimination.
>
> After going back and forth on this, my conclusion is that we need a
> linguistic mechanism for identifying the case in which the garbage
> collector is being used to managed external resources associated with a
> field.

So kind of the opposite of WeakReference - a SuperStrongReference :).

Kidding aside, it seems like the way you’d want to encapsulate this at the
language level is via a type that the JVM intrinsically knows about; in
this way it’s similar to the reference types today.

An annotation probably does the trick when the value doesn’t escape from
the enclosing instance but I’ve no idea if that assumption covers enough
code to warrant this approach.  AFAICT, if the value escapes into an
instance of another type that doesn’t annotate its field then all bets are
off.

Having a wrapper type would at least make it harder to leak the  native
handle vs the annotation approach.  But of course the wrapper comes with
footprint and indirection costs.  Maybe Valhalla could allow exposing some
magic value type that’s a zero-cost wrapper but preserves the type
information the JIT can track?

> A (still slowly evolving) proposal to add an annotation to do so is
> at
>
>
> https://docs.google.com/document/d/1yMC4VzZobMQTrVAraMy7xBdWPCJ0p7G5r_43yaqsj-s/edit?usp=sharing
>
> In many ways, this is inherently a compromise. But in the vast majority of
> cases, it greatly reduces the required source code changes over all but (4)
> above. And I think I could usually explain to an author of currently broken
> code in under 5 minutes exactly what they need to do to fix it. And I
> wouldn't have to lie much to do so. I don't think (0)-(3) above share that
> property.
>
> This has already benefited from comments provided by a few people. (Thanks
> to Doug, Jeremy, and Martin, among others.) But I would really like more
> feedback, including suggestions about how to proceed.
>
> Hans
>
--
Sent from my phone
Reply | Threaded
Open this post in threaded view
|

Re: Finalization and dead references: another proposal

Peter Levart
In reply to this post by Hans Boehm-2
Hi,

I'm trying to read the rules and imagine cases where they would not work...


/A field declared to be reachability-sensitive is reachability-safe. An
access a to a reachability-safe field of object p inside a (possibly
static) method in p’s class, results in the introduction of
reachabilityFence()s according to the following rules://
//
//For every local reference variable v declared immediately inside
lexical scope s, if s contains such an access a, where p is obtained by
dereferencing v, then Reference.reachabilityFence(v) will be executed
just before either (1) the exit of the scope s, or (2) just before any
assignment to v. For present purposes, “this” is treated as a variable
declared at method scope, as if it were an explicit parameter.//
//If the object containing the field f is allocated in the same
full-expression as the access a, then Reference.reachabilityFence(p),
where p is a reference to the object containing f, is executed at the
end of the full expression.//
//
//The second case covers expressions like//
//
//    nativeDoSomething(new Foo(...).nativeHandle)//
//
//where again the newly allocated object could otherwise be cleaned
before nativeHandle is used.//
/

1st case that comes to mind and is a real case in the JDK is the
existence of sun.nio.ch.DirectBuffer internal (not exported) interface
implemented by NIO direct buffer(s) with method:

     long address();

Utility method like the following:

     static void erase(ByteBuffer bb) {
         unsafe.setMemory(((DirectBuffer)bb).address(), bb.capacity(),
(byte)0);
     }

...won't get reachabilityFence(bb) before return, will it? Calling a
method on a bb doesn't count as a dereference of a reachablity-sensitive
field of bb. Unless methods could also be annotated with the same
annotation. This would only work for instance methods that return a
native resource kept in the same instance or some instance that is
reachable from this instance.

Native resources are typically encapsulated, but there are various
levels of encapsulation. DirectBuffer is an example of module level
encapsulation by non-exported public interface implemented by
package-private classes.

2nd case is artificial. But, since we have streams, lambdas,
optionals... It's easy to fool above rules even if the annotation is put
on the DirectBuffer#address() method ...

static void doSomethingWith1stNonNull(DirectBuffer... dbs) {
     Stream.of(dbs)
         .filter(Objects::nonNull)
         .mapToLong(DirectBuffer::address)
         .findFirst()
         .ifPresent(addr -> {
             ... do something with addr ...
         });
}

Even with imperative programming, one can play with scopes:

static void doSomethingWith1stNonNull(DirectBuffer... dbs) {
     long addr = 0;
     for (DirectBuffer db : dbs) {
         if (db != null) {
             addr = db.address();
             break;
         }
     }
     if (addr != 0) {
         ... do something with addr ...
     }
}

Or, hope that this would work (but the above rules don't cover it):

static void doSomethingWithEither(DirectBuffer db1, DirectBuffer db2) {
     long addr = ((some condition) ? db1 : db2).address();
     ... do something with addr ...
}


But I can imagine that teaching users to not do such foolish things
would be easy. The rules are simple:
- always dereference reachablity-sensitive resources directly through
local reference variables.
- make sure that the local reference variable that you extracted
reachablity-sensitive resource from, is in scope while you perform
operations on the resource.

Regards, Peter


On 12/07/2017 01:38 AM, Hans Boehm wrote:

> We're still trying to deal with a fair amount of code that implicitly
> assumes that finalization or similar clean-up will not occur while a
> pointer to the affected object is in scope. Which is of course not true.
>
> As a reminder, the canonical offending usage (anti-)pattern with
> (deprecated, but easier to write) finalizers is
>
> class Foo {
>      private long mPtrToNativeFoo;
>
>      private static native void nativeDeallocate(long nativePtr);
>      private static native void nativeDoSomething(long nativePtr, long
> anotherNativePtr);
>
>      protected void finalize() { ... nativeDeallocate(mPtrToNativeFoo); ... }
>
>      public void doSomething(Foo another) { ...
> nativeDoSomething(mPtrToNativeFoo, another.mPtrToNativeFoo) ... }
>      ...
> }
>
> This is subtly incorrect in that, while executing the final call to
> doSomething() on a particular object, just after retrieving mPtrToNativeFoo
> and another.mPtrToNativeFoo, but before invoking nativeDoSomething(), the
> garbage collector may run, and "this" and "another" may be finalized,
> deallocating the native objects their mPtrToNativeFoos refer to.
> When nativeDoSomething() finally does run, it may see dangling pointers.
>
> Examples using java.lang.ref or Cleaners (or even WeakHashMap, if you must)
> instead of finalize() are as easy to construct, but a bit longer. (The
> finalizer version is also arguably incorrect in other ways that are not
> relevant here. Pretend this were written in terms of PhantomReferences.)
>
> It is easily possible to construct 100% Java code with the same problem.
> Instead of mPtrToNativeFoo, each object stores an integer handle that is
> used to access additional Java state logically associated with the object.
> But the native pointer case seems to dominate in practice.
>
> Various solutions to this have been proposed, but none seem quite
> attractive enough that I actually feel comfortable asking people to update
> their code to use them. Noteworthy proposals include:
>
> 0) Explicitly call close() on such objects. Great idea when it works. In
> general it doesn't, since the code needs to know when the enclosing Java
> object is no longer needed. If we always knew that we wouldn't need a GC.
> 1) Various hacks to keep variables live, e.g. the one based on synchronized
> blocks I advocated in my 2004 JavaOne talk. These are slow and ugly, as
> we've always admitted. Nobody used them. Incorrect won over slow, ugly, and
> complicated ~100% of the time.
> 2) Java 9's reachabilityFence(). This is better. It can be implemented so
> that it's no longer slow. But in many common cases, it's still quite ugly.
> For example, the call to nativeDoSomething() above must be followed by two
> reachabilityFences, one on this and one on another. And to do this really
> correctly, the whole thing would often need to be in a try...finally block.
> And in reality code following this pattern usually doesn't have just a
> single doSomething method that needs this treatment, but may easily have
> dozens. And the rules for placing reachabilityFences can become quite
> subtle if there are e.g. locally allocated objects involved. My assessment
> is that this isn't good enough. People may no longer write incorrect code
> 100% of the time, but I'd bet on 70%+.
> 3) JNI functions can be rewritten, so that the containing Java object is
> passed in addition to the native pointers. Somewhat accidentally, this
> happens to be roughly free for single argument functions. (Delete
> "static".) It adds overhead in other cases, like the one above, and the
> rewriting can get somewhat convoluted. In some cases, it doesn't work at
> all. AFAIK, it's never actually guaranteed to be correct; it works because
> standard implementations don't optimize across the language boundary.
> That's not too likely to change. Maybe.
> 4) We could change the language spec to always prohibit premature
> finalization/cleaning in cases like the above. I could personally live with
> that solution, and proposed it internally here in the past. But it doesn't
> seem to go over well among implementers. And AFAICT, doing it well requires
> significant tooling changes, in that we do want to reliably treat local
> variables as dead once they go out of scope, a piece of information that
> doesn't seem to be reliably preserved in class files. One could argue that
> the current class file design implicitly assumes that we can do dead
> variable elimination.
>
> After going back and forth on this, my conclusion is that we need a
> linguistic mechanism for identifying the case in which the garbage
> collector is being used to managed external resources associated with a
> field. A (still slowly evolving) proposal to add an annotation to do so is
> at
>
> https://docs.google.com/document/d/1yMC4VzZobMQTrVAraMy7xBdWPCJ0p7G5r_43yaqsj-s/edit?usp=sharing
>
> In many ways, this is inherently a compromise. But in the vast majority of
> cases, it greatly reduces the required source code changes over all but (4)
> above. And I think I could usually explain to an author of currently broken
> code in under 5 minutes exactly what they need to do to fix it. And I
> wouldn't have to lie much to do so. I don't think (0)-(3) above share that
> property.
>
> This has already benefited from comments provided by a few people. (Thanks
> to Doug, Jeremy, and Martin, among others.) But I would really like more
> feedback, including suggestions about how to proceed.
>
> Hans

Reply | Threaded
Open this post in threaded view
|

Re: Finalization and dead references: another proposal

Peter Levart
In reply to this post by Vitaly Davidovich
Hi,

On 12/07/2017 03:27 AM, Vitaly Davidovich wrote:

> So kind of the opposite of WeakReference - a SuperStrongReference :).
>
> Kidding aside, it seems like the way you’d want to encapsulate this at the
> language level is via a type that the JVM intrinsically knows about; in
> this way it’s similar to the reference types today.
>
> An annotation probably does the trick when the value doesn’t escape from
> the enclosing instance but I’ve no idea if that assumption covers enough
> code to warrant this approach.  AFAICT, if the value escapes into an
> instance of another type that doesn’t annotate its field then all bets are
> off.
>
> Having a wrapper type would at least make it harder to leak the  native
> handle vs the annotation approach.  But of course the wrapper comes with
> footprint and indirection costs.  Maybe Valhalla could allow exposing some
> magic value type that’s a zero-cost wrapper but preserves the type
> information the JIT can track?

There is a middle-ground. By (ab)using value types only and no special
JIT magic.

DirectBuffer(s) for example, could return the address not in a long, but
in a value type like the following (using valhalla MVT speak):

public __ByValue final class Address {
     private final long address;
     private final Object referent;

     public _ValueFactory static Address create(long address, Object
referent) {
         Address a = __MakeDefault Address();
         a.address = address;
         a.referent = referent;
         return a;
     }
}


DirectByteBuffer for example, would have the following address() method:

private long address;

public Address address() {
     return Address.create(address, this);
}

Notice that 'address' field of Address value is encapsulated, so Java
code can't access it directly nor it needs to. Native / Unsafe methods
would be taking Address values instead of long(s), making sure the
embedded referent is kept reachable.

This is equivalent to passing the DirectBuffer reference to the native
method(s) together with the address value, but enforced by the API so
the programmer can not make a mistake. There's a lot of arithmetic going
on with addresses, inside and outside of DirectBuffer implementations.
Such arithmetic would have to be performed by the Address value type
itself, making sure the results of operations on Address values are
Address values that maintain the same referent. For example:

public __ByValue final class Address {
...
     public _ValueFactory Address plus(long offset) {
         Address a = __MakeDefault Address();
         a.address = address + offset;
         a.referent = referent;
         return a;
     }
...

So no magic here. Just API.

Regards, Peter


Reply | Threaded
Open this post in threaded view
|

Re: Finalization and dead references: another proposal

Vitaly Davidovich
On Thu, Dec 7, 2017 at 12:28 PM Peter Levart <[hidden email]> wrote:

> Hi,
>
> On 12/07/2017 03:27 AM, Vitaly Davidovich wrote:
> > So kind of the opposite of WeakReference - a SuperStrongReference :).
> >
> > Kidding aside, it seems like the way you’d want to encapsulate this at
> the
> > language level is via a type that the JVM intrinsically knows about; in
> > this way it’s similar to the reference types today.
> >
> > An annotation probably does the trick when the value doesn’t escape from
> > the enclosing instance but I’ve no idea if that assumption covers enough
> > code to warrant this approach.  AFAICT, if the value escapes into an
> > instance of another type that doesn’t annotate its field then all bets
> are
> > off.
> >
> > Having a wrapper type would at least make it harder to leak the  native
> > handle vs the annotation approach.  But of course the wrapper comes with
> > footprint and indirection costs.  Maybe Valhalla could allow exposing
> some
> > magic value type that’s a zero-cost wrapper but preserves the type
> > information the JIT can track?
>
> There is a middle-ground. By (ab)using value types only and no special
> JIT magic.
>
> DirectBuffer(s) for example, could return the address not in a long, but
> in a value type like the following (using valhalla MVT speak):
>
> public __ByValue final class Address {
>      private final long address;
>      private final Object referent;
>
>      public _ValueFactory static Address create(long address, Object
> referent) {
>          Address a = __MakeDefault Address();
>          a.address = address;
>          a.referent = referent;
>          return a;
>      }
> }
>
>
> DirectByteBuffer for example, would have the following address() method:
>
> private long address;
>
> public Address address() {
>      return Address.create(address, this);
> }
>
> Notice that 'address' field of Address value is encapsulated, so Java
> code can't access it directly nor it needs to. Native / Unsafe methods
> would be taking Address values instead of long(s), making sure the
> embedded referent is kept reachable.
>
> This is equivalent to passing the DirectBuffer reference to the native
> method(s) together with the address value, but enforced by the API so
> the programmer can not make a mistake. There's a lot of arithmetic going
> on with addresses, inside and outside of DirectBuffer implementations.
> Such arithmetic would have to be performed by the Address value type
> itself, making sure the results of operations on Address values are
> Address values that maintain the same referent. For example:
>
> public __ByValue final class Address {
> ...
>      public _ValueFactory Address plus(long offset) {
>          Address a = __MakeDefault Address();
>          a.address = address + offset;
>          a.referent = referent;
>          return a;
>      }
> ...
>
> So no magic here. Just API.

This is an API version of Hans’s #3 approach.  As he said, there’s
performance overhead and nothing guarantees that the referent is kept alive
- that’s an implementation artifact.

I think without the VM knowing about these things intrinsically it’s not a
100% reliable solution because it’s not concretely requesting a certain
behavior.

>
>
> Regards, Peter
>
>
> --
Sent from my phone
Reply | Threaded
Open this post in threaded view
|

Re: Finalization and dead references: another proposal

Remi Forax
----- Mail original -----
> De: "Vitaly Davidovich" <[hidden email]>
> À: "Peter Levart" <[hidden email]>
> Cc: "core-libs-dev" <[hidden email]>
> Envoyé: Jeudi 7 Décembre 2017 18:46:41
> Objet: Re: Finalization and dead references: another proposal

> On Thu, Dec 7, 2017 at 12:28 PM Peter Levart <[hidden email]> wrote:
>
>> Hi,
>>
>> On 12/07/2017 03:27 AM, Vitaly Davidovich wrote:
>> > So kind of the opposite of WeakReference - a SuperStrongReference :).
>> >
>> > Kidding aside, it seems like the way you’d want to encapsulate this at
>> the
>> > language level is via a type that the JVM intrinsically knows about; in
>> > this way it’s similar to the reference types today.
>> >
>> > An annotation probably does the trick when the value doesn’t escape from
>> > the enclosing instance but I’ve no idea if that assumption covers enough
>> > code to warrant this approach.  AFAICT, if the value escapes into an
>> > instance of another type that doesn’t annotate its field then all bets
>> are
>> > off.
>> >
>> > Having a wrapper type would at least make it harder to leak the  native
>> > handle vs the annotation approach.  But of course the wrapper comes with
>> > footprint and indirection costs.  Maybe Valhalla could allow exposing
>> some
>> > magic value type that’s a zero-cost wrapper but preserves the type
>> > information the JIT can track?
>>
>> There is a middle-ground. By (ab)using value types only and no special
>> JIT magic.
>>
>> DirectBuffer(s) for example, could return the address not in a long, but
>> in a value type like the following (using valhalla MVT speak):
>>
>> public __ByValue final class Address {
>>      private final long address;
>>      private final Object referent;
>>
>>      public _ValueFactory static Address create(long address, Object
>> referent) {
>>          Address a = __MakeDefault Address();
>>          a.address = address;
>>          a.referent = referent;
>>          return a;
>>      }
>> }
>>
>>
>> DirectByteBuffer for example, would have the following address() method:
>>
>> private long address;
>>
>> public Address address() {
>>      return Address.create(address, this);
>> }
>>
>> Notice that 'address' field of Address value is encapsulated, so Java
>> code can't access it directly nor it needs to. Native / Unsafe methods
>> would be taking Address values instead of long(s), making sure the
>> embedded referent is kept reachable.
>>
>> This is equivalent to passing the DirectBuffer reference to the native
>> method(s) together with the address value, but enforced by the API so
>> the programmer can not make a mistake. There's a lot of arithmetic going
>> on with addresses, inside and outside of DirectBuffer implementations.
>> Such arithmetic would have to be performed by the Address value type
>> itself, making sure the results of operations on Address values are
>> Address values that maintain the same referent. For example:
>>
>> public __ByValue final class Address {
>> ...
>>      public _ValueFactory Address plus(long offset) {
>>          Address a = __MakeDefault Address();
>>          a.address = address + offset;
>>          a.referent = referent;
>>          return a;
>>      }
>> ...
>>
>> So no magic here. Just API.
>
> This is an API version of Hans’s #3 approach.  As he said, there’s
> performance overhead and nothing guarantees that the referent is kept alive
> - that’s an implementation artifact.

it's not even true, the implementation do not even guarantee that the referent field will be kept on stack,
a value type can disappear completely if its field are not used,
so without a reachabilityFence somewhere, it will not work.

>
> I think without the VM knowing about these things intrinsically it’s not a
> 100% reliable solution because it’s not concretely requesting a certain
> behavior.

here is another approach based on the Peter idea,
what we want is that in order to access to an address, you have to send an object that will be kept until the end of the call because the is a call to reachabilityFence at the end, so

class DirectByteBuffer {
  private long address;

  void compute(LongConsumer consumer) {
    consumer.accept(address);
    Reference.reachabilityFence(this);
  }
}


so with the example of Hans,
  private static native void nativeDoSomething(long nativePtr, long anotherNativePtr);

a call to nativeDoSomething, is
  buffer1.compute(address1 -> buffer2.compute(address2 -> nativeDoSomething(address1, address2)));

so at least in term of API it's doable, the question is how to be sure that the JIT will inline the whole thing.

>
>>
>>
>> Regards, Peter
>>
>>
>> --
> Sent from my phone

cheers,
Rémi
Reply | Threaded
Open this post in threaded view
|

Re: Finalization and dead references: another proposal

Vitaly Davidovich
On Thu, Dec 7, 2017 at 1:12 PM, Remi Forax <[hidden email]> wrote:

> ----- Mail original -----
> > De: "Vitaly Davidovich" <[hidden email]>
> > À: "Peter Levart" <[hidden email]>
> > Cc: "core-libs-dev" <[hidden email]>
> > Envoyé: Jeudi 7 Décembre 2017 18:46:41
> > Objet: Re: Finalization and dead references: another proposal
>
> > On Thu, Dec 7, 2017 at 12:28 PM Peter Levart <[hidden email]>
> wrote:
> >
> >> Hi,
> >>
> >> On 12/07/2017 03:27 AM, Vitaly Davidovich wrote:
> >> > So kind of the opposite of WeakReference - a SuperStrongReference :).
> >> >
> >> > Kidding aside, it seems like the way you’d want to encapsulate this at
> >> the
> >> > language level is via a type that the JVM intrinsically knows about;
> in
> >> > this way it’s similar to the reference types today.
> >> >
> >> > An annotation probably does the trick when the value doesn’t escape
> from
> >> > the enclosing instance but I’ve no idea if that assumption covers
> enough
> >> > code to warrant this approach.  AFAICT, if the value escapes into an
> >> > instance of another type that doesn’t annotate its field then all bets
> >> are
> >> > off.
> >> >
> >> > Having a wrapper type would at least make it harder to leak the
> native
> >> > handle vs the annotation approach.  But of course the wrapper comes
> with
> >> > footprint and indirection costs.  Maybe Valhalla could allow exposing
> >> some
> >> > magic value type that’s a zero-cost wrapper but preserves the type
> >> > information the JIT can track?
> >>
> >> There is a middle-ground. By (ab)using value types only and no special
> >> JIT magic.
> >>
> >> DirectBuffer(s) for example, could return the address not in a long, but
> >> in a value type like the following (using valhalla MVT speak):
> >>
> >> public __ByValue final class Address {
> >>      private final long address;
> >>      private final Object referent;
> >>
> >>      public _ValueFactory static Address create(long address, Object
> >> referent) {
> >>          Address a = __MakeDefault Address();
> >>          a.address = address;
> >>          a.referent = referent;
> >>          return a;
> >>      }
> >> }
> >>
> >>
> >> DirectByteBuffer for example, would have the following address() method:
> >>
> >> private long address;
> >>
> >> public Address address() {
> >>      return Address.create(address, this);
> >> }
> >>
> >> Notice that 'address' field of Address value is encapsulated, so Java
> >> code can't access it directly nor it needs to. Native / Unsafe methods
> >> would be taking Address values instead of long(s), making sure the
> >> embedded referent is kept reachable.
> >>
> >> This is equivalent to passing the DirectBuffer reference to the native
> >> method(s) together with the address value, but enforced by the API so
> >> the programmer can not make a mistake. There's a lot of arithmetic going
> >> on with addresses, inside and outside of DirectBuffer implementations.
> >> Such arithmetic would have to be performed by the Address value type
> >> itself, making sure the results of operations on Address values are
> >> Address values that maintain the same referent. For example:
> >>
> >> public __ByValue final class Address {
> >> ...
> >>      public _ValueFactory Address plus(long offset) {
> >>          Address a = __MakeDefault Address();
> >>          a.address = address + offset;
> >>          a.referent = referent;
> >>          return a;
> >>      }
> >> ...
> >>
> >> So no magic here. Just API.
> >
> > This is an API version of Hans’s #3 approach.  As he said, there’s
> > performance overhead and nothing guarantees that the referent is kept
> alive
> > - that’s an implementation artifact.
>
> it's not even true, the implementation do not even guarantee that the
> referent field will be kept on stack,
> a value type can disappear completely if its field are not used,
> so without a reachabilityFence somewhere, it will not work.
>
Not sure what you mean here.  Even if the value type is scalar replaced
(which I'd actually hope to be the case for something like this, but that's
an aside), it doesn't change anything - that referent is passed into a
black hole (i.e. the JNI call) that, as of today, the JIT cannot reason
around.  As such, the referent is kept alive across the call.  That's #3
that Hans mentioned.  The value type aspect doesn't change anything other
than not requiring a separate heap object to be the wrapper.

>
> >
> > I think without the VM knowing about these things intrinsically it’s not
> a
> > 100% reliable solution because it’s not concretely requesting a certain
> > behavior.
>
> here is another approach based on the Peter idea,
> what we want is that in order to access to an address, you have to send an
> object that will be kept until the end of the call because the is a call to
> reachabilityFence at the end, so
>
> class DirectByteBuffer {
>   private long address;
>
>   void compute(LongConsumer consumer) {
>     consumer.accept(address);
>     Reference.reachabilityFence(this);
>   }
> }
>
>
> so with the example of Hans,
>   private static native void nativeDoSomething(long nativePtr, long
> anotherNativePtr);
>
> a call to nativeDoSomething, is
>   buffer1.compute(address1 -> buffer2.compute(address2 ->
> nativeDoSomething(address1, address2)));
>
> so at least in term of API it's doable, the question is how to be sure
> that the JIT will inline the whole thing.
>
It's not just inlining, but escape analysis as well (for this particular
example).  And this particular API will be very much a victim of profile
pollution, and so inlining will stop once this goes megamorphic (and it
probably will).  Once inlining stops, EA will most likely fall apart as
well.

>
> >
> >>
> >>
> >> Regards, Peter
> >>
> >>
> >> --
> > Sent from my phone
>
> cheers,
> Rémi
>
Reply | Threaded
Open this post in threaded view
|

Re: Finalization and dead references: another proposal

Peter Levart
In reply to this post by Vitaly Davidovich


On 12/07/2017 06:46 PM, Vitaly Davidovich wrote:

>
>     So no magic here. Just API.
>
> This is an API version of Hans’s #3 approach.  As he said, there’s
> performance overhead and nothing guarantees that the referent is kept
> alive - that’s an implementation artifact.
>
> I think without the VM knowing about these things intrinsically it’s
> not a 100% reliable solution because it’s not concretely requesting a
> certain behavior.

I would say that for JNI "there’s performance overhead XOR nothing
guarantees that the referent is kept alive", because the overhead is
caused by reference parameter management that guarantees that the
referents are kept alive while the native method executes and may access
them. Can we live with such overhead? I don't know. Would have to
measure if it really presents a problem.

The magic would have to be performed by intrinsified method(s) (Unsafe
for example), but they are just few and developed by select group of
developers.

The main problem I think is not the overhead of passing referents
together with addresses to JNI functions and their overheads, but lack
of enforcement for this to be performed consistently. It's just too easy
to split the native resource from the referent that keeps it into two
parts with each having separate lifetime. API with value types could
enforce such pairs (address, referent) to be kept together until they
hit the final leaf operation which is typically a native method where
the referent(s) are either automatically kept alive or would have to be
manually, but such methods are in minority.

Regards, Peter

Reply | Threaded
Open this post in threaded view
|

Re: Finalization and dead references: another proposal

Vitaly Davidovich
On Thu, Dec 7, 2017 at 1:22 PM, Peter Levart <[hidden email]> wrote:

>
>
> On 12/07/2017 06:46 PM, Vitaly Davidovich wrote:
>
> So no magic here. Just API.
>
> This is an API version of Hans’s #3 approach.  As he said, there’s
> performance overhead and nothing guarantees that the referent is kept alive
> - that’s an implementation artifact.
>
> I think without the VM knowing about these things intrinsically it’s not a
> 100% reliable solution because it’s not concretely requesting a certain
> behavior.
>
>
> I would say that for JNI "there’s performance overhead XOR nothing
> guarantees that the referent is kept alive", because the overhead is caused
> by reference parameter management that guarantees that the referents are
> kept alive while the native method executes and may access them. Can we
> live with such overhead? I don't know. Would have to measure if it really
> presents a problem.
>
I'd say the bigger overhead is in JNI argument marshaling.  But JNI is
already costly so I don't know.

>
> The magic would have to be performed by intrinsified method(s) (Unsafe for
> example), but they are just few and developed by select group of developers.
>
> The main problem I think is not the overhead of passing referents together
> with addresses to JNI functions and their overheads, but lack of
> enforcement for this to be performed consistently. It's just too easy to
> split the native resource from the referent that keeps it into two parts
> with each having separate lifetime. API with value types could enforce such
> pairs (address, referent) to be kept together until they hit the final leaf
> operation which is typically a native method where the referent(s) are
> either automatically kept alive or would have to be manually, but such
> methods are in minority.
>
I agree that the main problem is how to not "detach" these things by
accident.  To that end, you want to bundle them together as tightly as
possible, and prevent de-encapsulation by the users (or at least make it
harder and more explicit).  This is why I threw value types out there since
I think they might provide a better solution than an annotation, which I
suspect will be very easy to accidentally circumvent.

But ultimately, you really want the JVM to understand about "foreign
resources/pointers" in a 1st class/principled manner, which suggests a type
based approach (I'm assuming a language approach is off the table :)).
Since this type would be requesting special handling by the JVM, it's hard
to make it library/API only.  Or rather, it would be hard, if not
impossible, to make it library/API only but then not suffer performance
consequences.  You also don't want to accidentally omit or put the
reachabilityFence() in the wrong place or on the wrong object, both things
possible with one careless refactoring (gone wrong).

>
> Regards, Peter
>
>
Reply | Threaded
Open this post in threaded view
|

Re: Finalization and dead references: another proposal

Peter Levart
In reply to this post by Remi Forax
Hi Remi,

On 12/07/2017 07:12 PM, Remi Forax wrote:
>>> So no magic here. Just API.
>> This is an API version of Hans’s #3 approach.  As he said, there’s
>> performance overhead and nothing guarantees that the referent is kept alive
>> - that’s an implementation artifact.
> it's not even true, the implementation do not even guarantee that the referent field will be kept on stack,
> a value type can disappear completely if its field are not used,
> so without a reachabilityFence somewhere, it will not work.
>

What do you mean by saying "the implementation do not even guarantee
that the referent field will be kept on stack".

If a referent is passed through a reference typed parameter to the JNI
method (there's no other way actually), then such referent must be kept
alive for the entire time the native method executes, because native
method may call methods or access fields on such referent. Because
there's no cross-native-baundary optimization going on currently, JNI
must guarantee that. If/when there will be cross-native-baundary
optimization, I suppose native side would have to have an equivalent to
reachabilityFence too ...

Regards, Peter

Reply | Threaded
Open this post in threaded view
|

Re: Finalization and dead references: another proposal

Peter Levart
In reply to this post by Peter Levart


On 12/07/2017 07:22 PM, Peter Levart wrote:

>
>
> On 12/07/2017 06:46 PM, Vitaly Davidovich wrote:
>>
>>     So no magic here. Just API.
>>
>> This is an API version of Hans’s #3 approach. As he said, there’s
>> performance overhead and nothing guarantees that the referent is kept
>> alive - that’s an implementation artifact.
>>
>> I think without the VM knowing about these things intrinsically it’s
>> not a 100% reliable solution because it’s not concretely requesting a
>> certain behavior.
>
> I would say that for JNI "there’s performance overhead XOR nothing
> guarantees that the referent is kept alive", because the overhead is
> caused by reference parameter management that guarantees that the
> referents are kept alive while the native method executes and may
> access them. Can we live with such overhead? I don't know. Would have
> to measure if it really presents a problem.
>
> The magic would have to be performed by intrinsified method(s) (Unsafe
> for example), but they are just few and developed by select group of
> developers.
>
> The main problem I think is not the overhead of passing referents
> together with addresses to JNI functions and their overheads, but lack
> of enforcement for this to be performed consistently. It's just too
> easy to split the native resource from the referent that keeps it into
> two parts with each having separate lifetime. API with value types
> could enforce such pairs (address, referent) to be kept together until
> they hit the final leaf operation which is typically a native method
> where the referent(s) are either automatically kept alive or would
> have to be manually, but such methods are in minority.
>
> Regards, Peter
>

There's a quick solution to get rid of that overhead. Each native method
(or intrinsified leaf method such as those on Unsafe) can have a java
front end. They are in minority and kept under control. Similar to
argument validation front-ends for intrinsified methods. For example:

public class Unsafe {

     public Address allocateMemory(long bytes, Object referent) {
         return Address.create(allocateMemory(bytes), referent);
     }

     public void setMemory(Address address, long bytes, byte value) {
         setMemory(/* access check absent equivalent of */
address.address, bytes, value);
         Reference.reachabilityFence(/* access check absent equivalent
of */ address.referent);
     }
     ...


Regards, Peter

Reply | Threaded
Open this post in threaded view
|

Re: Finalization and dead references: another proposal

Hans Boehm-2
In reply to this post by Vitaly Davidovich
Some sort of zero cost wrapper type for native pointers, with semantics
similar to the annotation, would certainly be better than just the
annotation. My best guess is that it would cover something like 90% of use
cases. I certainly don't want to preclude it, once we have such a mechanism.

But I'm not yet convinced that a "native pointer" type covers enough of the
use cases that it can serve as the only mechanism. Not all handles
requiring some sort of deletion are represented as native pointers.
Sometimes this problem does arise in pure Java contexts, e.g.
FinalizableDelegatedExecutorService. And there are cases in which the Java
object being cleaned owns the native pointer indirectly, via another Java
object.

On Wed, Dec 6, 2017 at 6:27 PM, Vitaly Davidovich <[hidden email]> wrote:

> On Wed, Dec 6, 2017 at 7:38 PM Hans Boehm <[hidden email]> wrote:
>
>> We're still trying to deal with a fair amount of code that implicitly
>> assumes that finalization or similar clean-up will not occur while a
>> pointer to the affected object is in scope. Which is of course not true.
>>
>> As a reminder, the canonical offending usage (anti-)pattern with
>> (deprecated, but easier to write) finalizers is
>>
>> class Foo {
>>     private long mPtrToNativeFoo;
>>
>>     private static native void nativeDeallocate(long nativePtr);
>>     private static native void nativeDoSomething(long nativePtr, long
>> anotherNativePtr);
>>
>>     protected void finalize() { ... nativeDeallocate(mPtrToNativeFoo);
>> ... }
>>
>>     public void doSomething(Foo another) { ...
>> nativeDoSomething(mPtrToNativeFoo, another.mPtrToNativeFoo) ... }
>>     ...
>> }
>>
>> This is subtly incorrect in that, while executing the final call to
>> doSomething() on a particular object, just after retrieving
>> mPtrToNativeFoo
>> and another.mPtrToNativeFoo, but before invoking nativeDoSomething(), the
>> garbage collector may run, and "this" and "another" may be finalized,
>> deallocating the native objects their mPtrToNativeFoos refer to.
>> When nativeDoSomething() finally does run, it may see dangling pointers.
>>
>> Examples using java.lang.ref or Cleaners (or even WeakHashMap, if you
>> must)
>> instead of finalize() are as easy to construct, but a bit longer. (The
>> finalizer version is also arguably incorrect in other ways that are not
>> relevant here. Pretend this were written in terms of PhantomReferences.)
>>
>> It is easily possible to construct 100% Java code with the same problem.
>> Instead of mPtrToNativeFoo, each object stores an integer handle that is
>> used to access additional Java state logically associated with the object.
>> But the native pointer case seems to dominate in practice.
>>
>> Various solutions to this have been proposed, but none seem quite
>> attractive enough that I actually feel comfortable asking people to update
>> their code to use them. Noteworthy proposals include:
>>
>> 0) Explicitly call close() on such objects. Great idea when it works. In
>> general it doesn't, since the code needs to know when the enclosing Java
>> object is no longer needed. If we always knew that we wouldn't need a GC.
>> 1) Various hacks to keep variables live, e.g. the one based on
>> synchronized
>> blocks I advocated in my 2004 JavaOne talk. These are slow and ugly, as
>> we've always admitted. Nobody used them. Incorrect won over slow, ugly,
>> and
>> complicated ~100% of the time.
>> 2) Java 9's reachabilityFence(). This is better. It can be implemented so
>> that it's no longer slow. But in many common cases, it's still quite ugly.
>> For example, the call to nativeDoSomething() above must be followed by two
>> reachabilityFences, one on this and one on another. And to do this really
>> correctly, the whole thing would often need to be in a try...finally
>> block.
>> And in reality code following this pattern usually doesn't have just a
>> single doSomething method that needs this treatment, but may easily have
>> dozens. And the rules for placing reachabilityFences can become quite
>> subtle if there are e.g. locally allocated objects involved. My assessment
>> is that this isn't good enough. People may no longer write incorrect code
>> 100% of the time, but I'd bet on 70%+.
>> 3) JNI functions can be rewritten, so that the containing Java object is
>> passed in addition to the native pointers. Somewhat accidentally, this
>> happens to be roughly free for single argument functions. (Delete
>> "static".) It adds overhead in other cases, like the one above, and the
>> rewriting can get somewhat convoluted. In some cases, it doesn't work at
>> all. AFAIK, it's never actually guaranteed to be correct; it works because
>> standard implementations don't optimize across the language boundary.
>> That's not too likely to change. Maybe.
>> 4) We could change the language spec to always prohibit premature
>> finalization/cleaning in cases like the above. I could personally live
>> with
>> that solution, and proposed it internally here in the past. But it doesn't
>> seem to go over well among implementers. And AFAICT, doing it well
>> requires
>> significant tooling changes, in that we do want to reliably treat local
>> variables as dead once they go out of scope, a piece of information that
>> doesn't seem to be reliably preserved in class files. One could argue that
>> the current class file design implicitly assumes that we can do dead
>> variable elimination.
>>
>> After going back and forth on this, my conclusion is that we need a
>> linguistic mechanism for identifying the case in which the garbage
>> collector is being used to managed external resources associated with a
>> field.
>
> So kind of the opposite of WeakReference - a SuperStrongReference :).
>
> Kidding aside, it seems like the way you’d want to encapsulate this at the
> language level is via a type that the JVM intrinsically knows about; in
> this way it’s similar to the reference types today.
>
> An annotation probably does the trick when the value doesn’t escape from
> the enclosing instance but I’ve no idea if that assumption covers enough
> code to warrant this approach.  AFAICT, if the value escapes into an
> instance of another type that doesn’t annotate its field then all bets are
> off.
>
> Having a wrapper type would at least make it harder to leak the  native
> handle vs the annotation approach.  But of course the wrapper comes with
> footprint and indirection costs.  Maybe Valhalla could allow exposing some
> magic value type that’s a zero-cost wrapper but preserves the type
> information the JIT can track?
>
>> A (still slowly evolving) proposal to add an annotation to do so is
>> at
>>
>> https://docs.google.com/document/d/1yMC4VzZobMQTrVAraMy7xBdWPCJ0p
>> 7G5r_43yaqsj-s/edit?usp=sharing
>>
>> In many ways, this is inherently a compromise. But in the vast majority of
>> cases, it greatly reduces the required source code changes over all but
>> (4)
>> above. And I think I could usually explain to an author of currently
>> broken
>> code in under 5 minutes exactly what they need to do to fix it. And I
>> wouldn't have to lie much to do so. I don't think (0)-(3) above share that
>> property.
>>
>> This has already benefited from comments provided by a few people. (Thanks
>> to Doug, Jeremy, and Martin, among others.) But I would really like more
>> feedback, including suggestions about how to proceed.
>>
>> Hans
>>
> --
> Sent from my phone
>
Reply | Threaded
Open this post in threaded view
|

Re: Finalization and dead references: another proposal

Hans Boehm-2
In reply to this post by Peter Levart
On Thu, Dec 7, 2017 at 7:49 AM, Peter Levart <[hidden email]> wrote:

> ...
> 1st case that comes to mind and is a real case in the JDK is the existence
> of sun.nio.ch.DirectBuffer internal (not exported) interface implemented by
> NIO direct buffer(s) with method:
>
>     long address();
>
> Utility method like the following:
>
>     static void erase(ByteBuffer bb) {
>         unsafe.setMemory(((DirectBuffer)bb).address(), bb.capacity(),
> (byte)0);
>     }
>
> ...won't get reachabilityFence(bb) before return, will it? Calling a
> method on a bb doesn't count as a dereference of a reachablity-sensitive
> field of bb. Unless methods could also be annotated with the same
> annotation. This would only work for instance methods that return a native
> resource kept in the same instance or some instance that is reachable from
> this instance.
>
> Native resources are typically encapsulated, but there are various levels
> of encapsulation. DirectBuffer is an example of module level encapsulation
> by non-exported public interface implemented by package-private classes.
>

I think there are two issues here. The first one is the use of a getter to
retrieve the value of the address field. This yields a value whose logical
lifetime is limited by the lifetime of a separate garbage-collected Java
object. That feels very un-Java-like and dangerous. My immediate
inclination is to strongly discourage this idiom.

A second issue is a possible access to an @ReachabilitySensitive field from
outside the declaring class. I've gone back and forth as to whether they
should be covered, i.e. result in a logical reachabilityFence(bb). It's
easy enough to specify either way. The major difference is whether an
implementation that simply avoids doing dead reference elimination on any
class containing an @ReachabilitySensitive annotation is conforming. If you
want this to work, the criterion becomes "accesses
an @ReachabilitySensitive field". Which may be OK?


> 2nd case is artificial. But, since we have streams, lambdas, optionals...
> It's easy to fool above rules even if the annotation is put on the
> DirectBuffer#address() method ...
>
> static void doSomethingWith1stNonNull(DirectBuffer... dbs) {
>     Stream.of(dbs)
>         .filter(Objects::nonNull)
>         .mapToLong(DirectBuffer::address)
>         .findFirst()
>         .ifPresent(addr -> {
>             ... do something with addr ...
>         });
> }
>
> Even with imperative programming, one can play with scopes:
>
> static void doSomethingWith1stNonNull(DirectBuffer... dbs) {
>     long addr = 0;
>     for (DirectBuffer db : dbs) {
>         if (db != null) {
>             addr = db.address();
>             break;
>         }
>     }
>     if (addr != 0) {
>         ... do something with addr ...
>     }
> }
>

Aside from the above issues, the intent was for these to work.   A
rechabilityFence(dbs) should be generated at the end of either method
(possibly among others).

>
> Or, hope that this would work (but the above rules don't cover it):
>
> static void doSomethingWithEither(DirectBuffer db1, DirectBuffer db2) {
>     long addr = ((some condition) ? db1 : db2).address();
>     ... do something with addr ...
> }
>
> Again, this should be covered, aside from the issues discussed above. If
this were an access to an @ReachabilitySensitive field, a
reachaibilityFence for the appropriate dbX is required at the end. A real
compiler would just generate one each for both.


> But I can imagine that teaching users to not do such foolish things would
> be easy. The rules are simple:
> - always dereference reachablity-sensitive resources directly through
> local reference variables.
> - make sure that the local reference variable that you extracted
> reachablity-sensitive resource from, is in scope while you perform
> operations on the resource.
>
> Regards, Peter
>
>
>