On 25/04/2013 7:18 AM, Gábor Lehel wrote:

I'm an outsider, so apologies if I'm off the right track. But this
sounds wrong to me. Isn't the point of condition handlers to /handle the
condition/? Meaning resolve the problem, so that processing can
continue? Here as far as I can tell, they're only being used to say
"fail differently", which then leads to failing again the next time you
try to do something. Opening a file was actually one of the examples
used to illustrate the condition system way back when[1].

Yeah. I read this design a few times and felt uneasy about the combination of files, options and conditions. I couldn't put my finger on exactly how to arrange it, but it felt off. I thought about it some more and I think I can now put my concern plainly.

When a condition is handled -- not-in-failure -- there are _many_ possible ways the user might want to handle it. All these are plausible:

  - This error is ok but subsequent errors on the same filehandle
    should cause retriggering of the condition (so we can count
    errors and fail after too many).

  - This error is ok and all subsequent errors on the same filehandle
    should be absorbed silently; turn it into /dev/null.

  - This error should cause a state-change to the existing file,
    such as closing it, truncating it, creating it, reopening it,
    or similar. If it's a socket, maybe we want to try reestablishing
    it. Maybe redirect it to an in-memory pseudo-file.

Because of this variety of possible solutions, the approach in the current design is inadequate: the Some(existing_file) and None strategies are only two of the possible strategies, and both are statically dispatched due to the implementation of file methods on Option<File>. The condition handler isn't really involved in picking the strategy, only choosing between a fixed menu. It should be able to _provide_ a strategy, which means providing an implementation of File (or more likely: ~Stream).

In other words (very much thinking-out-loud here) I suspect that any concrete stream type in our IO library that has serious error modes like this ought to be able to "replace itself" after a failed operation with a ~Stream that points to some other implementation. So it should be something like:

enum FileStream {
  FileDescriptor(libc::fd_t),
  FileSimulation(~Stream)
}

and so forth on other types. Then your file conditions would look like:

condition! {
   no_such_file : Path -> FileStream;
}

and

condition! {
   write_error : (libc::fd_t, libc::errno_t) -> FileStream;
}

and we'd assume that any handler for that condition will either provide you a "real" FileDescriptor or else a "fake" simulation of a file that directs to some other Stream type. And the 'write' operation would look like:

    fn write(&mut self, bytes: &[u8]) {
        match self {
            FileSimulation(s) => s.write(bytes),
            FileDescriptor(fd) => {
                let e = libc::write(bytes.to_ptr, bytes.len());
                if e != 0 {
                    *self = no_such_file.cond.raise(fd, e);
                }
            }
        }
    }

Apologies for the sketchines; I realize real code looks gnarlier than this, just trying to convey a possible strategy. I say all this, again, because the alternatives (option<File> or a single "bad" flag, as in many IO libraries) seems to provide very few recovery options to a user.

(Also aware that getting the abstraction layer right might be challenging; maybe FileStream is the wrong place to put in this redirection, and it should be one layer further up, or down..)

It's not clear to me if MemStream or other such things have quite as wide a repertoire of conditions. I think if there are no operations that raise conditions of this sort, then there'd be no reason to use such an enum.

-Graydon

_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev

Reply via email to