I have been trying to do a small Groovy project, and wanted a ConcurrentLinkedHashSet, but Java doesn't provide one. So I set about creating my own using a Gpars agent to 'protect' an ordinary LinkedHashSet. I then created my wrapper class to hold the agent, and delegated the methods on my class to the internal Agent like this (this version is using methodMissing as delegation approach). I tried Groovy interceptable/invokeMethod and could get it to work either
class ConcurrentLinkedHashSet /*implements GroovyInterceptable*/ {
Agent $concurrentHashSet = new Agent (new LinkedHashSet())
/*Object[] toArray () {
$concurrentHashSet.val.asType(LinkedHashSet).toArray()
}*/
def asType (Set) {
$concurrentHashSet.val
}
//set up delegation action to pass all methods to the agent to execute
def /*invokeMethod*/methodMissing (String name, args){
def result
System.out.println "methodMissing with $name and $args on $this"
System.out.println "agent value is : ${$concurrentHashSet.val.dump()} is instance of: ${$concurrentHashSet.getClass()}"
$concurrentHashSet {
System.out.println "\t\tconcHashSet methodMissing: it is of type ${it.getClass()} so called $it.invokemethod ($name, $args) "
if (args == []) {
System.out.println "\t\tconcHashSet methodMissing: invoke method '$name' with no args "
result = it."$name"()//it.invokeMethod (name)
} else {
System.out.println "\t\tconcHashSet methodMissing: invoke method '$name' with args $args"
result = it.invokeMethod(name, *args)//"$name" (*args)//it.invokeMethod(name, args)
}
System.out.println "\tconcHashSet methodMissing: 'it' is now showing as > '$it'"
"success"
}
//System.out.println "agent protected value is now : " + $concurrentHashSet.val + " and result now $result"
System.out.println "agent protected value is now : " + $concurrentHashSet.val.dump() + " and result now $result"
$concurrentHashSet.val
}
}
however when I try to use this - it works first time through, my strings is added, but on the second call on the same missing method the agent.send call is never made, and gets skipped.
So my simple script consumer looks like this
// delegates add method via agent.send first time through but not after !
ConcurrentLinkedHashSet conhs = new ConcurrentLinkedHashSet ()
conhs.add ('col1')
println "1st conHashSet as list : ${conhs.toArray()}"
conhs.add ('col2')
println "2nd conHashSet as list : ${conhs.toArray()}"
// direct calls on an agent using invokeMethod
Agent myHash = new Agent (new LinkedHashSet())
myHash {it.invokeMethod ('add', 'col1')}
println "1st agentHashSet as list : ${myHash.val.toArray()}"
myHash {it.invokeMethod ('add','col2')}
println "2nd agentHashSet as list : ${myHash.val.toArray()}"
and my simple trace log looks like this on the console output
methodMissing with add and [col1] on org2.softwood.rules.utils.ConcurrentLinkedHashSet@3b0090a4
agent value is : <java.util.LinkedHashSet@0 map=[:]> is instance of: class groovyx.gpars.agent.Agent
concHashSet methodMissing: it is of type class java.util.LinkedHashSet so called [] (add, [col1])
concHashSet methodMissing: invoke method 'add' with args [col1]
concHashSet methodMissing: 'it' is now showing as > '[col1]'
agent protected value is now : <java.util.LinkedHashSet@2eaeb1 map=[col1:java.lang.Object@6a2f6f80]> and result now true
methodMissing with toArray and [] on org2.softwood.rules.utils.ConcurrentLinkedHashSet@3b0090a4
agent value is : <java.util.LinkedHashSet@2eaeb1 map=[col1:java.lang.Object@6a2f6f80]> is instance of: class groovyx.gpars.agent.Agent
agent protected value is now : <java.util.LinkedHashSet@2eaeb1 map=[col1:java.lang.Object@6a2f6f80]> and result now null
1st conHashSet as list : [col1]
methodMissing with add and [col2] on org2.softwood.rules.utils.ConcurrentLinkedHashSet@3b0090a4
agent value is : <java.util.LinkedHashSet@2eaeb1 map=[col1:java.lang.Object@6a2f6f80]> is instance of: class groovyx.gpars.agent.Agent
agent protected value is now : <java.util.LinkedHashSet@2eaeb1 map=[col1:java.lang.Object@6a2f6f80]> and result now null
methodMissing with toArray and [] on org2.softwood.rules.utils.ConcurrentLinkedHashSet@3b0090a4
agent value is : <java.util.LinkedHashSet@2eaeb1 map=[col1:java.lang.Object@6a2f6f80]> is instance of: class groovyx.gpars.agent.Agent
agent protected value is now : <java.util.LinkedHashSet@2eaeb1 map=[col1:java.lang.Object@6a2f6f80]> and result now null
2nd conHashSet as list : [col1]
1st agentHashSet as list : [col1]
2nd agentHashSet as list : [col1, col2]
As you can see on the first attempt at delegation you can see the 'concHashSet methodMissing:' trace and the agent calls invokeMethod on it in the agent to effect adding the element.
On the second call to conhs.add ('col2') the agent.sand call never happens and so my extra item never gets added.
This is annoying as I thought I had an easy way to create my ConcurrentLinkedHashSet, but the code doesn't work. What mechanism could I use to get the right outcome?
As you can see when I invokeMethod (add) directly on an Agent<LinkedHashSet>
it works just fine. In my real consuming class if I replace my ConcurrentLinkedHashSet with an ordinary LinkedHashSet it works a dream, but isn't thread safe. I wanted create a thread safe version which depended on trying to get this to work.
I guess I could try and replace the Agent and just use synchronize blocks round my LinkedHashSet - but its a bit ugly - I thought the Gpars Agent would sort all this for me as a general solution pattern as a wrapper with delegation.
PS I tried another tack and this sort of works I think, but doesn't feel right - it uses @Synchronise on invokeMethod on class that implements GroovyInterceptable to achieve a thread safe call when delegating. I am not sure if this truly thread safe or not.
class ConcurrentLinkedHashSet2 implements GroovyInterceptable{
@Delegate private LinkedHashSet $mySet = new LinkedHashSet()
@Synchronized
def invokeMethod (String name, args) {
System.out.println "call to $name intercepted invoke using metaclass invoke"
ConcurrentLinkedHashSet2.metaClass.getMetaMethod(name).invoke (this, *args)
}
}
It works as expected after commenting out the trace output: System.out.println "\t\tconcHashSet methodMissing: it is of type ${it.getClass()} so called $it.invokemethod ($name, $args) "
This line causes an unhandled exception to be thrown from the agent, which terminates the agent.