|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [MirageOS-devel] Error handling in Mirage - request for comments!
On 2 February 2015 at 00:45, Christophe Troestler
<Christophe.Troestler@xxxxxxxxxxx> wrote:
> On Sat, 31 Jan 2015 21:07:22 +0000, Thomas Leonard wrote:
>>
>> [â] But the real problem here is that by making exceptions fatal we
>> turn every exception used in existing OCaml code into a security
>> vulnerability. [â]
>
> Since OCaml supports exceptions, I think there is no doubt that we
> must handle them effectively â not just wipe them under the carpet and
> let them abort the program/unikernel. Ideally Lwt should deal
> sensibly with exceptions (and the discussion seems to imply there are
> areas of improvement â but it is unclear to me how you can do that
> efficiently without using a try block in all binds).
Lwt already puts a try block in all (sleeping) binds, so this is
handled for you:
let bind t f =
let t = repr t in
match t.state with
| Return v ->
f v
| Fail _ as state ->
thread { state }
| Sleep sleeper ->
let res = temp t in
let data = !current_data in
add_immutable_waiter sleeper
(function
| Return v -> current_data := data; connect res (try f v
with exn -> fail exn)
| Fail _ as state -> fast_connect res state
| _ -> assert false);
res
> So it seems to
> me that there is little choice but that exceptions be turned into
> failed threads at the programmer's discretion. Maybe ârunâ should be
> renamed ârun_exnâ to indicate that exceptions pass through and the
> "safe" run should be
>
> run : (unit -> 'a t) -> [`Ok of 'a | `Exn of exn]
>
> As it was previously mentioned, I also think that using "error-aware
> return types" should be limited mostly to cases where the failure is
> not really an error and should be dealt with immediately by the
> caller. And I agree with Thomas when he says that overusing this
> scheme will result in errors being ignored instead of being dealt with
> properly.
>
> There are however errors that you want to mandate that they are dealt
> with but not necessarily at the calling point. A typical example to
> me is âreadâ because it is often much "higher" in the program that an
> appropriate response to a failing read can be made â this is important
> for libraries that will post-process âreadâs and will have no idea
> what to do if âreadâ fails.
Yes, this is a case where you might want an Or_error.t threaded
through everything. Or, it might be easier to wrap the whole thing so
that any error code returned by the read is thrown and then caught
again.
> To make the discussion a bit more concrete, here is a signature
> illustrating how these latter errors may be handled:
>
> type ('a, 'b) t
> val return : 'a -> ('a, 'b) t
> val ( >>= ) : ('a, 'b) t -> ('a -> ('c, 'b) t) -> ('c, 'b) t
> val fail : ([> ] as 'b) -> (_, 'b) t
> val fail_exn : exn -> ('a, 'b) t
> val catch : ('a, 'b) t -> ('b -> ('a, 'c) t) -> ('a, 'c) t
> val catch_exn : ?exn:(exn -> ('a, 'b) t) -> (unit -> ('a, 'b) t) -> ('a,
> 'b) t
> val run : (unit -> ('a, unit) t) -> [`Ok of 'a | `Exn of exn]
> val run_exn : ('a, unit) t -> 'a
>
> Some remarks. The âfailâ function imposes that the errors are labeled
> with polymorphic variants. Since these are not qualified by the
> module and to allow generic treatment, a good idiom would probably be
> to use something like â`X of X.errâ where â`Xâ is sufficiently
> explicit and âX.errâ enumerates the possible failures for the module
> âXâ. The function âcatch_exn fâ will catch exceptions raised by âfâ
> as well as failed-threads ones (by default, only turning all exceptions
> into failing thread ones). The use of âunitâ in â('a, unit) tâ ensures
> that the thread is of type â(_, 'b) tâ â i.e. that all errors have
> been dealt with â so it can be unified with a non-polymorphic variant
> type.
The problem we have in Mirage is that our types are abstract. For
example, if you have a functor
module FS (B : BLOCK) = ...
then the author of the FS functor doesn't know exactly what errors the
concrete B might throw. For example, if B is a remote disk then it
might throw network errors. So ensuring that callers enumerate all
errors isn't very helpful in this case.
You can give FS an error code of [`Block_error of B.error] and just
propagate it to the caller, which is what we do now. That seems quite
clever, but I'm struggling to imagine a case where it's actually
useful.
It's probably worth compiling a list of examples where Mirage code
currently matches on specific returned Error codes and takes specific
actions. My guess is there aren't enough to justify the complexity
though.
I tried grepping my source directory (containing git clones of various
Mirage components) with zsh:
for x in *(/); (echo '=========' $x; cd $x && git grep '| `Error (')
but in every one of these cases where we match on an `Error, we just
propagate it (i.e. the default behaviour of an exception would have
been correct).
Here are some examples:
| `Error (`Invalid_console x) -> fail (Failure (Printf.sprintf
"Invalid_console %s" x))
| `Error (`Block_device e) -> fail (Failure (Fs.string_of_block_error e))
| `Error (KV.Unknown_key key) -> fail (Invalid_argument key)
| `Error (`Unknown msg) -> Lwt.fail (Failure msg)
The only case where we do something interesting is `Error (Unknown_key
_), where we note that it was a read:
| `Error (TMPL.Unknown_key _) -> fail (Failure ("read " ^ name))
However, Unknown_key wouldn't be an exception under any proposed scheme anyway.
> This is similar to declaring
>
> type ('a, 'b) t = ('a, 'b) Result.t Lwt.t
>
> except that one cannot ditch the âResult.tâ (and one avoids 1
> indirection per bind).
>
> Unfortunately, it is some work to ensure that the compiler understands
> that all errors were dealt with. Consider
>
> let m1 = fail(`X 1) >>= fun x -> fail `Y
>
> Then
>
> let m2 = catch m1 (function `X x -> return x
> | e -> fail e)
>
> will not do, you have to write
>
> let m2 = catch m1 (function `X x -> return x
> | `Y as e -> fail e)
>
> to make sure âm2â shows that â`Xâ is no longer an issue. Thus
> defining good types to use â#typeâ is important.
>
> My 0.02â,
> C.
--
Dr Thomas Leonard http://0install.net/
GPG: 9242 9807 C985 3C07 44A6 8B9A AE07 8280 59A5 3CC1
GPG: DA98 25AE CAD0 8975 7CDA BD8E 0713 3F96 CA74 D8BA
_______________________________________________
MirageOS-devel mailing list
MirageOS-devel@xxxxxxxxxxxxxxxxxxxx
http://lists.xenproject.org/cgi-bin/mailman/listinfo/mirageos-devel
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |