This adds an interface to list the
ThreadIds of existing threads and fetch their labels:
GHC.Conc.Sync.listThreads :: IO [ThreadId] GHC.Conc.Sync.threadLabel :: ThreadId -> IO (Maybe String)
This also fits nicely with the existing
threadStatus :: ThreadId -> IO ThreadStatus data ThreadStatus = ThreadRunning | ThreadFinished | ThreadBlocked BlockReason | ThreadDied
The implementation of
threadLabel required a rather considerable rework of how thread labels are managed. In particular, previously they were tracked as C strings separately from the TSO via a hashtable. However, this complicated reasoning about the lifetime of the string, which could now be exposed to the mutator via
threadLabel. Consequently, I moved the label into
StgTSO and represented it as a standard UTF-8 encoded
ByteArray#. This greatly simplifies reasoning and makes the interface more consistent. However, this then prompted the need for a simple UTF-8 codec over
base. For this I moved the implementation currently living in
base (see !8658 (closed), on which this MR depends).
This is based upon !8658 (closed).
I have opened #21877 to track this idea.
In principle this could be further generalized by exposing the precise object on which the thread is blocked via
BlockReason. This could in principle make it easier to inspect "soft" deadlocks in concurrent programs which are currently extremely painful to debug. However, this would only be truly useful if we had a means of identifying (e.g. perhaps via textual label)
MVars and thunks in non-profiled code. Cost centers are one way to achieve this in profiled code. In non-profiled code, IPE information helps for thunks but primitives like
MVars are problematic.
Sadly, there is no great way of identifying primitives in pure Haskell with the tools that we have today. However, this could be addressed by introducing a new type of primitive object: a finite map keyed on (weak) object references:
-- | A map keyed on object references. Entries keyed on GC'd objects will -- be automatically dropped. data WeakMap v insertWeakMap :: forall (lev :: Levity) (k :: TYPE (BoxedRep lev)). k -> v -> WeakMap v -> WeakMap v lookupWeakMap :: forall (lev :: Levity) (k :: TYPE (BoxedRep lev)). k -> WeakMap v -> Maybe v labelledObjects :: IORef (WeakMap String) labelObject :: a -> String -> IO () labelObject x lbl = do atomicModifyIORef' labelledObjects (insertWeakMap w lbl) getObjectLabel :: a -> Maybe String getObjectLabel x = lookupWeakMap x labelledObjects
In principle one might be able to do implement something like this (with some parts written in Cmm) as an
Array# (Weak# v), although efficiency would likely be marginal.
Perhaps even better than
String names would be:
data ObjectName where ObjectName :: (Typeable a, Show a) => a -> ObjectName