@@ -24,8 +24,10 @@ import TcRnMonad (initIfaceLoad, WhereFrom (ImportByUser))
24
24
import LoadIface (loadInterface )
25
25
import qualified Maybes
26
26
import OpenTelemetry.Eventlog (withSpan )
27
- import System.IO.Unsafe (unsafePerformIO )
28
- import Control.Monad.Extra (mapMaybeM )
27
+ import Control.Monad.Extra (mapMaybeM , join )
28
+ import Control.Concurrent.Extra (newVar , modifyVar )
29
+ import Control.Concurrent.Async (Async , async , waitCatch )
30
+ import Control.Exception (throwIO , mask )
29
31
30
32
-- | An 'HscEnv' with equality. Two values are considered equal
31
33
-- if they are created with the same call to 'newHscEnvEq'.
@@ -39,7 +41,7 @@ data HscEnvEq = HscEnvEq
39
41
, envImportPaths :: Maybe [String ]
40
42
-- ^ If Just, import dirs originally configured in this env
41
43
-- If Nothing, the env import dirs are unaltered
42
- , envPackageExports :: ExportsMap
44
+ , envPackageExports :: IO ExportsMap
43
45
}
44
46
45
47
-- | Wrap an 'HscEnv' into an 'HscEnvEq'.
@@ -58,9 +60,8 @@ newHscEnvEqWithImportPaths :: Maybe [String] -> HscEnv -> [(InstalledUnitId, Dyn
58
60
newHscEnvEqWithImportPaths envImportPaths hscEnv deps = do
59
61
envUnique <- newUnique
60
62
61
- let
62
- -- evaluate lazily, using unsafePerformIO for a pure API
63
- envPackageExports = unsafePerformIO $ withSpan " Package Exports" $ \ _sp -> do
63
+ -- it's very important to delay the package exports computation
64
+ envPackageExports <- onceAsync $ withSpan " Package Exports" $ \ _sp -> do
64
65
-- compute the package imports
65
66
let pkgst = pkgState (hsc_dflags hscEnv)
66
67
depends = explicitPackages pkgst
@@ -119,3 +120,19 @@ instance Hashable HscEnvEq where
119
120
instance Binary HscEnvEq where
120
121
put _ = error " not really"
121
122
get = error " not really"
123
+
124
+ -- | Given an action, produce a wrapped action that runs at most once.
125
+ -- The action is run in an async so it won't be killed by async exceptions
126
+ -- If the function raises an exception, the same exception will be reraised each time.
127
+ onceAsync :: IO a -> IO (IO a )
128
+ onceAsync act = do
129
+ var <- newVar OncePending
130
+ let run as = either throwIO pure =<< waitCatch as
131
+ pure $ mask $ \ unmask -> join $ modifyVar var $ \ v -> case v of
132
+ OnceRunning x -> pure (v, unmask $ run x)
133
+ OncePending -> do
134
+ x <- async (unmask act)
135
+ pure (OnceRunning x, unmask $ run x)
136
+
137
+ data Once a = OncePending | OnceRunning (Async a )
138
+
0 commit comments