packages feed

concurrent-output 1.7.9 → 1.10.21

raw patch · 13 files changed

Files

CHANGELOG view
@@ -1,3 +1,192 @@+concurrent-output (1.10.21) unstable; urgency=medium++  * Allow ansi-terminal-1.1++ -- Joey Hess <id@joeyh.name>  Mon, 29 Apr 2024 10:15:55 -0400++concurrent-output (1.10.20) unstable; urgency=medium++  * Allow text-2.1.++ -- Joey Hess <id@joeyh.name>  Thu, 12 Oct 2023 15:46:36 -0400++concurrent-output (1.10.19) unstable; urgency=medium++  * Support building for WASM.++ -- Joey Hess <id@joeyh.name>  Wed, 30 Aug 2023 16:03:03 -0400++concurrent-output (1.10.18) unstable; urgency=medium++  * Avoid some build warnings on Windows.+  * Allow ansi-terminal-1.0++ -- Joey Hess <id@joeyh.name>  Mon, 15 May 2023 16:32:50 -0400++concurrent-output (1.10.17) unstable; urgency=medium++  * Fix ansi-terminal dependency to only include >= 0.6.2,+    which is needed for hSupportsANSI.++ -- Joey Hess <id@joeyh.name>  Tue, 10 Jan 2023 15:40:11 -0400++concurrent-output (1.10.16) unstable; urgency=medium++  * Allow transformers-0.6+    (Thanks, Janus Troelsen)++ -- Joey Hess <id@joeyh.name>  Fri, 20 May 2022 19:31:56 -0400++concurrent-output (1.10.15) unstable; urgency=medium++  * Allow text-2.0++ -- Joey Hess <id@joeyh.name>  Fri, 04 Feb 2022 15:32:58 -0400++concurrent-output (1.10.14) unstable; urgency=medium++  * Allow ansi-terminal-0.11.1.++ -- Joey Hess <id@joeyh.name>  Wed, 24 Nov 2021 09:18:20 -0400++concurrent-output (1.10.13) unstable; urgency=medium++  * outputConcurrent and errorConcurrent are now safe to call from a thread+    that receives async exceptions.+  * Fix compilation with GHC head.+    (Thanks, Ellie Hermaszewska)++ -- Joey Hess <id@joeyh.name>  Mon, 22 Nov 2021 11:19:21 -0400++concurrent-output (1.10.12) unstable; urgency=medium++  * Bugfix: createProcessConcurrent would sometimes send the process's+    output to the console when it was supposed to be piped to a handle.+    (1.7.5 introduced this bug)+  * System.Console.Regions should be safe to use in a thread that+    receives async exceptions; displayConsoleRegions and withConsoleRegion+    have been made async exception safe.+    (However, System.Console.Concurrent is not async exception safe.)+  * Improve documentation about when withConcurrentOutput should be used.+  * Remove some unused code.+  * Allow building with older ansi-terminal versions, not only 0.9+.++ -- Joey Hess <id@joeyh.name>  Wed, 10 Jun 2020 17:22:39 -0400++concurrent-output (1.10.11) unstable; urgency=medium++  * Improve documentation of how to use ANSI color codes.+  * Allow ansi-terminal-0.10.++ -- Joey Hess <id@joeyh.name>  Sun, 06 Oct 2019 15:14:42 -0400++concurrent-output (1.10.10) unstable; urgency=medium++  * Allow ansi-terminal-0.9.+  * Fix a build warning.++ -- Joey Hess <id@joeyh.name>  Sun, 12 May 2019 16:29:18 -0400++concurrent-output (1.10.9) unstable; urgency=medium++  * waitDisplayChange would deadlock if the STM action passed to it did not+    cause any changes to the display. That is now safe to do.++ -- Joey Hess <id@joeyh.name>  Mon, 19 Nov 2018 18:40:55 -0400++concurrent-output (1.10.8) unstable; urgency=medium++  * Fix bug in waitDisplayChange that could result in an STM deadlock+    exception.++ -- Joey Hess <id@joeyh.name>  Mon, 19 Nov 2018 17:01:12 -0400++concurrent-output (1.10.7) unstable; urgency=medium++  * Allow stm-2.5.++ -- Joey Hess <id@joeyh.name>  Sat, 22 Sep 2018 17:14:59 -0400++concurrent-output (1.10.6) unstable; urgency=medium++  * Relax unix bound for ghc 8.6.++ -- Joey Hess <id@joeyh.name>  Mon, 02 Jul 2018 10:58:59 -0400++concurrent-output (1.10.5) unstable; urgency=medium++  * Allow exceptions-0.10.++ -- Joey Hess <id@joeyh.name>  Tue, 13 Mar 2018 11:50:39 -0400++concurrent-output (1.10.4) unstable; urgency=medium++  * Allow exceptions-0.9.++ -- Joey Hess <id@joeyh.name>  Mon, 26 Feb 2018 11:45:08 -0400++concurrent-output (1.10.3) unstable; urgency=medium++  * Allow async-2.2.++ -- Joey Hess <id@joeyh.name>  Mon, 05 Feb 2018 11:07:27 -0400++concurrent-output (1.10.2) unstable; urgency=medium++  * Allow ansi-terminal-0.8.++ -- Joey Hess <id@joeyh.name>  Thu, 25 Jan 2018 11:51:43 -0400++concurrent-output (1.10.1) unstable; urgency=medium++  * Allow ansi-terminal-0.7.++ -- Joey Hess <id@joeyh.name>  Sat, 25 Nov 2017 17:11:50 -0400++concurrent-output (1.10.0) unstable; urgency=medium++  * Simplified by removing workaround for waitForProcess race+    condition (https://github.com/haskell/process/issues/46).+  * Depends on process-1.6.0.0 which fixed that race.+  * ConcurrentProcessHandle is now a type alias for ProcessHandle,+    and waitForProcessConcurrent simply calls waitForProcess.+    These are now only provided to avoid breaking backwards+    compatability.++ -- Joey Hess <id@joeyh.name>  Fri, 19 May 2017 11:54:49 -0400++concurrent-output (1.9.2) unstable; urgency=medium++  * Allow process-1.6.0.0.++ -- Joey Hess <id@joeyh.name>  Fri, 19 May 2017 11:36:35 -0400++concurrent-output (1.9.1) unstable; urgency=medium++  * Documentation fix: createProcessConcurrent has been available on+    Windows since 1.7.5 but the docs said not.+  * When all regions are closed, flush stdout so that the display gets+    updated, which didn't happen before.++ -- Joey Hess <id@joeyh.name>  Tue, 16 May 2017 16:49:43 -0400++concurrent-output (1.9.0) unstable; urgency=medium++  * Replaced displayUpdateNotifier with a simpler and safer+    waitDisplayChange interface.++ -- Joey Hess <id@joeyh.name>  Fri, 12 May 2017 17:17:09 -0400++concurrent-output (1.8.0) unstable; urgency=medium++  * Added displayUpdateNotifier, which can be used to wait for+    changes to console regions to be displayed.+    (stmdemo has an example of using that to temporarily shut down the+    region based display to run a bash prompt, and restore the region+    display later.)++ -- Joey Hess <id@joeyh.name>  Fri, 12 May 2017 16:27:41 -0400+ concurrent-output (1.7.9) unstable; urgency=medium    * Allow lazy text to be used as an Outputable value, and as
LICENSE view
@@ -1,4 +1,4 @@-Copyright © 2015 Joey Hess <id@joeyh.name>+Copyright © 2015-2017 Joey Hess <id@joeyh.name> Copyright © 2009 Joachim Breitner  Redistribution and use in source and binary forms, with or without
System/Console/Concurrent.hs view
@@ -7,7 +7,7 @@ -- > import Control.Concurrent.Async -- > import System.Console.Concurrent -- >--- > main = withConcurrentOutput $+-- > main = withConcurrentOutput $ do -- > 	outputConcurrent "washed the car\n" -- > 		`concurrently` -- >	outputConcurrent "walked the dog\n"@@ -20,12 +20,12 @@ 	Outputable(..), 	outputConcurrent, 	errorConcurrent,-	ConcurrentProcessHandle, 	createProcessConcurrent,-	waitForProcessConcurrent, 	createProcessForeground, 	flushConcurrentOutput, 	lockOutput,+	ConcurrentProcessHandle,+	waitForProcessConcurrent, 	-- * Low level access to the output buffer 	OutputBuffer, 	StdHandle(..),
System/Console/Concurrent/Internal.hs view
@@ -40,8 +40,6 @@ 	, outputBuffer :: TMVar OutputBuffer 	, errorBuffer :: TMVar OutputBuffer 	, outputThreads :: TMVar Integer-	, processWaiters :: TMVar [Async ()]-	, waitForProcessLock :: TMVar () 	}  data Lock = Locked@@ -54,8 +52,6 @@ 	<*> newTMVarIO (OutputBuffer []) 	<*> newTMVarIO (OutputBuffer []) 	<*> newTMVarIO 0-	<*> newTMVarIO []-	<*> newEmptyTMVarIO  -- | Holds a lock while performing an action. This allows the action to -- perform its own output to the console, without using functions from this@@ -106,7 +102,8 @@ dropOutputLock = withLock $ void . takeTMVar  -- | Use this around any actions that use `outputConcurrent`--- or `createProcessConcurrent`+-- or `createProcessConcurrent`, unless +-- `System.Console.Regions.displayConsoleRegions` is being used. -- -- This is necessary to ensure that buffered concurrent output actually -- gets displayed before the program exits.@@ -147,15 +144,20 @@  -- | Displays a value to stdout. ----- No newline is appended to the value, so if you want a newline, be sure--- to include it yourself.--- -- Uses locking to ensure that the whole output occurs atomically -- even when other threads are concurrently generating output. --+-- No newline is appended to the value, so if you want a newline, be sure+-- to include it yourself.+-- -- When something else is writing to the console at the same time, this does -- not block. It buffers the value, so it will be displayed once the other -- writer is done.+--+-- When outputConcurrent is used within a call to+-- `System.Console.Regions.displayConsoleRegions`, the output is displayed+-- above the currently open console regions. Only lines ending in a newline+-- are displayed in this case (it uses `waitCompleteLines`). outputConcurrent :: Outputable v => v -> IO () outputConcurrent = outputConcurrent' StdOut @@ -166,7 +168,12 @@ errorConcurrent = outputConcurrent' StdErr  outputConcurrent' :: Outputable v => StdHandle -> v -> IO ()-outputConcurrent' stdh v = bracket setup cleanup go+outputConcurrent' stdh v = do+	-- Use a worker thread. This is so any async exception that+	-- is thrown to the current thread does not affect+	-- tryTakeOutputLock, which is not async exception safe.+	worker <- async $ bracket setup cleanup go+	wait worker   where 	setup = tryTakeOutputLock 	cleanup False = return ()@@ -181,69 +188,13 @@ 	h = toHandle stdh 	bv = bufferFor stdh -newtype ConcurrentProcessHandle = ConcurrentProcessHandle P.ProcessHandle--toConcurrentProcessHandle :: (Maybe Handle, Maybe Handle, Maybe Handle, P.ProcessHandle) -> (Maybe Handle, Maybe Handle, Maybe Handle, ConcurrentProcessHandle)-toConcurrentProcessHandle (i, o, e, h) = (i, o, e, ConcurrentProcessHandle h)+-- | This alias is provided to avoid breaking backwards compatibility.+type ConcurrentProcessHandle = P.ProcessHandle --- | Use this to wait for processes started with --- `createProcessConcurrent` and `createProcessForeground`, and get their--- exit status.------ Note that such processes are actually automatically waited for--- internally, so not calling this explicitly will not result--- in zombie processes. This behavior differs from `P.waitForProcess`+-- | Same as `P.waitForProcess`; provided to avoid breaking backwards+-- compatibility. waitForProcessConcurrent :: ConcurrentProcessHandle -> IO ExitCode-waitForProcessConcurrent (ConcurrentProcessHandle h) = -	bracket lock unlock checkexit-  where-	lck = waitForProcessLock globalOutputHandle-	lock = atomically $ tryPutTMVar lck ()-	unlock True = atomically $ takeTMVar lck-	unlock False = return ()-	checkexit locked = maybe (waitsome locked) return-		=<< P.getProcessExitCode h-	waitsome True = do-		let v = processWaiters globalOutputHandle-		l <- atomically $ readTMVar v-		if null l-			-- Avoid waitAny [] which blocks forever-			then P.waitForProcess h-			else do-				-- Wait for any of the running-				-- processes to exit. It may or may not-				-- be the one corresponding to the-				-- ProcessHandle. If it is,-				-- getProcessExitCode will succeed.-				void $ tryIO $ waitAny l-				checkexit True-	waitsome False = do-		-- Another thread took the lck first. Wait for that thread to-		-- wait for one of the running processes to exit.-		atomically $ do-			putTMVar lck ()-			takeTMVar lck-		checkexit False---- Registers an action that waits for a process to exit,--- adding it to the processWaiters list, and removing it once the action--- completes.-asyncProcessWaiter :: IO () -> IO ()-asyncProcessWaiter waitaction = do-	regdone <- newEmptyTMVarIO-	waiter <- async $ do-		self <- atomically (takeTMVar regdone)-		waitaction `finally` unregister self-	register waiter regdone-  where-	v = processWaiters globalOutputHandle-  	register waiter regdone = atomically $ do-		l <- takeTMVar v-		putTMVar v (waiter:l)-		putTMVar regdone waiter-	unregister waiter = atomically $ do-		l <- takeTMVar v-		putTMVar v (filter (/= waiter) l)+waitForProcessConcurrent = P.waitForProcess  -- | Wrapper around `System.Process.createProcess` that prevents  -- multiple processes that are running concurrently from writing@@ -262,8 +213,10 @@ -- redirected to a buffer. The buffered output will be displayed as soon -- as the output lock becomes free. ----- Currently only available on Unix systems, not Windows.-createProcessConcurrent :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ConcurrentProcessHandle) +-- Note that the the process is waited for by a background thread,+-- so unlike createProcess, neglecting to call waitForProcess will not+-- result in zombie processess.+createProcessConcurrent :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, P.ProcessHandle)  createProcessConcurrent p 	| willOutput (P.std_out p) || willOutput (P.std_err p) = 		ifM tryTakeOutputLock@@ -272,31 +225,34 @@ 			) 	| otherwise = do 		r@(_, _, _, h) <- P.createProcess p-		asyncProcessWaiter $-			void $ tryIO $ P.waitForProcess h-		return (toConcurrentProcessHandle r)+		_ <- async $ void $ tryIO $ P.waitForProcess h+		return r  -- | Wrapper around `System.Process.createProcess` that makes sure a process -- is run in the foreground, with direct access to stdout and stderr. -- Useful when eg, running an interactive process.-createProcessForeground :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ConcurrentProcessHandle)+--+-- Note that the the process is waited for by a background thread,+-- so unlike createProcess, neglecting to call waitForProcess will not+-- result in zombie processess.+createProcessForeground :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, P.ProcessHandle) createProcessForeground p = do 	takeOutputLock 	fgProcess p -fgProcess :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ConcurrentProcessHandle)+fgProcess :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, P.ProcessHandle) fgProcess p = do 	r@(_, _, _, h) <- P.createProcess p 		`onException` dropOutputLock 	registerOutputThread 	-- Wait for the process to exit and drop the lock.-	asyncProcessWaiter $ do+	_ <- async $ do 		void $ tryIO $ P.waitForProcess h 		unregisterOutputThread 		dropOutputLock-	return (toConcurrentProcessHandle r)+	return r -bgProcess :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ConcurrentProcessHandle)+bgProcess :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, P.ProcessHandle) bgProcess p = do 	let p' = p 		{ P.std_out = rediroutput (P.std_out p)@@ -311,15 +267,20 @@ 		, mungeret (P.std_err p) stderr_h 		, h 		)-	asyncProcessWaiter $ void $ tryIO $ P.waitForProcess h-	outbuf <- setupOutputBuffer StdOut stdout_h-	errbuf <- setupOutputBuffer StdErr stderr_h+	-- Wait for the process for symmetry with fgProcess,+	-- which does the same.+	_ <- async $ void $ tryIO $ P.waitForProcess h+	outbuf <- setupOutputBuffer StdOut (mungebuf (P.std_out p) stdout_h)+	errbuf <- setupOutputBuffer StdErr (mungebuf (P.std_err p) stderr_h) 	void $ async $ bufferWriter [outbuf, errbuf]-	return (toConcurrentProcessHandle r)+	return r   where 	rediroutput ss 		| willOutput ss = P.CreatePipe 		| otherwise = ss+	mungebuf ss mh+		| willOutput ss = mh+		| otherwise = Nothing 	mungeret ss mh 		| willOutput ss = Nothing 		| otherwise = mh
System/Console/Regions.hs view
@@ -9,7 +9,9 @@ -- License: BSD-2-clause --  -- Console regions are displayed near the bottom of the console, and can be--- updated concurrently by threads. Any other output displayed using+-- updated concurrently by threads. +--+-- Any other output lines displayed using -- `outputConcurrent` and `createProcessConcurrent` -- will scroll up above the open console regions. --@@ -108,6 +110,7 @@ 	consoleWidth, 	consoleHeight, 	regionList,+	waitDisplayChange, ) where  import Data.Monoid@@ -123,17 +126,19 @@ import Control.Concurrent.STM.TSem import Control.Concurrent.Async import System.Console.ANSI-import qualified System.Console.Terminal.Size as Console import System.IO import System.IO.Unsafe (unsafePerformIO) import Text.Read-import Data.List+import Data.List (intercalate, nubBy)+import Control.Applicative+import Prelude+#ifdef VERSION_terminal_size+import qualified System.Console.Terminal.Size as Console #ifndef mingw32_HOST_OS import System.Posix.Signals import System.Posix.Signals.Exts #endif-import Control.Applicative-import Prelude+#endif  import System.Console.Concurrent import Utility.Monad@@ -142,6 +147,7 @@ -- | Controls how a region is laid out in the console. -- -- Here's an annotated example of how the console layout works.+-- Each sequence of the same letter represents a distinct region. -- -- > scrolling...... -- > scrolling......@@ -178,22 +184,26 @@ regionList :: TMVar [ConsoleRegion] regionList = unsafePerformIO newEmptyTMVarIO --- | On Unix systems, this TVar is automatically updated when the--- terminal is resized. On Windows, it is only initialized on program start--- with the current terminal size.+data ConsoleSize = ConsoleSize+	{ _consoleHeight :: Int+	, _consoleWidth :: Int+	}+ {-# NOINLINE consoleSize #-}-consoleSize :: TVar (Console.Window Int)-consoleSize = unsafePerformIO $ newTVarIO $ -	Console.Window { Console.width = 80, Console.height = 25}+consoleSize :: TVar ConsoleSize+consoleSize = unsafePerformIO $ newTVarIO $+	ConsoleSize { _consoleWidth = 80, _consoleHeight = 25}  type Width = Int  -- | Gets the width of the console. -- -- On Unix, this is automatically updated when the terminal is resized.--- On Windows, it is only initialized on program start.+-- On Windows, it is determined at start. On WASM,+-- the console width is hard coded to 80 since WASI does not provide a way+-- to determine it. consoleWidth :: STM Int-consoleWidth = munge . Console.width <$> readTVar consoleSize+consoleWidth = munge . _consoleWidth <$> readTVar consoleSize   where #ifndef mingw32_HOST_OS 	munge = id@@ -204,11 +214,15 @@ #endif  -- | Get the height of the console.+--+-- On Unix, this is automatically updated when the terminal is resized.+-- On Windows, it is determined at start. On WASM,+-- the console heigth is hard coded to 25 since WASI does not provide a way+-- to determine it. consoleHeight :: STM Int-consoleHeight = Console.height <$> readTVar consoleSize+consoleHeight = _consoleHeight <$> readTVar consoleSize --- | The RegionList TMVar is left empty when `displayConsoleRegions`--- is not running.+-- | Check if `displayConsoleRegions` is running. regionDisplayEnabled :: IO Bool regionDisplayEnabled = atomically $ not <$> isEmptyTMVar regionList @@ -256,10 +270,26 @@ -- necessary. -- -- The value can include ANSI SGR escape sequences for changing--- the colors etc of all or part of a region.+-- the colors of all or part of a region. For this to display properly,+-- a reset escape sequence must be included to get the color back+-- to default. System.Console.ANSI makes it easy to construct such+-- values. For example:+--+-- > import System.Console.ANSI+-- > +-- > setConsoleRegion region +-- > 	( "hello "+-- > 	<> setSGRCode [SetColor Foreground Vivid Red] +-- >	<> "Mars" +-- >	<> setSGRCode [Reset]+-- > 	<> "!"+-- >	) --  -- Other ANSI escape sequences, especially those doing cursor -- movement, will mess up the layouts of regions. Caveat emptor.+--+-- ANSI SGR escape sequences that span multiple lines do not currently+-- display as you might hope. (Patches would be accepted.) setConsoleRegion :: (ToRegionContent v, LiftRegion m) => ConsoleRegion -> v -> m () setConsoleRegion r v = liftRegion $ 	modifyRegion r $ const $ pure $ toRegionContent v@@ -293,7 +323,9 @@ -- | Runs the action with a new console region, closing the region when -- the action finishes or on exception. withConsoleRegion :: (MonadIO m, MonadMask m) => RegionLayout -> (ConsoleRegion -> m a) -> m a-withConsoleRegion ly = bracketIO (openConsoleRegion ly) (closeConsoleRegion)+withConsoleRegion ly = bracketIO+	(openConsoleRegion ly)+	(uninterruptibleMask_ . closeConsoleRegion)  -- | Opens a new console region. openConsoleRegion :: LiftRegion m => RegionLayout -> m ConsoleRegion@@ -402,7 +434,9 @@ -- Note that this uses `lockOutput`, so it takes over all output to the -- console while the passed IO action is running. As well as displaying -- the console regions, this handles display of anything buffered by--- `outputConcurrent` and `createProcessConcurrent`.+-- `outputConcurrent` and `createProcessConcurrent`. So,+-- `withConcurrentOutput` and `flushConcurrentOutput` should not be run+-- while this is in use, and will block. -- -- When standard output is not an ANSI capable terminal, -- console regions are not displayed.@@ -412,17 +446,17 @@ 	, lockOutput $ bracket setup cleanup (const a) 	)   where-	setup = liftIO $ do+	setup = liftIO $ uninterruptibleMask $ \unmask -> do 		atomically $ putTMVar regionList [] 		endsignal <- atomically $ do 			s <- newTSem 1 			waitTSem s 			return s 		isterm <- liftIO $ hSupportsANSI stdout-		when isterm trackConsoleWidth-		da <- async $ displayThread isterm endsignal+		when isterm (unmask trackConsoleWidth)+		da <- async $ unmask $ displayThread isterm endsignal 		return (isterm, da, endsignal)-	cleanup (isterm, da, endsignal) = liftIO $ do+	cleanup (isterm, da, endsignal) = liftIO $ uninterruptibleMask_ $ do 		atomically $ signalTSem endsignal 		void $ wait da 		void $ atomically $ takeTMVar regionList@@ -430,48 +464,107 @@ 			installResizeHandler Nothing  trackConsoleWidth :: IO ()+#ifdef VERSION_terminal_size trackConsoleWidth = do-	let getwidth = maybe noop (atomically . writeTVar consoleSize)+	let getsz = maybe noop (atomically . writeTVar consoleSize . conv) 		=<< Console.size-	getwidth-	installResizeHandler (Just getwidth)+	getsz+	installResizeHandler (Just getsz)+  where+	conv wsz = ConsoleSize+		{ _consoleWidth = Console.width wsz+		, _consoleHeight = Console.height wsz+		}+#else+trackConsoleWidth = return ()+#endif  data DisplayChange-	= BufferChange (StdHandle, OutputBuffer)+	= BufferChange BufferSnapshot  	| RegionChange RegionSnapshot+	| RegionListChange RegionSnapshot 	| TerminalResize Width-	| EndSignal ()+	| Shutdown+	| DisplayChangeBarrier Barrier +type BufferSnapshot = (StdHandle, OutputBuffer) type RegionSnapshot = ([ConsoleRegion], [R], [[Text]])+type Barrier = Integer +-- | This is a broadcast TChan, which gets a DisplayChange written to it+-- after the display has been updated. It can be used to wait for something+-- to be displayed.+{-# NOINLINE displayUpdateNotifier #-}+displayUpdateNotifier :: TChan DisplayChange+displayUpdateNotifier = unsafePerformIO $ newBroadcastTChanIO++{-# NOINLINE displayChangeBarrier #-}+displayChangeBarrier :: TVar Barrier+displayChangeBarrier = unsafePerformIO $ newTVarIO 0++-- | Runs a STM action, and waits for the display to be fully updated+-- with any changes that action makes to the displayed regions.+waitDisplayChange :: STM a -> IO a+waitDisplayChange a = do+	c <- atomically $ dupTChan displayUpdateNotifier+	bv <- newEmptyTMVarIO+	_ <- setbarrier bv `concurrently` waitchange c bv+	snd <$> atomically (readTMVar bv)+  where+	setbarrier bv = atomically $ do+		!b <- succ <$> readTVar displayChangeBarrier+		r <- a+		writeTVar displayChangeBarrier b+		putTMVar bv (b, r)+	waitchange c bv = do+		change <- atomically $ readTChan c+		-- this blocks until the STM action has run, and the+		-- barrier is set.+		b <- fst <$> atomically (readTMVar bv)+		case change of+			DisplayChangeBarrier b' | b' >= b -> return ()+			_ -> waitchange c bv+ displayThread :: Bool -> TSem -> IO () displayThread isterm endsignal = do 	origwidth <- atomically consoleWidth-	go ([], [], []) origwidth+	origbarrier <- atomically (readTVar displayChangeBarrier)+	go ([], [], []) origwidth origbarrier   where-	go origsnapshot@(orighandles, origregions, origlines) origwidth = do+	go origsnapshot@(orighandles, origregions, origlines) origwidth origbarrier = do 		let waitwidthchange = do 			w <- consoleWidth 			if w == origwidth then retry else return w+		let waitbarrierchange = do+			b <- readTVar displayChangeBarrier+			if b /= origbarrier+				then return b+				else retry 		let waitanychange = 			(RegionChange <$> regionWaiter origsnapshot origwidth) 				`orElse`-			(RegionChange <$> regionListWaiter origsnapshot)+			(RegionListChange <$> regionListWaiter origsnapshot) 				`orElse` 			(BufferChange <$> outputBufferWaiterSTM waitCompleteLines) 				`orElse` 			(TerminalResize <$> waitwidthchange) 				`orElse`-			(EndSignal <$> waitTSem endsignal)+			(waitTSem endsignal >> pure Shutdown)+				`orElse`+			-- Must come last, so the changes above are+			-- processed before barriers.+			(DisplayChangeBarrier <$> waitbarrierchange) 		(change, height) <- atomically $ (,) 			<$> waitanychange 			<*> consoleHeight 		let onscreen = take (height - 1) . concat-		case change of-			RegionChange snapshot@(_, _, newlines) -> do-				when isterm $ do-					changedLines (onscreen origlines) (onscreen newlines)-				go snapshot origwidth+		let update snapshot@(_, _, newlines) = do+			when isterm $+				changedLines (onscreen origlines) (onscreen newlines)+			return $ go snapshot origwidth origbarrier+		next <- case change of+			RegionChange snapshot -> update snapshot+			RegionListChange snapshot -> update snapshot 			BufferChange (h, buf) -> do 				-- Note that even when every available line 				-- is dedicated to visible regions, the@@ -481,13 +574,19 @@ 				let origlines' = onscreen origlines 				inAreaAbove isterm (length origlines') origlines' $ 					emitOutputBuffer h buf-				go origsnapshot origwidth+				return $ go origsnapshot origwidth origbarrier 			TerminalResize newwidth -> do 				newlines <- atomically (mapM (resizeRegion newwidth) orighandles) 				when isterm $ do 					resizeRecovery (onscreen newlines)-				go (orighandles, origregions, newlines) newwidth-			EndSignal () -> return ()+				return $ go (orighandles, origregions, newlines) newwidth origbarrier+			Shutdown ->+				return $ return ()+			DisplayChangeBarrier b ->+				return $ go origsnapshot origwidth b+		hFlush stdout+		atomically $ writeTChan displayUpdateNotifier change+		next  readRegions :: [ConsoleRegion] -> STM [R] readRegions = mapM (\(ConsoleRegion h) -> readTVar h)@@ -576,7 +675,6 @@ #endif 		cursorDown (sum (map (snd . fst) l')) 		setCursorColumn 0-		hFlush stdout   where 	l' = changeOffsets l 1 [] @@ -608,7 +706,6 @@ 	when isterm $ do 		setCursorColumn 0 -- just in case the output lacked a newline 		displayLines (reverse ls)-	hFlush stdout  displayLines :: [Text] -> IO () displayLines = mapM_ $ \l -> do@@ -617,11 +714,15 @@  installResizeHandler :: Maybe (IO ()) -> IO () #ifndef mingw32_HOST_OS+#ifdef VERSION_terminal_size installResizeHandler h = void $ 	installHandler windowChange (maybe Default Catch h) Nothing #else installResizeHandler _ = return () #endif+#else+installResizeHandler _ = return ()+#endif  calcRegionLines :: R -> Width -> STM [Text] calcRegionLines r width = do@@ -694,6 +795,8 @@ endSGR :: Char endSGR = 'm' +#ifndef mingw32_HOST_OS+ -- | Finds the least expensive output to make a console that was displaying -- the old line display the new line. Cursor starts at far left. --@@ -829,3 +932,5 @@ 		| x >= 90 && x <= 97 = Just (Foreground, x) 		| x >= 100 && x <= 107 = Just (Background, x) 		| otherwise = Nothing++#endif
System/Process/Concurrent.hs view
@@ -9,26 +9,14 @@ module System.Process.Concurrent where  import System.Console.Concurrent-import System.Console.Concurrent.Internal (ConcurrentProcessHandle(..)) import System.Process hiding (createProcess, waitForProcess) import System.IO import System.Exit  -- | Calls `createProcessConcurrent`------ You should use the waitForProcess in this module on the resulting--- ProcessHandle. Using System.Process.waitForProcess instead can have--- mildly unexpected results. createProcess :: CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)-createProcess p = do-	(i, o, e, ConcurrentProcessHandle h) <- createProcessConcurrent p-	return (i, o, e, h)+createProcess = createProcessConcurrent  -- | Calls `waitForProcessConcurrent`------ You should only use this on a ProcessHandle obtained by calling--- createProcess from this module. Using this with a ProcessHandle--- obtained from System.Process.createProcess etc will have extremely--- unexpected results; it can wait a very long time before returning. waitForProcess :: ProcessHandle -> IO ExitCode-waitForProcess = waitForProcessConcurrent . ConcurrentProcessHandle+waitForProcess = waitForProcessConcurrent
TODO view
@@ -0,0 +1,53 @@+* System.Console.Regions handling of outputConcurrent and errorConcurrent+  when the output lacks a newline is surprising. The output may never be+  displayed. A better approach:++  When output arrives that lacks a newline, allocate a region for it+  and buffer it in that region, so it displays immediately and more+  output will go to the same region. When a newline arrives, close the+  region and scroll up the completed line the same way any completed region+  is displayed.++* Parts of System.Console.Concurrent may not be async exception safe.++  If a thread is running an action from that module and an async exception+  is sent to it, it may result in a deadlock or other problem.++  (System.Console.Regions has been made safe,+  also outputConcurrent and errorConcurrent.)++  Particularly problematic is takeOutputLock', which takes the lock+  and then outputs buffers to the console. If the emitOutputBuffer+  calls are interrupted by async exception, it will be left locked+  and the buffered output is also lost. But masking them is not good+  because emitOutputBuffer could run for a long time in some situations.++  fgProcess and bgProcess also do stuff with registerOutputThread/unregisterOutputThread+  that may not be async exception safe. ++  And createProcessForeground uses takeOutputLock but then calls fgProcess,+  which could be interrupted (during its call to registerOutputThread)+  before it starts the async thread that drops the lock.++  One approach to all this might be to fork off a worker thread,+  which will thus be immune to any async exception directed at the calling+  thread.++* Calling setConsoleRegion with something that throws an error+  will cause no further display updates to happen.+  +  The exception in the thunk will crash the displayThread,+  and that is not waited on until the action passed to displayConsoleRegions+  finishes, so the exception is deferred to that point. Of course if the+  action never finishes, that hides the exception.++  It does seem this should be improved, by catching the crashing+  displayThread and ideally propigating the exception immediately.+  But displayConsoleRegions would need to use a different class than+  the current MonadIO to be able to do that. Eg, MonadBaseControl+  so lifted-async can be used, or a class that lets the action be+  canceled.++  Or it could be dealt with by having setConsoleRegion force the thunk+  before passing it over to the displayThread. But the instance+  ToRegionContent (STM Text) seems to prevent doing that.
− Utility/Data.hs
@@ -1,19 +0,0 @@-{- utilities for simple data types- -- - Copyright 2013 Joey Hess <id@joeyh.name>- -- - License: BSD-2-clause- -}--{-# OPTIONS_GHC -fno-warn-tabs #-}--module Utility.Data where--{- First item in the list that is not Nothing. -}-firstJust :: Eq a => [Maybe a] -> Maybe a-firstJust ms = case dropWhile (== Nothing) ms of-	[] -> Nothing-	(md:_) -> md--eitherToMaybe :: Either a b -> Maybe b-eitherToMaybe = either (const Nothing) Just
Utility/Exception.hs view
@@ -31,8 +31,6 @@ import System.IO.Error (isDoesNotExistError, ioeGetErrorType) import GHC.IO.Exception (IOErrorType(..)) -import Utility.Data- {- Catches IO errors and returns a Bool -} catchBoolIO :: MonadCatch m => m Bool -> m Bool catchBoolIO = catchDefaultIO False@@ -86,7 +84,7 @@ tryWhenExists :: MonadCatch m => m a -> m (Maybe a) tryWhenExists a = do 	v <- tryJust (guard . isDoesNotExistError) a-	return (eitherToMaybe v)+	return (either (const Nothing) Just v)  {- Catches only exceptions caused by hardware faults.  - Ie, disk IO error. -}
Utility/Monad.hs view
@@ -9,62 +9,11 @@  module Utility.Monad where -import Data.Maybe-import Control.Monad--{- Return the first value from a list, if any, satisfying the given- - predicate -}-firstM :: Monad m => (a -> m Bool) -> [a] -> m (Maybe a)-firstM _ [] = return Nothing-firstM p (x:xs) = ifM (p x) (return $ Just x , firstM p xs)--{- Runs the action on values from the list until it succeeds, returning- - its result. -}-getM :: Monad m => (a -> m (Maybe b)) -> [a] -> m (Maybe b)-getM _ [] = return Nothing-getM p (x:xs) = maybe (getM p xs) (return . Just) =<< p x--{- Returns true if any value in the list satisfies the predicate,- - stopping once one is found. -}-anyM :: Monad m => (a -> m Bool) -> [a] -> m Bool-anyM p = liftM isJust . firstM p--allM :: Monad m => (a -> m Bool) -> [a] -> m Bool-allM _ [] = return True-allM p (x:xs) = p x <&&> allM p xs--{- Runs an action on values from a list until it succeeds. -}-untilTrue :: Monad m => [a] -> (a -> m Bool) -> m Bool-untilTrue = flip anyM- {- if with a monadic conditional. -} ifM :: Monad m => m Bool -> (m a, m a) -> m a ifM cond (thenclause, elseclause) = do 	c <- cond 	if c then thenclause else elseclause--{- short-circuiting monadic || -}-(<||>) :: Monad m => m Bool -> m Bool -> m Bool-ma <||> mb = ifM ma ( return True , mb )--{- short-circuiting monadic && -}-(<&&>) :: Monad m => m Bool -> m Bool -> m Bool-ma <&&> mb = ifM ma ( mb , return False )--{- Same fixity as && and || -}-infixr 3 <&&>-infixr 2 <||>--{- Runs an action, passing its value to an observer before returning it. -}-observe :: Monad m => (a -> m b) -> m a -> m a-observe observer a = do-	r <- a-	_ <- observer r-	return r--{- b `after` a runs first a, then b, and returns the value of a -}-after :: Monad m => m b -> m a -> m a-after = observe . const  {- do nothing -} noop :: Monad m => m ()
concurrent-output.cabal view
@@ -1,14 +1,14 @@ Name: concurrent-output-Version: 1.7.9-Cabal-Version: >= 1.8+Version: 1.10.21+Cabal-Version: >= 1.10 License: BSD2 Maintainer: Joey Hess <id@joeyh.name> Author: Joey Hess, Joachim Breitner Stability: Stable-Copyright: 2015 Joey Hess, 2009 Joachim Breitner+Copyright: 2015-2021 Joey Hess, 2009 Joachim Breitner License-File: LICENSE Build-Type: Simple-Category: User Interfaces+Category: User Interfaces, Concurrency Synopsis: Ungarble output from several threads or commands Description:  Lets multiple threads and external processes concurrently output to the@@ -29,17 +29,17 @@   stmdemo.hs  Library+  Default-Language: Haskell2010   GHC-Options: -Wall -fno-warn-tabs -O2   Build-Depends: base (>= 4.6), base < 5-    , text (>= 0.11.0 && < 1.3.0)-    , async (>= 2.0 && < 2.2)-    , stm (>= 2.0 && < 2.5)-    , process (>= 1.1.0 && < 1.5.0)+    , text (>= 0.11.0 && < 2.2)+    , async (>= 2.0 && < 2.3)+    , stm (>= 2.0 && < 2.6)+    , process (>= 1.6.0 && < 1.7.0)     , directory (>= 1.2.0 && < 1.4.0)-    , transformers (>= 0.3.0 && < 0.6.0)-    , exceptions (>= 0.6.0 && < 0.9.0)-    , ansi-terminal (>= 0.6.0 && < 0.7.0)-    , terminal-size (>= 0.3.0 && < 0.4.0)+    , transformers (>= 0.3.0 && < 0.7.0)+    , exceptions (>= 0.6.0 && < 0.11.0)+    , ansi-terminal (>= 0.6.2 && < 1.2.0)   Exposed-Modules:     System.Console.Concurrent     System.Console.Concurrent.Internal@@ -47,11 +47,12 @@     System.Process.Concurrent   Other-Modules:     Utility.Monad-    Utility.Data     Utility.Exception    if (! os(Windows))-    Build-Depends: unix (>= 2.7.0 && < 2.8.0)+    Build-Depends: unix (>= 2.7.0 && < 2.9.0)+  if (! arch(wasm32))+    Build-Depends: terminal-size (>= 0.3.0 && < 0.4.0)  source-repository head   type: git
demo2.hs view
@@ -1,21 +1,16 @@ import Control.Concurrent.Async import Control.Concurrent-import System.Console.Concurrent import System.Console.Regions-import System.Console.Terminal.Size-import qualified Data.Text as T-import Control.Concurrent.STM-import Control.Applicative-import Data.Time.Clock import Control.Monad -main = displayConsoleRegions $ mapConcurrently id+main :: IO ()+main = displayConsoleRegions $ void $ mapConcurrently id 	[ spinner 100 1 "Pinwheels!!" setConsoleRegion "/-\\|" (withtitle 1) 	, spinner 100 1 "Bubbles!!!!" setConsoleRegion ".oOo." (withtitle 1) 	, spinner 100 1 "Dots......!" appendConsoleRegion "."  (const (take 3)) 	, spinner  30 2 "KleisiFish?" setConsoleRegion "  <=<   <=<  " (withtitle 10)-	, spinner  10 9 "Countdowns!" setConsoleRegion-		(reverse [1..10])+	, spinner   9 9 "Countdowns!" setConsoleRegion+		(reverse ([1..10] :: [Int])) 		(\t n -> t ++ show (head n)) 	]   where
stmdemo.hs view
@@ -7,15 +7,23 @@ import Data.Time.Clock import Control.Monad import Data.Monoid+import System.Process  main :: IO () main = void $ displayConsoleRegions $ do+	void titleRegion 	ir <- infoRegion 	cr <- clockRegion 	rr <- rulerRegion-	growingDots+	growingDots `concurrently` runBash 	mapM_ closeConsoleRegion [ir, cr] +titleRegion :: IO ConsoleRegion+titleRegion = do+	r <- openConsoleRegion Linear+	setConsoleRegion r "STM demo!"+	return r+ infoRegion :: IO ConsoleRegion infoRegion = do 	r <- openConsoleRegion Linear@@ -48,24 +56,36 @@ 		rightAlign r 		return r +rulerRegion :: IO ConsoleRegion+rulerRegion = do+	r <- openConsoleRegion Linear+	setConsoleRegion r $ do+		width <- consoleWidth+		return $ T.pack $ take width nums+	return r+  where+	nums = cycle $ concatMap show [0..9]+ rightAlign :: ConsoleRegion -> STM () rightAlign r = tuneDisplay r $ \t -> do         w <- consoleWidth         return (T.replicate (w - T.length t) (T.singleton ' ') <> t) +growingDots :: IO () growingDots = withConsoleRegion Linear $ \r -> do 	atomically $ rightAlign r 	width <- atomically consoleWidth-	replicateM width $ do+	void $ replicateM width $ do 		appendConsoleRegion r "."  		threadDelay (100000) -rulerRegion :: IO ConsoleRegion-rulerRegion = do-	r <- openConsoleRegion Linear-	setConsoleRegion r $ do-		width <- consoleWidth-		return $ T.pack $ take width nums-	return r-  where-	nums = cycle $ concatMap show [0..9]+runBash :: IO ()+runBash = do+	-- Wait for growingDots to display some.+	threadDelay 1000000+	-- Temporarily clear whatever console regions are open.+	rs <- waitDisplayChange $ swapTMVar regionList []+	putStrLn "We interrupt this demo to run a shell prompt. exit to continue!"+	callCommand "bash"+	-- Restore the console regions.+	void $ atomically $ swapTMVar regionList rs