Skip to content

Efficient error handling in streams #585

@harendra-kumar

Description

@harendra-kumar

The onException combinator is essentially implemented as:

    step' gst st = do
        res <- step gst st `MC.onException` action
        case res of
            Yield x s -> return $ Yield x s
            Skip s    -> return $ Skip s
            Stop      -> return Stop

onException ultimately turns into a catch#, the step is wrapped into a catch# and this code cannot fuse and becomes very inefficient if we are doing exception handling on byte stream. Basically we have to check exceptions on each byte breaking fusion and allocating the constructor for each step. We can see this in the performance of exception combinators on a byte stream, these are the worst benchmarks in the FileSystem.Handle benchmark:

Benchmark                                                                                                            default(MiB)
-------------------------------------------------------------------------------------------------------------------- ------------
FileSystem.Handle/o-1-space/copy/read/group-ungroup/UA.unwords . UA.words (Array Char) (1/10)                             1576.53
FileSystem.Handle/o-1-space/copy/read/exceptions/S.finallyIO (1/10)                                                       1540.07
FileSystem.Handle/o-1-space/copy/read/exceptions/S.finally (1/10)                                                         1540.07
FileSystem.Handle/o-1-space/copy/read/exceptions/S.handle (1/10)                                                          1540.07
FileSystem.Handle/o-1-space/copy/read/exceptions/S.onException (1/10)                                                     1540.07
FileSystem.Handle/o-1-space/copy/fromToBytes/exceptions/S.bracketIO (1/10)                                                1379.72
FileSystem.Handle/o-1-space/copy/fromToBytes/exceptions/S.bracket (1/10)                                                  1379.72
FileSystem.Handle/o-1-space/copy/read/exceptions/UF.bracketIO (1/10)                                                      1299.66
FileSystem.Handle/o-1-space/copy/read/exceptions/UF.finallyIO (1/10)                                                      1299.66
FileSystem.Handle/o-1-space/copy/read/exceptions/UF.handle (1/10)                                                         1299.66
FileSystem.Handle/o-1-space/copy/read/exceptions/UF.bracket (1/10)                                                        1140.31
FileSystem.Handle/o-1-space/copy/read/exceptions/UF.finally (1/10)                                                        1140.31
FileSystem.Handle/o-1-space/copy/read/exceptions/UF.onException (1/10)                                                    1140.31

Alternatively, we can have an Error constructor to propagate errors in the stream more efficiently. The error constructor will fuse nicely. At the points where we need to interface with IO and can encounter IO errors we can use catch# and turn that into an Error to propagate through the stream.

Note that we use an Error constructor in parsers for the same purpose, we can have the same in streams as well.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions