Typechecking the same module concurrently can result in a race condition
Sometimes, ghcide
would attempt to typecheck the same module concurrently(using the same HscEnv). This lead to strange errors when the module used TH and the newName
function.
The example that reproduces the bug uses newName
to generate a data constructor name.
The implementation of newName
is the following, from GHC/Tc/Gen/Splice.hs
instance TH.Quasi TcM where
qNewName s = do { u <- newUnique
; let i = toInteger (getKey u)
; return (TH.mkNameU s i) }
The data constructor gets a new, distinct Unique
during each of the concurrent typechecks. However, only one of these ends up in the NameCache
, leading to the tcg_type_env
(and possibly other things) having an inconsistent state.
From one of the typechecks, we get the following type env:
[a46h :-> Data constructor fake_uid:B.A{d a46h}, ...
From the other, we got:
[a46I :-> Data constructor fake_uid:B.A{d a46I}, ...
When we called makeSimpleDetails
on the result of one of these typechecks, this is the type-env we ended up with was:
[a46I :-> Data constructor fake_uid:B.A{d a46h}, ...
(Note the unique a46I
maps to symbol with unique a46h
)
The unique associated with B.A
in the NameCache
is a46h
Finally, when type-checking a dependent module which tries to use the data constructor B.A
, we get
Can't find interface-file declaration for data constructor fake_uid:B.A{d a46h}
This issue has been fixed in ghcide
by ensuring we never typecheck the same module concurrently.
I'm not sure what ghc should do here. We can't consult the NameCache
in qNewName
because names generated via that method can't capture already existing names. Perhaps all that is needed is to document this behavior with a Note/comment somewhere. Another fix could be a module-keyed lock on the NameCache
, but I doubt the cost is worth it for such a rare and easily worked around issue.