I've got a graph. This graph consists of a node class that has the following:
public abstract class AbstractNode{
public Collection<AbstractNode> predecessors;
public Collection<AbstractNode> successors;
//accept methods for visitors
}
I recently added a component that allows you to import configuration from a YAML file. This component reads the YAML file and generates a graph using this node class. This code is fairly slow and makes lots of operations on the graph, including for-eaching through some predecessors, analyzing various nodes, and making decisions about how other nodes successors and/or predecessors should be updated.
Our existing code has a service that polls this same graph once every 100ms to generate a list of errors in our program, using a visitor. A visitor is effectively a class of objects that's going to for-each through every single list of successors and predecessors once.
This is causing concurrent modification exceptions. :(
My choices appear to be:
- update the polling service to be event driven.
- We switched from an event driven strategy to a polling one because the complexity of the events was getting very high. There is a lot of subtle timing issues involved in this.
- Best for 'purity' of model.
- update the problem polling service and my new yaml-importer to both synchronize on a thread work-queue (the obvious choice being the UI thread since they've already got queue-based implementations)
- more dependency on UI framework, if the UI framework isn't available (and our application does operate in a headless command-line mode), then what?
- Implement collections that use closeable-iterables that lock their source until either a
close()
call is made orhasNext()
returns false. This would allow us to either block the writer until the readers complete.- clooge on iterables. Probably fairly safe since nobody actually uses iterators anymore, but still it is an odd use of the contract.
- update the AbstractNode to use
CopyOnWriteArrayCollection
as its backing sets/lists of predecessors and successors- replaces on-boarding friendly POJO
List<String> predecessors = new ArrayList<>()
code withList<String> predecessors = new CopyOnWriteArrayList<>()
- requires additional serializer configuration and/or produces nasty XML/JSON
- replaces on-boarding friendly POJO
- Separate the read from the write model by pushing the graph-mutation operations onto the AbstractNode class, and replace the getSuccesors/Precessors to return a defensive copy and/or an immutable collection (such as those in PCollections)
- requires a great deal of code to solve what is conceptually a very small problem
- requires updating a huge number of places that are consuming this graph expecting standard-mutable-collections to instead use the methods on the AbstractNode
What I really want is for a collections framework that is built around composition rather than around inheritance.
To turn this into a rant for a second, and with the benefit of (lots) of hindsight, its pretty well recognized that when faced with the choice of creating abstract classes and multiple implementations of them, or one class that delegates to dependencies (read: is composed of its dependencies), the latter is universally preferable.
Is there no library to allow me to do something like
private final Collection<AbstractNode> predecessors = Collections.create(
DuplicationPolicies.ForbidDuplicates,
Orders.InsertionOrder,
Concurrency.ThrowOnConcurrentModification
);
for a LinkedHashSet<>
and
private final Collection<AbstractNode> predecessors = Collections.create(
DuplicationPolicies.AllowDuplicates,
Orders.InsertionOrder,
Concurrency.CopyOnWrite
);
for a CopyOnWriteArrayList<>
?
With such a system, it would (presumably) be reasonably simple for me to switch from a concurrency-averse implementation (such as an ArrayList) to a concurrency-tolerant one (such as a CopyOnWrite list or a Shared/Exclusive-locking list).