5 Avr
2013
5 Avr
'13
16:29
Great news! :-D Thanks! 2013/4/4 Marc Feeley <feeley@iro.umontreal.ca> > I've added a port-specific exception handler that can be set with the new > "port-io-exception-handler-set!" procedure. I/O exceptions which are > detected by the I/O primitives are passed to this handler (if one is set) > by calling it in tail position with respect to the primitive. That way, > you can define the specific behavior you need in your application. In > particular, if the handler returns a value (instead of raising an > exception), then that value will be the result of the primitive. > > Here's an example: > > (let ((p (open-tcp-client "localhost:9999"))) > (port-io-exception-handler-set! > p > (lambda (e) (display-exception e (current-output-port)) #f)) > (pp (list 'return-value= (read-u8 p)))) > > that prints: > > Connection refused > (read-u8 '#<input-output-port #4 (tcp-client "localhost" 9999)>) > (return-value= #f) > > Note that timeouts are not considered to be io-exceptions. That shouldn't > be a problem because a timeout handler can be set explicitly for any port > in addition to the io-exception handler. > > Marc > > On 2013-03-24, at 2:12 PM, Mikael <mikael.rcv@gmail.com> wrote: > > > Hi Marc! > > > > Ah, one thing struck me: in some cases, IO primitives don't raise an > exception on error but instead return a special value, I think it's #!eof > always. > > > > This would not transform to returning #f by a with-exception-handler > wrapper, but it could with a port specific flag |io-error| or |eof-value| > or |treat-eof-as-error|. > > > > > > > > Do you have any thoughts on the previous email on with-exception-handler > and IO error semantics yet? > > > > Of course there's time though would be great to get this question about > how to reliably do general IO error handling settled. > > > > > > The two possible ways I have the impression that there are now are > > > > 1) By: > > * Add a |treat-eof-as-error| port flag > > * Add an optional third arg |first?| to |with-exception-catcher| and > -handler that makes the thunk be invoked first in case of exception. > > Internally there's two exception handler chains, the non-first that > works just like the one today - an exception handler added makes it be > invoked as first line in case of exception, and another chain that has > priority over the non-first chain, that's invoked before it and where new > handlers are added at the end and not at the beginning. (Perhaps internally > they can be implemented as one chain only.) > > * Make |with-exception-handler| exception handlers recursive (now they > make an infinite loop). > > > > or > > > > 2): (Less general solution, so probably not desirable) By: > > * Add a port-specific flag |io-error| to IO ports that's invoked in the > current place of both |raise| and eof. > > > > In either of these two, the IO primitives need to be checked so that the > IO error handling/|raise| calls are done in tail position / place that > produces the primitive's return value anyhow. > > > > Thanks and best regards, > > Mikael > > > > 2013/3/22 Mikael <mikael.rcv@gmail.com> > > Dear Marc, > > > > Spontaneously I think that the prospect of using with-exception-handler > as you propose sounds better than adding custom error behavior code to the > IO, as, the w.e.h. route would be more holistic in that it maintains the > error handling mechanism in Gambit one in total in number; also, > > > > It would work from a performance point of view as there is no overhead > per IO primitive call that's successful, which accounts for almost all of > them. The only potential issue with performance would be if the exception > handler part would take a lot of time, though I guess it can be generalized > that that is not an issue. > > > > Then I guess the last point would be the design aspect that if somehow > IO code would be run outside the current environment that the w.e.h. is > installed in, the custom error behavior would disappear; that would indeed > be a benefit with a port specific flag, that it's not subject to the same > limitation. Though, from the practical use I see today, that more or less > does not happen so it's fine. > > > > > > I didn't think deeper about the possibility of w.e.h. before as I found > myself without clarity on how the things I addressed in the previous email > could be solved, as they need to be solved for it to be a practically > viable solution. > > > > > > Looking forward a lot to hear your take on those two-three things and > hopefully get to a reliable io-error => #f soon =) > > > > Thanks, > > Mikael > > > > > > > > 2013/3/20 Mikael > > > > Hi Marc! > > > > > > 2013/3/18 Marc Feeley <feeley@iro.umontreal.ca> > > The problem I see with the approach you propose (whether it uses a > parameter or a port specific flag) is that the semantics of an "io-error" > is vague. What is an IO error? The definition is important because > exceptions that are IO errors are going to be processed using this new > mechanism, and non-IO exceptions will use a different mechanism (normal > exception handling). > > > > The approach I propose does not have this problem because the programmer > has complete control over the definition of an IO error. The exception > object can be inspected to see if it qualifies as an IO error and an > appropriate action can be taken. The definition of IO error can depend on > the type of port, the type of primitive which caused the exception > (read-u8, read-char, read), etc. > > > > Marc > > > > > > Aha. To really get this, there are two quite fundamental things about > the applicability of the with-exception-handler possibility that I maybe > don't get yet or at least would benefit of clarification, can we have a > look at it? > > > > Also last a question re semantics of io-error. > > > > > > > > So, for the with-exception-handler mechanism to work out, it needs to go > together with other use of exception handling use that's being done in the > same scope. > > > > In a setup where there's another exception handler *outside* the IO > with-exception-handler, there would be no issue as the IO w.e.h. 's handler > would be invoked first for any exception that occurs, and do its matching > and handling. > > > > In a setup where the other exception handler is made *inside* the IO > w.e.h. though, any IO exception that arises within that exception handler > will be picked up by it first, and for the IO w.e.h. thing to work out, > that handler needs to be able to pass on the exception to the parent > exception handler (which is the IO w.e.h.) completely in its original > shape, in such a way that if the IO w.e.h. handler returns a value, that > will be passed as return value to the original |raise| call. > > > > An example of this would be a web-based PI calculator that uses > exception handling locally in its PI calculate request thunk to pick up > invalid user input. > > > > So let's ask how this could be done. > > > > Let's say below that we have a procedure (install-io-w.e.h. port thunk) > that installs the IO w.e.h. that makes IO primitives return #f on > exception, so that thunk is invoked with that w.e.h. installed. > > > > Then, we have application logics (logics) that, aside from using IO, > internally uses exception handling. > > > > So a setup something like, > > > > (define (logics) > > (with-exception-catcher > > (lambda (e) "Logics failed due to invalid user input!") > > (lambda () > > (pp (read-u8 broken-port)) > > (/ 0 0) ; This is to simulate an exception due to invalid user > input. > > ))) > > (install-io-w.e.h. broken-port logics) > > > > The desired behavior here is to print #f to the console and for logics > to then return "Logics failed due to invalid user input!". > > > > The issue now becomes, how would this local exception handler need to be > implemented as for this to work out. > > > > > > The local exception handler needs to be specific about what kind of > error it looks for as to know which to handle locally and which to > re-raise. This might be a complete PITA in some situations as you're > looking for a catch-all behavior, as in the example above! > > > > Perhaps the order of exception handlers could be tweaked somehow, so > that the IO w.e.h. would get highest priority or something, though how > could that be made as a 'clean' abstraction? I mean, who's in a place to > claim at a general level that one exception is of higher prio than another? > - different classes could be introduced, like, "user exceptions" and "io > exceptions" or "system exception" (this would lead to an at least almost > functional equivalent of the port specific flag solution, just with a more > indirect code path!), or, the exception matching procedure could be > exported to a separate mechanism, and the exception handler claiming the > highest specificity in the matching would get to handle it e.g. > (with-exception-handler exception-match exception-handler thunk) where > exception-match takes an exception argument e and returns how well it > matches the exception, i duno as a boolean or 0-10 or a symbol. > > > > Or, a global parameter object |is-nonlocal-exception?| could be > introduced that any exception handler is free to invoke as to check if it > should re-raise the exception, and to overlap.. hmm, a more general > solution would be better. > > > > Do you see any general solution to this specificity problem? > > > > > > > > > > > > > > Now to get to the next thing, let's just presume there's a solution to > this and we represent it in this example as a (if (is-nonlocal-exception? > e) (raise e) condition in the local exception handler, again not because > this would necessarily represent a general solution but just to get on to > the next thing in the reasoning; so now we have > > > > (define (logics) > > (with-exception-catcher > > (lambda (e) (if (is-nonlocal-exception? e) (raise e) "Logics failed > due to invalid user input!")) > > (lambda () > > (pp (read-u8 broken-port)) > > (/ 0 0) ; This is to simulate an exception due to invalid user > input. > > ))) > > (install-io-w.e.h. broken-port logics) > > > > > > Now, how do you make this *parent* exception handler (the IO w.e.h.) > that got the exception passed to it, able to pass a return value to the > original |raise| call? > > > > This is required for the exception handling-based IO error handling > behavior we're looking for to work. > > > > The problem reduces to > > > > (define (logics) (raise "Please return 'properly-handled!")) > > > > (define (proxy-exception-handler/catcher2 thunk) (lambda () > (with-exception-catcher (lambda (e) (raise e)) thunk))) > > > > (define (proxy-exception-handler/catcher1 thunk) (lambda () > (with-exception-handler (lambda (e) (raise e)) thunk))) > > > > (define (parent-exception-handler thunk) (continuation-capture (lambda > (cont) (with-exception-handler (lambda (e) 'properly-handled!) thunk)))) > > > > where proxy-exception-handler/catcher 1 & 2 are to represent an > arbitrary chain of exception handlers that logics code may come up with. > The intended behavior is > > > > (parent-exception-handler (proxy-exception-handler/catcher1 > (proxy-exception-handler/catcher2 logics))) => 'properly-handled! > > > > (parent-exception-handler (proxy-exception-handler/catcher1 logics)) => > 'properly-handled! > > > > (parent-exception-handler (proxy-exception-handler/catcher2 logics)) => > 'properly-handled! > > > > > > Actually evaluating these three tests showed: > > > > (parent-exception-handler (proxy-exception-handler/catcher1 > (proxy-exception-handler/catcher2 logics))) => infinite loop (!) > > > > (parent-exception-handler (proxy-exception-handler/catcher1 logics)) => > infinite loop (!) > > > > (parent-exception-handler (proxy-exception-handler/catcher2 logics)) => > 'properly-handled! > > > > To start with, great that we see that with-exception-catcher delivers > out of the box for this usecase! > > > > Thus we have with-exception-handler left. I guess the best way would be > if with-exception-handler inherently somehow would deliver for this, so > that support for this kind of use would be transparent and not require > possible updates of user code (e.g. any typical user code such as a PI > calculator etc. could without needing code review just be run within a web > server that uses this special IO error handling). > > > > Is there any way to make with-exception-handler deliver for this usecase? > > > > > > > > > > > > > > Last, what about that for introducing a port specific flag there would > be the issue that the semantics of an IO error is vague - > > > > Maybe you see something here I didn't get. As IO error for a port would > count any error reported from the OS about the port, as well as timeouts > within Gambit's IO system. > > > > So this would correspond to any OS IO primitive invocation that gives an > error return value (other than one that asks for a reiteration of the > procedure, which some of them come with). > > > > Another way to relate to it would be that such a port specific flag > would serve to protect from requirement of programmer or admin intervention > for any IO error that could possibly come up regarding the addressed ports. > > > > What do you see here? > > > > > > > > Best regards, > > Mikael > > > > > > > > > >