Sunday, April 12, 2009

Race condition in "First obstacles and solutions"

I fixed a bug in my asteroids clone yesterday which turned out to be a race condition. The problem was in map_parallel:


let map_parallel (func : 'a -> 'b) (items : 'a[]) =
let results = Array.zero_create (items.Length)
let count = ref items.Length
use notify = new System.Threading.AutoResetEvent(false)

items
|> Array.iteri (
fun i item ->
System.Threading.ThreadPool.QueueUserWorkItem (
fun _ ->
let res = func item
results.[i] <- res
!: System.Threading.Interlocked.Decrement count |> ignore
!: if !count = 0 then notify.Set() |> ignore
) |> ignore
)
notify.WaitOne() |> ignore
results


I have marked the two faulty lines in the code above with "!:". The (hopefully) correct version is:

let c = System.Threading.Interlocked.Decrement count
if c = 0 then notify.Set() |> ignore


In the faulty version, it is possible for two threads to execute "then notify.Set()". The second thread may then raise an exception as "notify" may already have been disposed of.

The code in the original post was corrected.