<!DOCTYPE html><html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body><div style="font-family: sans-serif;"><div class="markdown" style="white-space: normal;">
<p dir="auto">On 5 May 2022, at 12:21, Brian Goetz wrote:</p>
</div><div class="plaintext" style="white-space: normal;"><blockquote style="margin: 0 0 5px; padding-left: 5px; border-left: 2px solid #777777; color: #777777;"><blockquote style="margin: 0 0 5px; padding-left: 5px; border-left: 2px solid #777777; border-left-color: #999999; color: #999999;"><p dir="auto">There are lots of other things to discuss here, including a discussion of what does non-atomic B2 really mean, and whether there are additional risks that come from tearing _between the null and the fields_.</p>
</blockquote><p dir="auto">So, let's discuss non-atomic B2s. (First, note that atomicity is only relevant in the heap; on the stack, everything is thread-confined, so there will be no tearing.)</p>
<p dir="auto">If we have:</p>
<p dir="auto"> non-atomic __b2 class DateTime {
<br>
long date;
<br>
long time;
<br>
}</p>
<p dir="auto">then the layout of a B2 (or a B3.ref) is really (long, long, boolean), not just (long, long), because of the null channel. (We may be able to hide the null channel elsewhere, but that's an optimization.)</p>
</blockquote></div>
<div class="markdown" style="white-space: normal;">
<p dir="auto">That goes straight to a desired optimization, but it leaves something valuable in the dust.</p>
<p dir="auto">The valuable thing is one of the “affordances of references”, which is that a reference to an immutable value can be safely published. This is a core feature of the JMM that applies to all value-based classes.</p>
<p dir="auto">The behavior you are citing is inconsistent with a reference to an object containing an immutable field (of type <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">DateTime.val</code>). It is consistent with a reference to a mutable field or to an array of type <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">DateTime.val[]</code>, but none of our current wrapper types work like that. (Arrays do, which is a problem with arrays.)</p>
<p dir="auto">I see how you got there: You want to apply full flattening to <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">DateTime.ref</code>, simply adding a boolean. That’s a nice data structure but it departs from how we expect boxing of values to work. There are extra races between the components of <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">DateTime</code>, as well as a race between null and non-null states. With today’s value-based classes, a mutable <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">DateTime</code> reference will only show races between null and non-null, and between earlier and later pairs of field values. With this proposed feature, a mutable reference will act as if the wrapper object being referenced were no longer immutable, and not safely published. I think this is too much of a sharp edge, even for an opt-in feature.</p>
<p dir="auto">What I would prefer here is a principle that boxes (including <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">DateTime.ref</code>) are always safely publishable. The possibility to race on individual object states should be confined to the value companion type.</p>
<p dir="auto">That somewhat reduces the optimization for heap variables of reference type of non-atomics. I think that’s a fine price to pay, in order to avoid putting new exceptions into the JMM’s current assurances about safe publication. The optimizations on the val-companion are unaffected. This is good: Reasoning about strange race conditions can concentrate around uses of the val-companion, and all uses of the ref-companion would be race-safe.</p>
<p dir="auto">Part of my discomfort here is that when we say that the fields of a value-based class are final is that we are telling users their instances can be safely published. I don’t want to claw that back, even for a corner case like explicitly non-atomic value classes.</p>
<p dir="auto">I do see that there could be a workaround, if a class <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">Foo</code> allowed field-races even on its reference companion <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">Foo.ref</code>: Manually make a value-based wrapper class <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">AtomicFoo</code> which is (implicitly declared as) atomic and has a final <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">Foo</code> field as its sole payload. In that case I think the JMM will assure me (am I right?) that a variable of type <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">AtomicFoo</code> accesses a stable set of <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">Foo</code> fields, even if that <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">AtomicFoo</code> variable is updated by data races, because its nested field is not raced. And that should be true even if the JVM aggressively flattens <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">AtomicFoo</code> into the <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">Foo</code> fields plus two null channels. That’s all consistent, but I think it will cause bugs as people fumble around with a mix of <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">Foo</code> and <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">AtomicFoo</code> values in containers like <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">ArrayList</code> or <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">Object[]</code>.</p>
</div><div class="plaintext" style="white-space: normal;"><blockquote style="margin: 0 0 5px; padding-left: 5px; border-left: 2px solid #777777; color: #777777;"><p dir="auto">If two threads racily write (d1, t1) and (d2, t2) to a shared mutable DateTime, it is possible for an observer to observe (d1, t2) or (d2, t1). Saying non-atomic says "this is the cost of data races".</p>
</blockquote></div>
<div class="markdown" style="white-space: normal;">
<p dir="auto">(So of course that’s OK for mutable copies of <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">DateTime.val</code>, but that’s not how references behave now or should behave in the future.)</p>
</div><div class="plaintext" style="white-space: normal;"><blockquote style="margin: 0 0 5px; padding-left: 5px; border-left: 2px solid #777777; color: #777777;"><p dir="auto">But additionally, if we have a race between writing null and (d, t), there is another possible form of tearing.</p>
<p dir="auto">Let's write this out more explicitly. Suppose that T1 writes a non-null value (d, t, true), and T2 writes null as (0, 0, false). Then it would be possible to observe (0, 0, true), which means that we would be conceivably exposing the zero value to the user, even though a B2 class might want to hide its zero.</p>
</blockquote></div>
<div class="markdown" style="white-space: normal;">
<p dir="auto">This is another reason to confine races to the value companion, because we are making a plan to protect value companions specially, for cases like this.</p>
</div><div class="plaintext" style="white-space: normal;"><blockquote style="margin: 0 0 5px; padding-left: 5px; border-left: 2px solid #777777; color: #777777;"><p dir="auto">So, suppose instead that we implemented writing a null as simply storing false to the synthetic boolean field. Then, in the event of a race between reader and writer, we could only see values for date and time that were previously put there by some thread. This satisfies the OOTA (out of thin air) safety requirements of the JMM.</p>
</blockquote></div>
<div class="markdown" style="white-space: normal;">
<p dir="auto">I think the right approach here is starting with the semantics of value-based classes (which include safe publication) and working out the allowed implementation techniques.</p>
<p dir="auto">The semantics of a flattened ref are (or should be) that it must behave <em>as if</em> it were a non-flattened ref. (“Should be”: We are talking optimization here, not a changeable variation in the user model adopted randomly as the JIT comes and goes.) A ref, in fact, to a VBC.</p>
<p dir="auto">A non-flattened ref is a thing which you first query as to null-ness, and then if non-null you can load the VBC’s field or fields. (Without races.)</p>
<p dir="auto">So if there is a null channel sitting inside or next to some data fields, the read-access code has to first check for null, and if not null then to load a consistent view of the fields, in such a way that racing writes of null or other values do not impair the consistency.</p>
<p dir="auto">The write-access code can write null by asserting the null flag and (as others have observed) it is an implementation puzzle whether to “clear out” the other storage. (My take is that the JVM could do this during GC at a safepoint, but it is hard to do so at other times.)</p>
<p dir="auto">The write-access code can write non-null by (atomically) setting the field values and then (if that did not already de-assert the null channel) de-asserting the null channel. Again, the fields should be written as a group consistently, so as not to interfere with racing reads or writes. The null channel need not be written consistently.</p>
<p dir="auto">All this would imply that the size of a flattened ref, perhaps including its null channel, should be no larger than a naturally atomic unit of memory, which is 64 or maybe 128 bits today.</p>
<p dir="auto">Your argument above, which I think I buy, is that is also probably possible to place the null channel outside of the naturally atomic unit that contains the other fields; this would allow 9-byte and 17-byte refs.</p>
<p dir="auto">Such a racing null channel, with non-racing payload fields, can be modeled in classic Java in the JMM like this:</p>
<pre style="margin-left: 15px; margin-right: 15px; padding: 5px; background-color: #F7F7F7; border-radius: 5px 5px 5px 5px; overflow-x: auto; max-width: 90vw;"><code style="margin: 0; border-radius: 3px; background-color: #F7F7F7; padding: 0px;">class RacyNullable<V extends ValueBasedClass> {
private non-final boolean isNull = true;
private static final Object GARB = new Object(); //any value OK, even null
private non-final Object v = GARB; //null and GARB never observed
public V get() { return isNull ? null : (V) v; }
public void set(V v) {
if (v == null) { isNull = true; if (EAGER_CLEANUP) cleanup(); }
else { this.v = v; /*race here!*/ isNull = false; }
}
private final boolean EAGER_CLEANUP = false;
private void cleanup() { if (isNull) /*race here?*/ v = GARB; }
}
}
</code></pre>
<p dir="auto">I think really nice flattened refs can be built with “as if” semantics the follow that pattern. They won’t flatten quite as well as some of the “no holds barred” cases discussed by the EG, but they would behave… “as if” …they follow the JMM without surprises.</p>
<p dir="auto">The one race (outside of the cleanup method) is innocuous if the cleanup method is used with restraint. How to do that is a puzzle.</p>
</div><div class="plaintext" style="white-space: normal;"><blockquote style="margin: 0 0 5px; padding-left: 5px; border-left: 2px solid #777777; color: #777777;"><p dir="auto">…
<br>
So we have a choice for how we implement writing nulls, with a pick-your-poison consequence:</p>
<p dir="auto"> - If we do a wide write, and write all the fields to zero, we risk exposing a zero value even when the zero is a bad value;</p>
</blockquote></div>
<div class="markdown" style="white-space: normal;">
<p dir="auto">Yes, that’s like flipping <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">EAGER_CLEANUP</code> above.</p>
<p dir="auto">(After if we go to the trouble of making <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">C.val</code> access-controlled, let’s not make racy refs let the cat back out of the bag!)</p>
</div><div class="plaintext" style="white-space: normal;"><blockquote style="margin: 0 0 5px; padding-left: 5px; border-left: 2px solid #777777; color: #777777;"><p dir="auto"> - If we do a narrow write, and only write the null field, we risk pinning other OOPs in memory</p>
</blockquote></div>
<div class="markdown" style="white-space: normal;">
<p dir="auto">That’s the one I prefer. I think it’s actually a reasonable thing to try for. Basically, the GC would have to special-case those fields in a similar way that it special-cases weak-reference fields. For WR’s the GC clears them under certain non-local conditions. In this case the GC would clear them under a very local condition, the setting of the null channel. GC folks growl about requests like this, but I think this one is reasonable.</p>
<p dir="auto">— John</p>
<p dir="auto">P.S. Next up, a long-ish study on how to put access control on <code style="margin: 0; padding: 0 0.4em; border-radius: 3px; background-color: #F7F7F7;">C.val</code>!</p>
</div></div></body>
</html>