[[!comment format=mdwn
 username="kyle"
 avatar="http://cdn.libravatar.org/avatar/7d6e85cde1422ad60607c87fa87c63f3"
 subject="comment 16"
 date="2020-08-10T21:34:43Z"
 content="""
I applied the change from c59a51a0651b and 4466c1001d88.  Now I don't
see \"git-annex: AsyncCancelled\", f0 is transferred, and the exit
status is 0, so it looks to be working as intended.  Thank you.

As described in comment 8, this is all in the Xenial VM on top of
1df9e72a7829, because the hang goes away with the next commit
(4c9ad1de46dd).  For posterity, here is a diff of the changes on top
1df9e72a7829, applied from aa492bc65904, c59a51a0651b, and
4466c1001d88.

<details>
<summary>diff</summary>

[[!format diff \"\"\"
diff --git a/Utility/Metered.hs b/Utility/Metered.hs
index 1c35a9a05..c4063c90b 100644
--- a/Utility/Metered.hs
+++ b/Utility/Metered.hs
@@ -1,6 +1,6 @@
 {- Metered IO and actions
  -
- - Copyright 2012-2018 Joey Hess <id@joeyh.name>
+ - Copyright 2012-2020 Joey Hess <id@joeyh.name>
  -
  - License: BSD-2-clause
  -}
@@ -46,6 +46,7 @@ import Common
 import Utility.Percentage
 import Utility.DataUnits
 import Utility.HumanTime
+import Utility.ThreadScheduler
 
 import qualified Data.ByteString.Lazy as L
 import qualified Data.ByteString as S
@@ -314,10 +315,26 @@ outputFilter cmd params environ outfilter errfilter =
 	catchMaybeIO $ withCreateProcess p go
   where
 	go _ (Just outh) (Just errh) pid = do
-		void $ concurrently 
-			(tryIO (outfilter outh) >> hClose outh)
-			(tryIO (errfilter errh) >> hClose errh)
-		waitForProcess pid
+		outt <- async $ tryIO (outfilter outh) >> hClose outh
+		errt <- async $ tryIO (errfilter errh) >> hClose errh
+		ret <- waitForProcess pid
+
+		-- Normally, now that the process has exited, the threads
+		-- will finish processing its output and terminate.
+		-- But, just in case the process did something evil like
+		-- forking to the background while inheriting stderr,
+		-- it's possible that the threads will not finish, which
+		-- would result in a deadlock. So, wait a few seconds
+		-- maximum for them to finish and then cancel them.
+		-- (One program that has behaved this way in the past is
+		-- openssh.)
+		void $ tryNonAsync $ race_
+			(wait outt >> wait errt)
+			(threadDelaySeconds (Seconds 2))
+		cancel outt
+		cancel errt
+
+		return ret
 	go _ _ _ _ = error \"internal\"
 	
 	p = (proc cmd (toCommand params))
\"\"\"]]

</details>



"""]]
