Skip to content

GitLab

  • Projects
  • Groups
  • Snippets
  • Help
    • Loading...
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
  • Sign in / Register
GHC
GHC
  • Project overview
    • Project overview
    • Details
    • Activity
    • Releases
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
    • Locked Files
  • Issues 4,319
    • Issues 4,319
    • List
    • Boards
    • Labels
    • Service Desk
    • Milestones
    • Iterations
  • Merge Requests 355
    • Merge Requests 355
  • Requirements
    • Requirements
    • List
  • CI / CD
    • CI / CD
    • Pipelines
    • Jobs
    • Schedules
  • Security & Compliance
    • Security & Compliance
    • Dependency List
    • License Compliance
  • Operations
    • Operations
    • Incidents
    • Environments
  • Analytics
    • Analytics
    • CI / CD
    • Code Review
    • Insights
    • Issue
    • Repository
    • Value Stream
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Members
    • Members
  • Collapse sidebar
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
  • Glasgow Haskell Compiler
  • GHCGHC
  • Issues
  • #18871

Closed
Open
Opened Oct 21, 2020 by Isaac Berger@zacii

Fix Alternative instance for IO

The Alternative instance for IO is currently defined in GHC.Base, using

(<|>) = mplusIO

Hunting further, mplusIO is defined in GHC.IO as

mplusIO :: IO a -> IO a -> IO a
mplusIO m n = m `catchException` \ (_ :: IOError) -> n

Why this is (possibly) wrong

Quoting documentation in Control.Exception

Applying mask to an exception handler

There's an implied mask around every exception handler in a call to one of the catch family of functions. This is because that is what you want most of the time - it eliminates a common race condition in starting an exception handler, because there may be no exception handler on the stack to handle another exception if one arrives immediately. If asynchronous exceptions are masked on entering the handler, though, we have time to install a new exception handler before being interrupted. If this weren't the default, one would have to write something like

 mask $ \restore ->
      catch (restore (...))
            (\e -> handler)

If you need to unmask asynchronous exceptions again in the exception handler, restore can be used there too.

Note that try and friends do not have a similar default, because there is no exception handler in this case. Don't use try for recovering from an asynchronous exception.

Being that mplusIO only catches exceptions of type IOError, it is not suited for cleanup work. It is more likely useful for running a fallback action should the first one fail. In this case, one would not want to prevent asynchronous exceptions from being able to interrupt the application in the alternative branch.

So I would suggest a better implementation for mplusIO should be based on try rather than catch

Something like:

mplusIO :: IO a -> IO a -> IO a
mplusIO m n = do
  res <- catchException (m >>= \ v -> return (Right v)) (\e -> return (Left e))
  case res of
    Right a -> return a
    Left (e :: IOError) -> n
Edited Oct 21, 2020 by Isaac Berger
Assignee
Assign to
None
Milestone
None
Assign milestone
Time tracking
None
Due date
None
Reference: ghc/ghc#18871