-jsem jobserver can leak tokens
`cleanupSem` snapshots `heldTokens` and releases them **before** killing the jobserver loop:
```haskell
cleanupSem = do
-- this is interruptible
cleanupJobserver sjs
killThread loop_tid
mb_ex <- takeMVar loop_finished_mvar
for_ mb_ex MC.throwM
cleanupJobserver :: Jobserver -> IO ()
cleanupJobserver (Jobserver { jobs = jobs_tvar }) = do
Jobs { heldTokens = toks } <- readTVarIO jobs_tvar
forM_ toks releaseSemaphoreToken
```
The jobserver loop is still running while we read and release the snapshot. Its `acquireThread`/`releaseThread` children can
commit a new `addToken` into `jobs_tvar` after the `readTVarIO` and before `killThread loop_tid`. Any token added in that window is in `heldTokens` but is never released by the cleanup.
issue