{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}-- | A futures implementation that integrates with shelly-- -- > jobs 5 (\job -> background job (sleep 2) >> background job (sleep 1))---- 'jobs' will wait for all concurrent jobs to finish.-- The argument to jobs is the maximum number of concurrent tasks.-- Generally shell scripts contain a lot of quick commands, but when you have the occasional command that is noticeably long and independent of other commands, you can easily run it concurrently.moduleShelly.Background(-- * Running external commands asynchronously.jobs,background,getBgResult,BgResult)whereimportShellyimportControl.ConcurrentimportControl.Exception(finally,catch,throwIO,SomeException)importPreludehiding(catch)importqualifiedControl.Concurrent.MSemasSem-- | Create a 'BgJobManager' that has a 'limit' on the max number of background tasks.-- an invocation of jobs is independent of any others, and not tied to the ShIO monad in any way.-- This blocks the execution of the program until all 'background' jobs are finished.jobs::Int->(BgJobManager->ShIOa)->ShIOajobslimitaction=dounless(limit>0)$terror"expected limit to be > 0"availableJobsSem<-liftIO$Sem.newlimitres<-action$BgJobManageravailableJobsSemliftIO$waitForJobsavailableJobsSemreturnreswherewaitForJobssem=doavail<-Sem.peekAvailsemifavail==limitthenreturn()elsewaitForJobssem-- | The manager tracks the number of jobs. Register your 'background' jobs with it.newtypeBgJobManager=BgJobManager(Sem.MSemInt)-- | Type returned by tasks run asynchronously in the background.newtypeBgResulta=BgResult(MVara)-- | Returns the promised result from a backgrounded task. Blocks until-- the task completes.getBgResult::BgResulta->ShIOagetBgResult(BgResultmvar)=liftIO$takeMVarmvar-- | Run the `ShIO` task asynchronously in the background, returns-- the `BgResult a`, a promise immediately. Run "getBgResult" to wait for the result.-- The background task will inherit the current ShIO context-- The 'BjJobManager' ensures the max jobs limit must be sufficient for the parent and all children.background::BgJobManager->ShIOa->ShIO(BgResulta)background(BgJobManagermanager)proc=dostate<-getliftIO$do-- take up a spot-- It is important to do this before forkIO:-- It ensures that that jobs will block and the program won't exit before our jobs are done-- On the other hand, a user might not expect 'jobs' to blockSem.waitmanagermvar<-newEmptyMVar-- future resultmainTid<-myThreadId_<-forkIO$doresult<-finally((shelly$(putstate>>proc))`catch`(\(e::SomeException)->throwTomainTide>>throwIOe))(Sem.signalmanager>>return())-- open a spot back upputMVarmvarresultreturn$BgResultmvar