TLDR: I set a flag in a callback and its value doesn't change in the main loop.
Unfortunately I have to make a simplified version of the code since the original is quite large, but here is the problem I am facing, in a nutshell:
try
try
exchangeBus.OnTradeEvent.AddHandler(tradeEventHandler)
exchangeBus.OnOrderEvent.AddHandler(orderEventHandler)
let mutable keepGoing = true
while keepGoing do
let nodeRunner = buildRunner()
let waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset)
// handle the completion event
nodeRunner.OnCompletionEvent.Add(fun (completionType: CycleCompletionType) ->
match completionType with
| CodeException ex -> reportError side ex true
keepGoing <- false
abortHandle.Set() |> ignore
| Abort reason -> // do some stuff
keepGoing <- false
abortHandle.Set() |> ignore
| AutoAbort reason -> // do some stuff
waitHandle.Set() |> ignore
| NormalClose -> // do some stuff
waitHandle.Set() |> ignore
)
// start the runner
nodeRunner.Startup()
// wait for completion, or abort
WaitHandle.WaitAny([| waitHandle; abortHandle |]) |> ignore
with ex ->
status <- ExceptionState
getRunner(side) ||> (fun r -> r.Signal(CycleCompletionType.CodeException(ex)))
getRunner(side.Opposite) ||> (fun r -> r.Signal(CycleCompletionType.Abort($"exception on the {side.Opposite} side, aborting")))
finally
exchangeBus.OnTradeEvent.RemoveHandler(tradeEventHandler)
exchangeBus.OnOrderEvent.RemoveHandler(orderEventHandler)
so, in short here is how it works:
you have two objects containing this code running in parallel. They implement a loop where they create an object that holds a graph and executes a sequence of code based on nodes in that graph.
There can be an exception happening outside of the code executing the graph, in the callbacks tradeEventHandler and orderEventHandler, but this is handled separately.
once the core executing the graph (nodeRunner) is complete, it sends an event describing how it finished. There are 2 cases which are ok (Normal and AutoAbort) and 2 cases where the process needs to stop (CodeException and Abort).
There are two EventWaitHandle objects, one is unique for this object and is used to control the loop (waitHandle) and the other one is common to the two objects (abortHandle) and is used to tell both of them to stop their operations.
The issue happens when one of the object gets in to the abort state.
The OnCompletionEvent callback gets executed, then we have two things happening:
keepGoing <- false
abortHandle.Set() |> ignore
The abortHandle wait gets set and execution resumes after the WaitHandle.WaitAny call, as expected.
The keepGoing flag (which is local to that loop) gets set to false and... the loop goes on as if it was still true. When I print its state at the beginning of the loop, it is still showing true and not false.
So there must be a threading related issue. I don't know where the OnCompletionEvent call sets the keepGoing flag to false, but it's certainly not the same value as the one used in the loop.
How can I solve this? or, did I miss something obvious?