Unexplained Sys_error "Bad file descriptor"


I’m receiving the above error from the below function, and I’ve no idea why (I expected channels to be safely handled via the with_file function).

The code uses the opam csv package and core.

    let read3 filename =
        let csv = In_channel.with_file
                ~f:( fun channel ->
        let headers = Csv.Rows.header csv in
        print_endline ( "read headers : " ^ ( String.concat ~sep:"," headers ) );
            ~f:(fun acc row ->
                print_endline ( "parsing row : " ^ ( String.concat ~sep:"," ( Csv.Row.to_list row ) ) );
                let newrecord =
                        ~f:(fun record header ->
                            let value = Csv.Row.find row header in
                            set_field record header value
                print_endline "parsing row completed";
                newrecord :: acc

This is the test csv file:


and this is the output generated:

read headers : heading1,heading2,heading3
parsing row : A,B,C
parsing row completed
parsing row : 1,2,3
parsing row completed
Uncaught exception:
  (Sys_error "Bad file descriptor")

Any insight greatly appreciated!


My guess is that Csv.of_channel doesn’t read the whole file in right away, but rather it will be streamed in while you fold over the CSV file.

Since with_file closes the file descriptor when its ~f argument returns, the file is closed before being completely read. What you probably want to do is to move the rest of read3 into the ~f argument.

Or you may want to just create a channel and use Csv.close_in to close the file.

Thank you both - makes sense. Moving the rest of the function into ~f provided a fix.

A lazy naming convention for ocaml would help (me) diagnose this type of issue.

@Chris00 - thanks for the library!

What do you mean? May you be more specific?

in this case, the lazy evaluation @bcc32 described wasn’t obvious from the name of_channel, hence it was innocently misused. A name like of_channel_lazy would make it clear.

I thought that the signature of of_channel is

val of_channel : ... -> Pervasives.in_channel -> in_channel

returning a channel made it clear that the channel must stay open for as long as the data is being read — just like standard channels. I added some explanation but the name is unlikely to change. What could be done however is to use Base & Core approach and define an In_channel module.