Skip to content

Commit 08f4f6d

Browse files
committed
Update to semaphore-compat 2.0.0 fixing #9993 and https://gitlab.haskell.org/ghc/ghc/-/issues/25087
semaphore-compat now uses a unix sockets based implementation. Semaphore identifiers are now versioned, and we can really on the versioning scheme to detect semaphore version mismatch with GHC and fallback gracefully if possible. GHC patch: https://gitlab.haskell.org/ghc/ghc/-/merge_requests/15729 After the GHC patch lands, it will include a "Semaphore version" field in its settings file/`--info` output that we can use to guide Cabal behaviour. If this field does not exist (and ghc is 9.8+), then we assume it uses version v1 of the protocol and this triggers a graceful degradation of behaviour to `-jN` without semaphore based coordination. See also semaphore-compat MR: https://gitlab.haskell.org/ghc/semaphore-compat/-/merge_requests/8 ghc-proposals change: ghc-proposals/ghc-proposals#673
1 parent c5c2072 commit 08f4f6d

4 files changed

Lines changed: 84 additions & 22 deletions

File tree

Cabal/src/Distribution/Simple/Compiler.hs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ module Distribution.Simple.Compiler
8686
, libraryDynDirSupported
8787
, libraryVisibilitySupported
8888
, jsemSupported
89+
, jsemVersion
8990
, reexportedAsSupported
9091

9192
-- * Support for profiling detail levels
@@ -480,6 +481,17 @@ jsemSupported comp = case compilerFlavor comp of
480481
where
481482
v = compilerVersion comp
482483

484+
-- | What semaphore protocol version does this compiler use?
485+
--
486+
-- Returns @Nothing@ for compilers that don't report a "Semaphore version"
487+
-- field in @ghc --info@ (i.e. GHC 9.8–9.14, which use v1).
488+
jsemVersion :: Compiler -> Maybe Int
489+
jsemVersion comp = case compilerFlavor comp of
490+
GHC -> case Map.lookup "Semaphore version" (compilerProperties comp) of
491+
Just verStr | [(v, "")] <- reads verStr -> Just v
492+
_ -> Nothing
493+
_ -> Nothing
494+
483495
-- | Does the compiler support the -reexported-modules "A as B" syntax
484496
reexportedAsSupported :: Compiler -> Bool
485497
reexportedAsSupported comp = case compilerFlavor comp of

cabal-install/cabal-install.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ library
270270
, regex-base >= 0.94.0.0 && <0.95
271271
, regex-posix >= 0.96.0.0 && <0.97
272272
, safe-exceptions >= 0.1.7.0 && < 0.2
273-
, semaphore-compat >= 1.0.0 && < 1.1
273+
, semaphore-compat >= 2.0.0 && < 2.1
274274

275275
if flag(native-dns)
276276
if os(windows)

cabal-install/src/Distribution/Client/JobControl.hs

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -183,26 +183,34 @@ newSemaphoreJobControl _ n
183183
| n < 1 || n > 1000 =
184184
error $ "newParallelJobControl: not a sensible number of jobs: " ++ show n
185185
newSemaphoreJobControl verbosity maxJobLimit = do
186-
sem <- freshSemaphore "cabal_semaphore" maxJobLimit
187-
info verbosity $
188-
"Created semaphore called "
189-
++ getSemaphoreName (semaphoreName sem)
190-
++ " with "
191-
++ show maxJobLimit
192-
++ " slots."
193-
outqVar <- newTChanIO
194-
inqVar <- newTChanIO
195-
countVar <- newTVarIO 0
196-
void (forkIO (worker sem inqVar outqVar))
197-
return
198-
JobControl
199-
{ spawnJob = spawn inqVar countVar
200-
, collectJob = collect outqVar countVar
201-
, remainingJobs = remaining countVar
202-
, cancelJobs = cancel inqVar countVar
203-
, cleanupJobControl = destroySemaphore sem
204-
, jobControlSemaphore = Just (semaphoreName sem)
205-
}
186+
mbServer <- freshSemaphore "cabal_semaphore" maxJobLimit
187+
case mbServer of
188+
Left err -> do
189+
warn verbosity $
190+
"Failed to create semaphore: " ++ show err
191+
++ "; falling back to normal parallelism control."
192+
newParallelJobControl maxJobLimit
193+
Right server -> do
194+
let sem = serverSemaphore server
195+
info verbosity $
196+
"Created semaphore called "
197+
++ getSemaphoreName (semaphoreName sem)
198+
++ " with "
199+
++ show maxJobLimit
200+
++ " slots."
201+
outqVar <- newTChanIO
202+
inqVar <- newTChanIO
203+
countVar <- newTVarIO 0
204+
void (forkIO (worker sem inqVar outqVar))
205+
return
206+
JobControl
207+
{ spawnJob = spawn inqVar countVar
208+
, collectJob = collect outqVar countVar
209+
, remainingJobs = remaining countVar
210+
, cancelJobs = cancel inqVar countVar
211+
, cleanupJobControl = destroySemaphoreServer server
212+
, jobControlSemaphore = Just (semaphoreName sem)
213+
}
206214
where
207215
worker :: Semaphore -> TChan (IO a) -> TChan (Either SomeException a) -> IO ()
208216
worker sem inqVar outqVar =
@@ -291,8 +299,18 @@ newJobControlFromParStrat verbosity mcompiler parStrat numJobsCap = case parStra
291299
UseSem n ->
292300
case mcompiler of
293301
Just compiler
294-
| jsemSupported compiler ->
302+
| jsemSupported compiler
303+
, isJsemCompatible compiler ->
295304
newSemaphoreJobControl verbosity (capJobs n)
305+
| jsemSupported compiler ->
306+
do
307+
warn verbosity $
308+
"Semaphore version mismatch (cabal-install uses v"
309+
++ show semaphoreVersion
310+
++ ", but the selected GHC reports "
311+
++ maybe "no version (assumed v1)" (\v -> "v" ++ show v) (jsemVersion compiler)
312+
++ "); not using -jsem, GHC will be invoked without semaphore-based parallelism."
313+
newParallelJobControl (capJobs n)
296314
| otherwise ->
297315
do
298316
warn verbosity "-jsem is not supported by the selected compiler, falling back to normal parallelism control."
@@ -303,6 +321,17 @@ newJobControlFromParStrat verbosity mcompiler parStrat numJobsCap = case parStra
303321
where
304322
capJobs n = min (fromMaybe maxBound numJobsCap) n
305323

324+
-- | Check if the compiler's semaphore version is compatible with ours.
325+
--
326+
-- If the compiler doesn't report a "Semaphore version" field (GHC 9.8–9.14),
327+
-- we assume v1. On POSIX, v1 and v2 are incompatible (different mechanisms).
328+
-- On Windows, all versions are compatible (same Win32 API).
329+
isJsemCompatible :: Compiler -> Bool
330+
isJsemCompatible compiler =
331+
case jsemVersion compiler of
332+
Just v -> versionsAreCompatible v semaphoreVersion
333+
Nothing -> versionsAreCompatible 1 semaphoreVersion
334+
306335
withJobControl :: IO (JobControl IO a) -> (JobControl IO a -> IO b) -> IO b
307336
withJobControl mkJC = bracket mkJC cleanupJobControl
308337

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
synopsis: Detect semaphore version mismatch between cabal-install and GHC
3+
packages: [Cabal, cabal-install]
4+
prs: 0000
5+
issues: 0000
6+
significance: significant
7+
---
8+
9+
When using `--semaphore`, cabal-install now checks whether the selected GHC's
10+
semaphore protocol version is compatible before passing `-jsem`. If the GHC
11+
reports no `Semaphore version` field (GHC 9.8–9.14, which use v1) and
12+
cabal-install uses v2, a warning is emitted and cabal-install falls back to
13+
normal parallelism control instead of passing an incompatible semaphore name.
14+
15+
On Windows, v1 and v2 are always compatible (same Win32 API), so semaphore
16+
coordination is preserved across all version combinations.
17+
18+
- `Cabal`: add `jsemVersion :: Compiler -> Maybe Int` to read the
19+
`Semaphore version` field from `ghc --info`.
20+
- `cabal-install`: add `isJsemCompatible` check in `newJobControlFromParStrat`;
21+
emit a warning and fall back to `-jN` when versions are incompatible.

0 commit comments

Comments
 (0)