In Overload 49 Alan Griffiths continued his exploration of the exception safety landscape in the wilds of Java and looked at a problem introduced by Tom Cargill; namely how to make a copy of a whole object holding multiple parts in an exception safe manner. In his article Alan ensured that if construction of the whole object failed, the resources of all fully constructed parts were released. He achieved this by careful use of try/finally blocks and statement ordering - essentially, like this:
public class Part implements Cloneable { ... public void dispose(); } public class Whole { ... public void copy(Whole other) { Part t1 = (Part)other.p1.clone(); if (t1 != null) { try { Part t2 = (Part)other.p2.clone(); if (t2 != null) { t1.setParent(this); t2.setParent(this); // pivot point Part swap1 = t1; t1 = p1; p1 = swap1; Part swap2 = t2; t2 = p2; p2 = swap2; } } finally { t2.dispose(); } } finally { t1.dispose(); } } private Part p1, p2; // never null }
Copy succeeds atomically (and returns) if nothing before the pivot point throws an exception:
-
The cloned parts are held in local variables t1 and t2. The cloned parts are not used to set the p1 and p2 fields directly.
-
The local variables are swapped with the fields only if they are not null. This maintains the invariant that p1 and p2 are never null (the calls to other.p1.clone() and other.p2.clone() do not check other.p1 != null or other.p2 != null).
Copy fails atomically (with an exception) if any of the methods before the pivot point throw an exception:
-
The finally blocks will dispose of any cloned parts.
-
The exception will signal that the copy failed.
-
The target of the copy will be unchanged.
One potential problem with copy as it stands is that if a clone() call returns null there will be no exception, copy will return, and the target of the copy will remain unchanged. This means you don't know the state of the object if the call to copy returns (rather than throwing an exception). There are at least two solutions to this problem:
-
You could make copy return a boolean and return false if any call to clone() returns null. (The finally blocks will still dispose of any successfully cloned parts.)
-
You could throw an exception if a call to clone() returns false.
I prefer the latter option, partly because it fits with the exception-for- failure model, but mostly because it just seems right. We've already established that other.p1 and other.p2 are never null and null simply isn't a clone of something that's not null.
There's another copy subtlety worth mentioning. It concerns the calls to setParent. These calls nicely illustrate that a whole-part relationship is, in general, a two-way relationship. If you follow the code through carefully, you'll see that once the swaps have taken place, the cloned parts (now referred to by the p1 and p2 fields) see the target of the copy (this) as their parent (which is fine), but that the old parts (now in t1 and t2) also see the target of the copy (this) as their parent. In other words, if a Whole holds N parts then N*2 parts will simultaneously think they're in the same Whole. In reality, this probably won't be a problem because half of the parts are about to be disposed of.
The strategy I used in my attempt to progress through the jungle is an interface. The Disposable interface embodies the Disposal Method pattern - it creates a method you can explicitly call to dispose of a resource at a known point in the code (this interface is noticeable by its absence from the Java SDK).
public interface Disposable { void dispose(); }
The next step is to create something that holds a stack of Disposable objects. The purpose of this class will be to hold a number of resources. I call it a stack because it effectively plays the part of the control flow stack in C++. That is, a stack in the sense of the opposite of a heap; the stack that, in C++, holds local objects and calls their destructors when they go out of scope. DisposableStack is an example of the Composite pattern (although I don't actually require the composite in this article).
public final class DisposableStack implements Disposable { public DisposableArray(int fixedCapacity); public void push(Disposable resource); public void clear(); public void dispose(); ... }
Now consider what would happen if Part implemented Cloneable and Disposable (I will remove this assumption later):
public class Part implements Cloneable, Disposable { ... }
With these pieces of the puzzle in place you can collapse the multiple nested try/finally blocks in Whole.copy (one per part) down to just a single try/finally block. Notice that each resource is held twice, once in a local variable (eg t1, t2) and once inside the DisposableStack (resources). The trickiest part is the last three statements before the finally block. These statements ensure that a successful copy (one that passes the pivot point) disposes of the old Part resources just swapped from p1, p2 into t1, t2:
public class Whole { ... public void copy(Whole other) { DisposableStack resources = new DisposableStack(2); try { Part t1 = (Part)other.p1.clone(); push(resources, t1); Part t2 = (Part)other.p2.clone(); push(resources, t2); t1.setParent(this); t2.setParent(this); Part swap1 = t1; t1 = p1; p1 = swap1; Part swap2 = t2; t2 = p2; p2 = swap2; resources.clear(); resources.push(t1); resources.push(t2); } finally { resources.dispose(); } } private static void push( DisposableStack resources, Part resource) { if (resource == null) { throw ... } resources.push(resource); } private Part p1, p2; // never null }
If a call to clone() returns null, the attempt to add it to the resources collection must throw an exception. We'll handle this in a Whole helper method rather than in DisposableStack so that DisposableStack can stay a general purpose class. If all the parts are cloned and added to the resources collection, the part specific calls take place (setParent) and then the swaps are performed. The swaps must not throw an exception. After the swaps, the part clones (not null) are in p1 and p2, and the old parts (also not null) are in t1 and t2. The resources stack is cleared, and the old parts are pushed in ready to be disposed of in the finally block. The code tells us the critical exception guarantees of the methods:
-
DisposableStack.clear() must never throw an exception. This is because the call to clear() happens after the pivot point.
-
If a DisposableStack (eg resources) is initialized with a constructor argument N, then after a call to resources.clear() the next N calls to resources.push(resource) must not throw an exception. Again, this is because these calls happen after the pivot point.
-
DisposableStack.dispose() should allow any exception that arises from it to escape. DisposableStack is a general purpose class and cannot judge the severity of a disposal through an interface.
How can we guarantee these constraints? You might think about using a collection class such as ArrayList. Can ArrayList methods throw exceptions? I'm not sure and I'm not going to bother looking because there is a better solution: use an array. This will not only give us complete control over exceptional behaviour it will probably also makes the solution a little faster. Notice that this version disposes of the resources in a last-in last-out fashion (which seems appropriate), and also avoids the need for any casting:
public final class DisposableStack implements Disposable { public DisposableStack( int fixedCapacity) { resources = new Disposable[fixedCapacity]; at = 0; } public void push(Disposable resource) { // PreCondition(resource != null) // PreCondition(at < resources.length) resources[at] = resource; ++at; } public void clear() { at = 0; } public void dispose() { while (-at > 0) { resources[at].dispose(); } } private final Disposable[] resources; private int at; }
One glitch in this implementation is that if you push null, bad things will happen. You could solve this by checking for null in a push precondition. Another way is to use the Null Object pattern, like this:
public final class NullDisposable implements Disposable { public static final NullDisposable instance = new NullDisposable(); public void dispose() { // all done! } } public final class DisposableStack implements Disposable { ... public void push(Disposable resource) { // PreCondition(at < resources.length) resources[at] = resource != null ? resource : NullDisposable.instance; ++at; } ... }
Something else that bothers me about this solution is the lack of a swap abstraction. If you study Alan's original code you'll see that the swap cannot be factored out because it swaps local variables that are disposed in their following finally blocks. However, at the cost of introducing a temporary object, it is possible:
public class Whole { ... public void copy(Whole other) { DisposableStack resources = new DisposableStack(2); try { Whole copy = new Whole(); copy.p1 = (Part)other.p1.clone() push(resources, copy.p1); copy.p2 = (Part)other.p2.clone(); push(resources, copy.p2); copy.p1.setParent(this); copy.p2.setParent(this); copy.swap(this); resources.clear(); resources.push(copy.p1); resources.push(copy.p2); } finally { resources.dispose(); } } public void swap(Whole other) { Part swap1 = p1; p1 = other.p1; other.p1 = swap1; Part swap2 = p2; p2 = other.p2; other.p2 = swap2; } ... private Part p1, p2; // never null }
Note that this "solution" requires a default Whole constructor that creates an empty Whole object, that is, one that contains no resources. The idea is that you create an empty whole object, and then gradually fill it. If you've already implemented the default constructor and it doesn't do this you can simply create a private constructor with a dummy argument.
public class Whole { ... public Whole copy(Whole other) { DisposableStack resources = new DisposableStack(2); try { Whole empty = new Whole(Empty.instance); // as before } ... } private static final class Empty { public static final Empty instance = new Empty(); } private Whole(Empty unused) { // all done } ... }
This would be a reasonable solution if it worked but unfortunately it doesn't. Before the swap, both objects are well formed. The target of the copy (this) has its parts and these parts know this is their parent. Similarly, the copied object has its parts (clones of this's parts) and these parts know copied is their parent. All well and good! The problem is that the swap only swaps the references; it's a shallow swap and it needs to be a deep swap. After the swap, the parts will be referring to the wrong parents. To swap a whole you have to be able to swap its parts. It's not enough to just swap the references.
public class Part implements Cloneable, Disposable { ... public void swap(Part other) { // swap all fields, must not throw ... } } public class Whole { ... public void swap(Whole other) { // shallow Part swap1 = p1; p1 = other.p1; other.p1 = swap1; Part swap2 = p2; p2 = other.p2; other.p2 = swap2; // deep p1.swap(other.p1); p2.swap(other.p2); } ... }
Let's stop for a moment to think about what we've just done. We've created an empty object so that we can gradually fill it with cloned parts. And remember that we're doing this so we can implement the copy method. With a little reflection the idea of using a copy constructor suggests itself. That way, we won't have to worry about the initial state of the object we're creating. (If you've already implemented the copy constructor and it doesn't do this you can simply create a private constructor with a dummy second argument as before). Should the copy constructor be public? Well, copy method is public so it seems reasonable.
public class Whole { ... public Whole(Whole other) { DisposableStack resources = new DisposableStack(2); try { push(resources, p1 = (Part)other.p1.clone()); push(resources, p2 = (Part)other.p2.clone()); p1.setParent(this); p2.setParent(this); resources.clear(); } finally { resources.dispose(); } } ... private Part p1, p2; // never null }
With this copy constructor in place, the obvious (but wrong) way to write the copy method is as follows:
public class Whole { ... public void copy(Whole other) { Whole copy = new Whole(other); copy.swap(this); } }
The problem is that copy does not dispose of the old parts after the swap. This is not C++ remember! Luckily we have exactly the right tool to fix this problem: DisposableStack.
public class Whole { ... public void copy(Whole other) { DisposableStack resources = new DisposableStack(2); try { Whole copy = new Whole(other); copy.swap(this); push(resources, copy.p1); push(resources, copy.p2); } finally { resources.dispose(); } } ... }
This is now a reasonable solution. However, there is one last refactoring we can perform before returning to the assumption that Part implements Disposable. It revolves around the observation that there is a lot of pushing going on! A possibly more natural approach would be for the pushes to happen only at construction and for the clear to happen only at "disposal". The way to achieve this is to make the DisposableStack a field instead of a local variable. There are a number of points to note in this solution:
-
If there is no exception the copy constructor retains the references to the parts. The assumption is that all the constructors will do this. To do this you need a Boolean variable because there is no way to query whether an exception is currently pending. (Another noticeable omission from the Java SDK; interestingly, I've been told that it is possible to determine whether an exception is pending if you drop down to the JVM level.)
-
The copy method makes a copy using the copy constructor. If this succeeds it swaps itself with the copy. It then disposes of the parts of its old self (which of course are now held in the copy because the swap method swaps the resources field too). An issue you might like to consider is the possibility of an exception arising from the call to copy.resources.dispose. Should this exception be caught and suppressed? How valuable is the simple model of use this would afford? Is it a deep philosophical truth (and holds in all applicable programming languages) that exceptions should never arise from destruction/finalization? Or is disposal different because the object being disposed is still in scope?
-
It's noticeable how the ResourceStack is playing the role of a stack-based local variable in languages like C++ that supports scope based resources.
public class Whole { ... public Whole(Whole other) { boolean exception = true; try { push(p1 = (Part)other.p1.clone()); push(p2 = (Part)other.p2.clone()); p1.setParent(this); p2.setParent(this); exception = false; } finally { if (exception) { resources.dispose(); } } } public void copy(Whole other) { Whole copy = new Whole(other); copy.swap(this); copy.resources.dispose(); } public void swap(Whole other) { // shallow DisposableStack swap0 = resources; resources = other.resources; other.resources = swap0; Part swap1 = p1; p1 = other.p1; other.p1 = swap1; Part swap2 = p2; p2 = other.p2; other.p2 = swap2; // deep p1.swap(other.p1); p2.swap(other.p2); } private void push(Part resource) if (resource == null) throw ... } resources.push(resource); } private DisposableStack resources = new DisposableStack(2); private Part p1, p2; // never null }
Now let's return to the original assumption. What if Part does not implement the Disposable interface? Simple, just use an Adapter.
public final class PartDisposer implements Disposable public PartDisposer(Part adapted) { // PreCondition(adapted != null) adaptee = adapted; } public void dispose() { adaptee.dispose(); } private final Part adaptee; }
Fine, but how do you use the adapter? There is a final trap we must take care not to fall into. The obvious (but flawed) way to use this adapter is as follows:
public class Whole { ... private void push(Part resource) { if (resource == null) { throw ... } resources.push(new PartDisposer(resource)); } ... }
The problem is that if the creation of a new PartDisposer throws an exception the copied Part will not be pushed onto the resources stack. There are a number of ways to fix this. One is via a careful use of a try/finally block in the Whole.push helper method:
public class Whole { ... private void push(Part resource) { if (resource == null) { throw ... } try { resources.push( new PartDisposer(resource)); resource = null; } finally { if (resource != null) { resource.dispose(); } } } ... }
Has it been worth it? Is this version useful? As always there are opposing forces. This version is something of a sledgehammer cracking a nut when the number of parts is small. But as the number of parts increases, so does the depth of the try/finally nesting, and so the more attractive this version becomes. However, it does so at the cost of creating extra objects. It's also noticeable that this version involves a lot less work if the Part classes implement the Disposable interface, thus avoiding the need for Adapters.
It's also important to consider that Java programs normally express copying through the creation of new objects rather than emulating deep assignment. Perhaps it's best to think of this solution to Tom Cargill's whole-part copy problem as showing just one way to use the DisposableStack class. The important thing, as Alan says, is that the exception safety guarantees make sense in Java and should be applied when writing or reviewing code. The problem is that the Java language offers almost no in-built support for this activity. DisposableStack is a class that can help.
In design, as in life, you learn more from making the journey than you do from reaching the destination. I look forward to more, as yet, unvisited trails.
Overload Journal #50 - Aug 2002 + Programming Topics
Browse in : |
All
> Journals
> Overload
> 50
(7)
All > Topics > Programming (877) Any of these categories - All of these categories |