A question on the implementation of clojure.lang.Ref. Ref has a field TVal tvals. TVal clearly is implemented as a circular, doubly-linked list, via fields next and prior A singular object of type TVal has these fields initialized to point to itself. An inspection of all code touching these two fields seems to confirm that the requirements of a circular, doubly-linked list are maintained.
(BTW, the most fun piece of code is in Ref.trimHistory where we find tvals.next = tvals; tvals.prior = tval, discarding the all of the list except the head node itself -- a nice confirmation of the semantics.)
Ref has two constructors. The one-arg ctor just calls the two-arg ctor. The two-arg ctor initializes the tvals field to a new Tval(...) and hence the tvals field is not null upon construction.
There are a few places in the code (in Ref but also in LockingTransaction) that do set the tvals field, but in each case it is either to a new TVal(...) or ref.tvals = ref.tvals.next; . For the latter, given the circular nature, tvals.next will not be null.
Nevertheless, I count 13 places where ref.tvals is tested to see if it is null. Have I missed some situation where tvals can be null? Is someone running around creating bare Refs without using the supplied constructors? are there any other loopholes? Modification to Ref outside of the Ref and LockingTransaction code that I have overlooked.
(This is not idle curiousity -- I'm implementing in a language where if this field can be null I have to choose among declaring the type as allowing null values, declaring the field to be a nullable reference type, or using an option type. Yes vs no here impacts code design.)
While we're on the subject (even if it's been five+ months), there is another field that is checked for null . In this case, the field actually is set to null , but I cannot find a flow of control that exposes that to the places where the field is checked for null.
The field in question is . A LockingTransaction (LT) is created only by the static method LT.runInTransaction, which puts the transaction in thread-local storage and immediately calls LT.run on it. LT.run almost immediately sets the info field on the LT. We have a brief time here when info is null, but there is no way anyone can see that.
The info field is set to null by LT.stop. LT.stop is called by LT.run, LT.blockAndBail, and LT.abort. In LT.run, after the call to stop, either another iteration of the retry loop is run, which immediately sets info again, or it exits, which removes this LT from thread-local storage and no one will see the transaction object again. blockAndBail throws a RetryException which will be caught by run -- same effect: iterate or exit. abort throws an AbortException that will be caught by run leading to an exit
I don't see any way the three methods that check whether info is null can actually see info being null. Am I missing something, or do we just have defensive programming here?
(Why do I care? (a) I hope I'm not missing something obvious about control flow here. (b) I have to code specifically to allow nulls or use an Option type.)
A related question: why does LT.stop bother to set info to null? It does allow the then-current resident of the field to become garbage (assuming it isn't stuck into a tinfo field in a Ref that got touched by the transaction), but we are either going to reset the info slot anyway or turn the whole exception to garbage in just a few nanoseconds, so there is just that tiny gap in time where maybe the GC will run and collect the thing. That's a pretty slim opening. I suppose that opening is a little bigger than normal because in two circumstances an exception is raised and that is going to take more time. Just curious.
LThe field is
I think itโs likely the null clearing is for gc
I assume the other null checking is defensive, could possibly be from earlier impl if not needed
Well, if null clearing for GC was thought to be of use, I'll go with it. Thanks! Some clever tricks in this code. The problem when you take a dozen years between looks, memory doesn't serve well. I'm taking notes this time this through.
I think I now understand essentially every line of code in Ref and LockingTransaction. It's been a journey.
Except one trivial matter -- why does Ref.getHistoryCount require a write lock? It's only reading the history list. It should be able to share the list with any other reader, I think.
don't know of anything else, but since Ref is a non-final class, something else could subclass it and write this field
maybe it's just defensive for that sake
the only other magic constructor path that springs to mind is serialization, but this isn't serializable
so I think you would be ok making the presumption that its non-null in your impl
Serialization came to mind, but as you say ... I think I'm willing to take the chance and seal the class. If anyone wants to subclass, they can ask. ๐ Thanks for the quick response.