<div dir="ltr"><div dir="ltr">Andres,</div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Tue, Nov 5, 2019 at 1:15 AM Andres Valloud via Cuis-dev <<a href="mailto:cuis-dev@lists.cuis.st">cuis-dev@lists.cuis.st</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">I'm more familiar with how a message such as #receive would be <br>
implemented in some socket class, than how applications tend to write <br>
code that uses that functionality. So, suppose you had a method like this:<br>
<br>
doTheHttpThing<br>
<br>
| data |<br>
self prepareToDoTheHttpThing.<br>
data := [self socket receive] on: SocketError do: [:ex | ^nil].<br>
data preprocess filter blah blah.<br>
^data<br>
<br></blockquote><div><br></div><div>Many times, you're not dealing with a single http request, but a series of interrelated requests that represent a conversation. This is somewhat subjective in terms of how one answers the question 'when does it make sense to break this out into it's own method?' but I often end up with something like:</div><div><br></div><div>doTheHttpThing</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
So now the problem that the on:do: is dealing with is what happens when <br>
the socket disconnects from under the client code. Then #receive fails, <br>
there is no recourse, and the answer should be nil. One could do the <br>
wrap around like this:<br>
<br>
data := [self socket receive] on: SocketError do: [:ex | nil].<br>
data isNil ifTrue: [^nil].<br>
<br>
and I'd be tempted to write it that way now because who knows that ^nil <br>
does today. But suppose that no, that one would rather not have extra <br>
statements. Then one might want something like [:ex | ex methodReturn: <br>
nil] instead.<br>
<br>
In this case, why couldn't that be written like this?<br>
<br>
| data |<br>
^[<br>
self prepareToDoTheHttpThing.<br>
data := self socket receive.<br>
data preprocess filter blah blah.<br>
data<br>
] on: SocketError do: [:ex | nil]<br>
<br>
Now there's no non-local return as far as exceptions are concerned, and <br>
by the way the code is faster in all cases because non-local return is <br>
expensive.<br>
<br>
Once I wrote a multi-process web spider, and I remember writing the code <br>
so that the actual networking interaction happened in a very small place <br>
that could be controlled easily with patterns such as the above. Is the <br>
problem that application code doesn't always factor that nicely to allow <br>
that implementation strategy? I do not have enough of a sample to tell.<br>
<br>
> For example, I have a periodic task which involves ~10k http requests. <br>
> A couple hundred of those will fail each run for various reasons: <br>
> network errors, server down, server errors, page errors etc. There are <br>
> exception handlers at different levels of connection/request handling <br>
> that decide if the error is something that needs to be specifically <br>
> handled / retried or if terminating the request (or perhaps it was <br>
> terminated on us) and returning some specific value to the sender is the <br>
> appropriate result. These are almost always non-local returns since <br>
> we're failing right in the middle of processing a request which is no <br>
> longer valid.<br>
<br>
I suspect this is one of those cases where the code just doesn't factor <br>
nicely, right?<br>
<br>
> 3) FFI code and dealing with the 'outside world' more generally. <br>
> Similar situation as 2: something fails either in an expected place or <br>
> in an expected way. The current method can't continue but the sender <br>
> can. Examples: a non-critical log file or database can't be found or <br>
> has a problem... (maybe) log something to transcript and return from the <br>
> method in question. A backup file can't be copied to a secondary backup <br>
> location... we probably aren't connected to the network so ignore it and <br>
> try again the next time we get called. I have several of these: fatal <br>
> to the current method but the sender doesn't care.<br>
> <br>
> The thing all of these examples have in common is that they are all <br>
> essentially resumable, fatal exceptions... that need to resume somewhere <br>
> else.<br>
<br>
Maybe more like critical exceptions that are not fatal simply because of <br>
the context in which they occur.<br>
<br>
You know, it just occurred to me that having such exceptions implement a <br>
#defaultAction method isn't really useful, either. Suppose you could <br>
arrange the code so that the non-fatal yet critical exceptions were <br>
modeled by a particular class. Instead of a million exception handlers <br>
everywhere doing something like<br>
<br>
[...] on: NonFatalIOError do: [:ex | ex methodReturn: nil]<br>
<br>
one would like #defaultAction to do that. However, #defaultAction <br>
cannot do that now because a non-local return in the #defaultAction <br>
method of the exception does not do what is required.<br>
<br>
If exceptions implemented #methodReturn: instead, then it might be <br>
possible for #defaultAction to be written like this:<br>
<br>
NonFatalIOError>>defaultAction<br>
<br>
self methodReturn: nil<br>
<br>
This is not perfect yet, and one still needs to mark the return point <br>
with on:do:, but maybe there's a way to write even less code here.<br>
<br>
> Yeah, I can see this in general. I'd even phrase it as "from a best<br>
> practices standpoint, we recommend avoiding non-local returns in<br>
> exception handler blocks, but if you choose to do that then using the<br>
> #methodReturn: method provided for that effect helps the maintainers of<br>
> the exception framework keep things working as intended because said<br>
> method *defines* what it means to do a non-local return in this<br>
> context".<br>
> <br>
> I'm fine with that.<br>
<br>
Awesome, this is in the spirit of my initial comments above.<br>
<br>
> But let's wait until we get there, and especially after we get a feel<br>
> for the most difficult cleanup cases. There's no need to put that down<br>
> in stone right now.<br>
> <br>
> C'mon folks, the train is at the station and I'm on board. Let's get <br>
> moving... ;-)<br>
<br>
:)... let's just finish getting the evidence, then we can write the <br>
commentary in the right place with full confidence.<br>
<br>
> I've already started in my code as you've sold me on it being something <br>
> to avoid when not needed. I've been holding off going beyond that as I <br>
> don't think we've reached agreement as to the best course of action <br>
> yet.<br>
<br>
I'm not convinced that the system code is beyond cleaning up... I'll <br>
take a look and see what.<br>
<br>
Andres.<br>
-- <br>
Cuis-dev mailing list<br>
<a href="mailto:Cuis-dev@lists.cuis.st" target="_blank">Cuis-dev@lists.cuis.st</a><br>
<a href="https://lists.cuis.st/mailman/listinfo/cuis-dev" rel="noreferrer" target="_blank">https://lists.cuis.st/mailman/listinfo/cuis-dev</a><br>
</blockquote></div></div>