Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Is there a standard way to handle errors in servers?

1 view
Skip to first unread message

Ramon F Herrera

unread,
Sep 19, 2009, 3:12:52 AM9/19/09
to

Newbie alert...

I am writing my first fancy server (in C++) and just realized that
there is an important difference in the way errors should be handled
in servers.

In all my previous (client) programs, when the flow got into an error,
I simply had statements like this:

fprintf(stderr, "File not found\n");
exit (7);

However, servers by definition are never ending, so we need more
sophisticated error handling.

My first naive reaction was to do a search and replace ("exit" -->
"return"), but silly me, that was kind of dumb.

I guess I will need to "raise" something, or "catch" whatever?

TIA,

-Ramon

Richard Heathfield

unread,
Sep 19, 2009, 3:33:20 AM9/19/09
to
In
<5d50429f-b25a-4037...@l35g2000vba.googlegroups.com>,
Ramon F Herrera wrote:

>
> Newbie alert...
>
> I am writing my first fancy server (in C++) and just realized that
> there is an important difference in the way errors should be handled
> in servers.

There is?

> In all my previous (client) programs, when the flow got into an
> error, I simply had statements like this:
>
> fprintf(stderr, "File not found\n");
> exit (7);

Your users must have *loved* you. Imagine if your word processor
behaved like that!

> However, servers by definition are never ending, so we need more
> sophisticated error handling.

Only the same kind you need in clients.

> My first naive reaction was to do a search and replace ("exit" -->
> "return"), but silly me, that was kind of dumb.

Mm-hmm.

> I guess I will need to "raise" something, or "catch" whatever?

You need to handle stuff properly. At every level, if your code can
perform the currently required task, it should do so. Having done so,
it should clean up after itself where appropriate, and then return
control to the caller. But if it can't perform the currently required
task, it should - as far as possible - ensure that its state (if any)
is properly cleaned up, and then tell the caller what went wrong. It
is then the caller's responsibility to deal with that. This may
involve informing the user, or trying an alternative approach, or
even terminating the program. But terminating the program is really a
last resort unless it's just a student toy or something quick like a
text filter.

It is a useful intellectual exercise to imagine how you would write
programs if you didn't have setjmp, longjump, raise, signal, exit,
unswitchy break, continue, goto, static or extern for objects, or
even multiple returns from a function. In fact, it's a useful
exercise to write programs under precisely those constraints. (I
actually write production code that way where possible - i.e. nearly
all the time - and I find it leads to considerably fewer bugs.)

--
Richard Heathfield <http://www.cpax.org.uk>
Email: -http://www. +rjh@
"Usenet is a strange place" - dmr 29 July 1999
Sig line vacant - apply within

Pascal J. Bourguignon

unread,
Sep 19, 2009, 6:26:09 AM9/19/09
to
Richard Heathfield <r...@see.sig.invalid> writes:

> In
> <5d50429f-b25a-4037...@l35g2000vba.googlegroups.com>,
> Ramon F Herrera wrote:
>
>>
>> Newbie alert...

> It is a useful intellectual exercise to imagine how you would write
> programs if you didn't have setjmp, longjump, raise, signal, exit,
> unswitchy break, continue, goto, static or extern for objects, or
> even multiple returns from a function. In fact, it's a useful
> exercise to write programs under precisely those constraints. (I
> actually write production code that way where possible - i.e. nearly
> all the time - and I find it leads to considerably fewer bugs.)

Notice that a simple solution to this problem used in functional
programming is to use a "BOTTOM" absorbing value. This is not
necessarily a sufficient thing to do in production code, but if you
don't use exceptions, this is a good modeling of what you have to do.

Every function accepts, in addition to the normal values for its
arguments, a special value called BOTTOM. If any of its argument is
BOTTOM, then it returns BOTTOM. Otherwise if it detects any error
condition it will return BOTTOM or else it will return its normal
value.

Then when you chain functions (this is the only thing you can do in
functional programming):

(f (g (h x y) (r (s z))))

The result will be BOTTOM if and only iff any of the input values (x,
y or z) is BOTTOM, or if there is an error in any of the functions f,
g, h or r.


In a low level language such as C, it will of course be harder to
implement such a scheme.


A first solution would be to wrap all the types:

------------------------------------------------------------------------

#include <stdio.h>
#include <iso646.h>
#include <limits.h>

typedef enum{false=0,true} bool;

#define TYPE(TIPE) wrapped_##TIPE
#define WRAPTYPE(TIPE) typedef struct { bool bottom; TIPE value; } TYPE(TIPE)
#define BOTTOM(TIPE) ((TYPE(TIPE)){true})
#define WRAP(TIPE,VALUE) ((TYPE(TIPE)){false,(VALUE)})
#define BOTTOMP(VALUE) ((VALUE).bottom)
#define VALUEOF(VALUE) ((VALUE).value)

typedef char* charp;
WRAPTYPE(charp);
WRAPTYPE(int);
typedef struct{ TYPE(int) x;
TYPE(int) y;
TYPE(charp) name; } named_point;
WRAPTYPE(named_point);


TYPE(int) add(TYPE(int) a,TYPE(int) b){
return((BOTTOMP(a) or BOTTOMP(b)
or ((VALUEOF(a)>=0) and (INT_MAX-VALUEOF(a)<=VALUEOF(b)))
or ((VALUEOF(a)<0) and (INT_MIN-VALUEOF(a)>=VALUEOF(b))))
? BOTTOM(int)
: WRAP(int,VALUEOF(a)+VALUEOF(b))); }

TYPE(int) divide(TYPE(int) num,TYPE(int) denum){
return((BOTTOMP(num) or BOTTOMP(denum) or (VALUEOF(denum)==0))
?BOTTOM(int)
:WRAP(int,VALUEOF(num)/VALUEOF(denum))); }

TYPE(named_point) point_new(TYPE(int) x,TYPE(int) y,TYPE(charp) name){
if(BOTTOMP(x) or BOTTOMP(y) or BOTTOMP(name)){
return(BOTTOM(named_point));
}else{
TYPE(named_point) result;
BOTTOMP(result)=false;
VALUEOF(result).x=x;
VALUEOF(result).y=y;
VALUEOF(result).name=name;
return(result); }}

TYPE(named_point) point_offset(TYPE(named_point) a,TYPE(int) x,TYPE(int) y,
TYPE(int) scale){
return(point_new(add(VALUEOF(a).x,divide(x,scale)),
add(VALUEOF(a).y,divide(y,scale)),
(VALUEOF(a).name))); }

TYPE(named_point) point_print(TYPE(named_point) p,TYPE(charp) varname){
if(BOTTOMP(varname)){
return(BOTTOM(named_point)); }
if(BOTTOMP(p)){
printf("There is an error while computing %s.\n",VALUEOF(varname));
return(BOTTOM(named_point));
}else{
printf("%s={x=%d,y=%d,name=\"%s\"}\n",
VALUEOF(varname),
VALUEOF(VALUEOF(p).x),
VALUEOF(VALUEOF(p).y),
VALUEOF(VALUEOF(p).name));
return(p); }}


int main(){
TYPE(named_point) p=point_new(WRAP(int,42),WRAP(int,2260199),WRAP(charp,"No Panic Point"));
point_print(p,WRAP(charp,"p"));
TYPE(named_point) q=point_offset(p,WRAP(int,-42),WRAP(int,-2260199),WRAP(int,1));
point_print(q,WRAP(charp,"q"));
TYPE(named_point) r=point_offset(p,WRAP(int,-42),WRAP(int,-2260199),WRAP(int,0));
point_print(r,WRAP(charp,"r"));
return((BOTTOMP(p) or BOTTOMP(q) or BOTTOMP(r)) ? 1 : 0);
}

/*
-*- mode: compilation; default-directory: "/tmp/" -*-
Compilation started at Sat Sep 19 11:22:13

gcc --version ; SRC="/tmp/p.c" ; EXE="p" ; gcc -I. -L. -g3 -ggdb3 -o ${EXE} ${SRC} && ./${EXE} && echo status = $?
i686-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5490)
Copyright (C) 2005 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

p={x=42,y=2260199,name="No Panic Point"}
q={x=0,y=0,name="No Panic Point"}
There is an error while computing r.

Compilation exited abnormally with code 1 at Sat Sep 19 11:22:13
*/

------------------------------------------------------------------------


Another solution would be to use an additionnal parameter for bottom:


------------------------------------------------------------------------
#include <stdio.h>
#include <iso646.h>
#include <limits.h>

typedef enum{false=0,true} bool;


int add(bool* bottom,int a,int b){
(*bottom)|=(((a>=0) and (INT_MAX-a<=b))
or ((a<0) and (INT_MIN-a>=b)));
if(not(*bottom)){
return(a+b);}}

int divide(bool* bottom,int num,int denum){
(*bottom)|=(denum==0);
if(not(*bottom)){
return(num/denum); }}

typedef char* charp;
typedef struct{ int x;
int y;
charp name; } named_point;

named_point point_new(bool* bottom,int x,int y,charp name){
if(not(*bottom)){
return((named_point){x,y,name});
}else{
return((named_point){0,0,"BOTTOM???"}); }}


named_point point_offset(bool* bottom,named_point a,int x,int y,int scale){
return(point_new(bottom,
add(bottom,a.x,divide(bottom,x,scale)),
add(bottom,a.y,divide(bottom,y,scale)),
a.name)); }

named_point point_print(bool* bottom,named_point p,charp varname){
/* We cannot check each value separately... */
if(*bottom){
printf("There is an error while computing %s.\n",varname);
return((named_point){0,0,"BOTTOM???"}); }
printf("%s={x=%d,y=%d,name=\"%s\"}\n",
varname,p.x,p.y,p.name);
return(p); }


int main(){
bool bottom=false;
named_point p=point_new(&bottom,42,2260199,"No Panic Point");
point_print(&bottom,p,"p");
named_point q=point_offset(&bottom,p,-42,-2260199,1);
point_print(&bottom,q,"q");
named_point r=point_offset(&bottom,p,-42,-2260199,0);
point_print(&bottom,r,"r");
return(bottom?1:0);
}

/*
-*- mode: compilation; default-directory: "/tmp/" -*-
Compilation started at Sat Sep 19 11:34:05

SRC="/tmp/q.c" ; EXE="q" ; gcc -I. -L. -g3 -ggdb3 -o ${EXE} ${SRC} && ./${EXE} && echo status = $?
p={x=42,y=2260199,name="No Panic Point"}
q={x=0,y=0,name="No Panic Point"}
There is an error while computing r.

Compilation exited abnormally with code 1 at Sat Sep 19 11:34:05

*/
------------------------------------------------------------------------

Notice that we still have to invent ad-hoc "bottom" values for
functions returning values... (One advantage of exceptions is that you
don't need to invent such values).

A variant would be to use a global variable bottom, to spare passing
it as parameter everywhere, but with the inconvenient that you cannot
pass different variables:

bool b1=false;
bool b2=false;
f(bottom,g(&b1),h(&b2));
if(b1){ error("in g"); }
if(b2){ error("in h"); }

would be impossible with a global bottom.

And finally, of course in practical programs, you can add attributes
to the BOTTOM object, such as an error code or message, the name of
the function and file, and the number of the line where the error was
detected, etc.


------------------------------------------------------------------------

#include <stdio.h>
#include <iso646.h>
#include <limits.h>

typedef enum{false=0,true} bool;

typedef struct {
const char* message;
const char* function;
const char* file;
int line;
} bottom_object;

#define WRAPTYPE(TIPE) \
typedef struct { \
bool bottom; \
union { \
bottom_object error; \
TIPE value; } \
object; \
} TYPE(TIPE)

#define BOTTOMP(VALUE) ((VALUE).bottom)
#define VALUEOF(VALUE) ((VALUE).object.value)
#define TYPE(TIPE) wrapped_##TIPE
#define WRAP(TIPE,VALUE) \
({ TYPE(TIPE) r; \
r.bottom=false; \
r.object.value=(VALUE); \
r;})
#define BOTTOM(TIPE) \
({ TYPE(TIPE) r; \
r.bottom=true; \
r.object.error.message="An error occured"; \
r.object.error.function=__FUNCTION__; \
r.object.error.file=__FILE__; \
r.object.error.line=__LINE__; \
r;})
#define XBOTTOM(TIPE,MESSAGE) \
({ TYPE(TIPE) r; \
r.bottom=true; \
r.object.error.message=(MESSAGE); \
r.object.error.function=__FUNCTION__; \
r.object.error.file=__FILE__; \
r.object.error.line=__LINE__; \
r;})
#define CBOTTOM(TIPE,WRAPPED) \
({ TYPE(TIPE) r; \
r.bottom=true; \
r.object.error=(WRAPPED).object.error; \
r;})

void bottom_print(bottom_object bottom){
printf("%s:%d: in %s, %s\n",
bottom.file,
bottom.line,
bottom.function,
bottom.message); }

WRAPTYPE(int);

typedef char* charp;
WRAPTYPE(charp);

typedef struct{ TYPE(int) x;
TYPE(int) y;
TYPE(charp) name; } named_point;
WRAPTYPE(named_point);


TYPE(int) add(TYPE(int) a,TYPE(int) b){
if(BOTTOMP(a)){
return(CBOTTOM(int,a));
}else if(BOTTOMP(b)){
return(CBOTTOM(int,b));
}else if((VALUEOF(a)>=0) and (INT_MAX-VALUEOF(a)<=VALUEOF(b))){
return(XBOTTOM(int,"Addition Overflow"));
}else if((VALUEOF(a)<0) and (INT_MIN-VALUEOF(a)>=VALUEOF(b))){
return(XBOTTOM(int,"Addition Underflow"));
}else{
return(WRAP(int,VALUEOF(a)+VALUEOF(b))); }}

TYPE(int) divide(TYPE(int) num,TYPE(int) denum){
if(BOTTOMP(num)){
return(CBOTTOM(int,num));
}else if(BOTTOMP(denum)){
return(CBOTTOM(int,denum));
}else if(VALUEOF(denum)==0){
return(XBOTTOM(int,"Divide by zero"));
}else{
return(WRAP(int,VALUEOF(num)/VALUEOF(denum))); }}


TYPE(named_point) point_new(TYPE(int) x,TYPE(int) y,TYPE(charp) name){
if(BOTTOMP(x)){
return(CBOTTOM(named_point,x));
}else if(BOTTOMP(y)){
return(CBOTTOM(named_point,y));
}else if(BOTTOMP(name)){
return(CBOTTOM(named_point,name));
}else{
TYPE(named_point) result;
BOTTOMP(result)=false;
VALUEOF(result).x=x;
VALUEOF(result).y=y;
VALUEOF(result).name=name;
return(result); }}

TYPE(named_point) point_offset(TYPE(named_point) a,TYPE(int) x,TYPE(int) y,
TYPE(int) scale){
return(point_new(add(VALUEOF(a).x,divide(x,scale)),
add(VALUEOF(a).y,divide(y,scale)),
(VALUEOF(a).name))); }

TYPE(named_point) point_print(TYPE(named_point) p,TYPE(charp) varname){
if(BOTTOMP(varname)){
bottom_print(varname.object.error);
return(BOTTOM(named_point)); }
if(BOTTOMP(p)){
printf("There is an error while computing %s.\n",VALUEOF(varname));
bottom_print(p.object.error);
return(BOTTOM(named_point));
}else{
printf("%s={x=%d,y=%d,name=\"%s\"}\n",
VALUEOF(varname),
VALUEOF(VALUEOF(p).x),
VALUEOF(VALUEOF(p).y),
VALUEOF(VALUEOF(p).name));
return(p); }}


int main(){
TYPE(named_point) p=point_new(WRAP(int,42),WRAP(int,2260199),WRAP(charp,"No Panic Point"));
point_print(p,WRAP(charp,"p"));
TYPE(named_point) q=point_offset(p,WRAP(int,-42),WRAP(int,-2260199),WRAP(int,1));
point_print(q,WRAP(charp,"q"));
TYPE(named_point) r=point_offset(p,WRAP(int,-42),WRAP(int,-2260199),WRAP(int,0));
point_print(r,WRAP(charp,"r"));
return((BOTTOMP(p) or BOTTOMP(q) or BOTTOMP(r)) ? 1 : 0);
}

/*
-*- mode: compilation; default-directory: "/tmp/" -*-
Compilation started at Sat Sep 19 12:24:07

SRC="/tmp/r.c" ; EXE="r" ; gcc -I. -L. -g3 -ggdb3 -o ${EXE} ${SRC} && ./${EXE} && echo status = $?
p={x=42,y=2260199,name="No Panic Point"}
q={x=0,y=0,name="No Panic Point"}
There is an error while computing r.
/tmp/r.c:103: in divide, Divide by zero

Compilation exited abnormally with code 1 at Sat Sep 19 12:24:07
*/

------------------------------------------------------------------------
--
__Pascal Bourguignon__

Ramon F Herrera

unread,
Sep 19, 2009, 7:39:11 AM9/19/09
to
On Sep 19, 3:33 am, Richard Heathfield <r...@see.sig.invalid> wrote:
> In
> <5d50429f-b25a-4037-8d3e-4ba893417...@l35g2000vba.googlegroups.com>,

>
> Ramon F Herrera wrote:
>
> > Newbie alert...
>
> > I am writing my first fancy server (in C++) and just realized that
> > there is an important difference in the way errors should be handled
> > in servers.
>
> There is?
>

Yes, there is. It is important to compare apples with apples.

My comparison is between client and server programs, both of which
have to be non-GUI (or else you are introducing oranges).

> Your users must have *loved* you.

As much as they love the programmers of every single Unix/Linux
command.

-Ramon

Richard Heathfield

unread,
Sep 19, 2009, 3:49:27 PM9/19/09
to
In
<b8ebe033-e8fb-4850...@e34g2000vbm.googlegroups.com>,
Ramon F Herrera wrote:

> On Sep 19, 3:33 am, Richard Heathfield <r...@see.sig.invalid> wrote:
>> In
>>
<5d50429f-b25a-4037-8d3e-4ba893417...@l35g2000vba.googlegroups.com>,
>>
>> Ramon F Herrera wrote:
>>
>> > Newbie alert...
>>
>> > I am writing my first fancy server (in C++) and just realized
>> > that there is an important difference in the way errors should be
>> > handled in servers.
>>
>> There is?
>>
>
> Yes, there is. It is important to compare apples with apples.

And it's important to compare programs with programs. It is perfectly
possible (and indeed normal) to handle server errors the same way
that you handle client errors, *provided* you handle client errors
properly.

<snip>

Donkey Hottie

unread,
Sep 19, 2009, 3:47:24 PM9/19/09
to
"Ramon F Herrera" <ra...@conexus.net> wrote in message
news:b8ebe033-e8fb-4850...@e34g2000vbm.googlegroups.com

The difference is that you may have to replace the fprintf(stderr, xx) with
some similar way to deliver the message to the user of the client, using a
feature in the protocol defined to be used in the communication between the
client and the server.

The client may the show the message to the user and die or do whatever it
pleases.

Your server should send the message to the client using the protocol, clean
up, and wait for next command from the client.

a try/catch is a good potential way to handle this in C++, yes. In any case,
the server must abort the process of serving the query if it can't make it
happen, and client up (rollback) anything it has done so far.

It works similarly as basic concept in any (command line or GUI) software
too. But simple command line programs may easily just die, and in many cases
that is ok. Sometimes it is not, it may have created files (temporary or
not), which now must be removed before returning or dying.

GUI analogy: A word processor shows the error message in a popup (this is a
protocol), and returns waiting for the next command from the client (the
user).


dj3v...@csclub.uwaterloo.ca.invalid

unread,
Sep 19, 2009, 3:43:20 PM9/19/09
to
In article <b8ebe033-e8fb-4850...@e34g2000vbm.googlegroups.com>,

Ramon F Herrera <ra...@conexus.net> wrote:
>On Sep 19, 3:33 am, Richard Heathfield <r...@see.sig.invalid> wrote:

[aborting on error]

> > Your users must have *loved* you.
>
>As much as they love the programmers of every single Unix/Linux
>command.

I use several (non-GUI) unix commands that do not abort-on-any-error,
and for which doing so would be incredibly poor design.

The relevant distinction is between programs that atomically perform
(or fail to perform) a single action, and programs that do not. If the
program's intended behavior is atomic, you might as well bail out as
soon as you know you can't do it; but in any other case, you need to
make sure that you don't lose data from earlier steps, and that you
don't arbitrarily prevent later steps from running (it's possible that
they can still usefully be done even if the current step fails).


dave

--
Dave Vandervies dj3vande at eskimo dot com
I remember when computers were frustrating because they *did* exactly
what you told them to. That actually seems sort of quaint now.
--J.D. Baldwin in the scary devil monastery

Donkey Hottie

unread,
Sep 19, 2009, 3:55:01 PM9/19/09
to
"Donkey Hottie" <don...@fred.pp.fi> wrote in message
news:e3pfo6-...@wellington.fredriksson.dy.fi
>
> a try/catch is a good potential way to handle this in
> C++, yes. In any case, the server must abort the process
> of serving the query if it can't make it happen, and
> client up (rollback) anything it has done so far.
>
^clean up (rollback) anything it has done so far.

Chris Friesen

unread,
Sep 20, 2009, 1:46:47 AM9/20/09
to
On 09/19/2009 01:49 PM, Richard Heathfield wrote:

> And it's important to compare programs with programs. It is perfectly
> possible (and indeed normal) to handle server errors the same way
> that you handle client errors, *provided* you handle client errors
> properly.

The definition of "proper" can vary depending on the requirements.

On a server you need to return the error to the client, but then return
back to the main loop rather than exiting the server itself.

You could handle all programs this way, but there are a class of
programs (some unix commandline apps for instance) where it would be
quite reasonable to print an error message and simply exit (possibly
from down within several levels of subroutines). If there are no
persistent resources that must be cleaned up (temp files, etc.) then
calling exit() can be the simplest way to terminate.

Chris

Richard Heathfield

unread,
Sep 20, 2009, 2:53:59 AM9/20/09
to
In <VoidnfnD5MyXXyjX...@posted.sasktel>, Chris Friesen
wrote:

> On 09/19/2009 01:49 PM, Richard Heathfield wrote:
>
>> And it's important to compare programs with programs. It is
>> perfectly possible (and indeed normal) to handle server errors the
>> same way that you handle client errors, *provided* you handle
>> client errors properly.
>
> The definition of "proper" can vary depending on the requirements.
>
> On a server you need to return the error to the client, but then
> return back to the main loop rather than exiting the server itself.

Yes. And on a client you need to return the error to the user, but
then return back to the main loop rather than exiting the client
itself.

> You could handle all programs this way, but there are a class of
> programs (some unix commandline apps for instance) where it would be
> quite reasonable to print an error message and simply exit (possibly
> from down within several levels of subroutines). If there are no
> persistent resources that must be cleaned up (temp files, etc.) then
> calling exit() can be the simplest way to terminate.

Oh, sure, I agree. But where state is involved, wilful abandonment is
IMHO not acceptable.

Barry Margolin

unread,
Sep 20, 2009, 1:02:33 PM9/20/09
to
In article
<5d50429f-b25a-4037...@l35g2000vba.googlegroups.com>,

Ramon F Herrera <ra...@conexus.net> wrote:

> Newbie alert...
>
> I am writing my first fancy server (in C++) and just realized that
> there is an important difference in the way errors should be handled
> in servers.
>
> In all my previous (client) programs, when the flow got into an error,
> I simply had statements like this:
>
> fprintf(stderr, "File not found\n");
> exit (7);
>
> However, servers by definition are never ending, so we need more
> sophisticated error handling.

Many servers are designed to fork a child process to handle each
connection. The original main process should be never-ending, but the
per-connection processes can exit.

You could also run your server via inetd, which handles this distinction
automatically for you.

--
Barry Margolin, bar...@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
*** PLEASE don't copy me on replies, I'll read them in the group ***

David Schwartz

unread,
Sep 21, 2009, 1:41:01 AM9/21/09
to
On Sep 19, 4:39 am, Ramon F Herrera <ra...@conexus.net> wrote:

> Yes, there is. It is important to compare apples with apples.

But you're not doing that. You're comparing trivial client application
to real-world server applications.

> My comparison is between client and server programs, both of which
> have to be non-GUI (or else you are introducing oranges).

It has nothing to do with GUI or non-GUI. Text-mode client programs
that terminate when they encounter an error are no fun either.

>  > Your users must have *loved* you.
>
> As much as they love the programmers of every single Unix/Linux
> command.

That's nonsense. Only the most trivial such commands simply terminate
when they encounter an error. Look at the source code to even commands
of moderate complexity (like 'df') and you will see tons of
intelligent error-handling code.

DS

Barry Margolin

unread,
Sep 21, 2009, 11:18:35 AM9/21/09
to
In article
<ac16f7c5-55fc-4a1d...@r24g2000prf.googlegroups.com>,
David Schwartz <dav...@webmaster.com> wrote:

Or even simple programs like ls and cat. If you give them multiple
filename arguments, they'll report "File not found" for the missing ones
and operate normally for the ones that are opened successfully.

Scott Lurndal

unread,
Sep 23, 2009, 5:44:45 PM9/23/09
to
Chris Friesen <cbf...@mail.usask.ca> writes:
>On 09/19/2009 01:49 PM, Richard Heathfield wrote:
>
>> And it's important to compare programs with programs. It is perfectly
>> possible (and indeed normal) to handle server errors the same way
>> that you handle client errors, *provided* you handle client errors
>> properly.
>
>The definition of "proper" can vary depending on the requirements.
>
>On a server you need to return the error to the client, but then return
>back to the main loop rather than exiting the server itself.

That too, depends. Consider a server that is fired up by inetd; the
server exiting after the request has been fulfilled (whether sucessfully
or with failure) is normal.

scott

David Schwartz

unread,
Sep 24, 2009, 4:41:58 AM9/24/09
to
On Sep 19, 12:43 pm, dj3va...@csclub.uwaterloo.ca.invalid wrote:

> The relevant distinction is between programs that atomically perform
> (or fail to perform) a single action, and programs that do not.  If the
> program's intended behavior is atomic, you might as well bail out as
> soon as you know you can't do it; but in any other case, you need to
> make sure that you don't lose data from earlier steps, and that you
> don't arbitrarily prevent later steps from running (it's possible that
> they can still usefully be done even if the current step fails).

That's definitely a big part of the distinction.

In a typical project, you break errors down into two types, expected
and unexpected.

For expected errors, you consider the impact of the error and decide
on a rational way to handle it. Obvious expected errors include
running out of memory, a configuration file not being present, a
permissions problem when trying to create a directory, out of disk
space for an FTP server, a server not responding, and so on. Each of
these should be considered and an appropriate action coded.

Unexpected errors are things that are so unlikely to happen that it's
not worth the effort of treating them as expected errors. An example
would include the 'socket' function returning 'EACCES'. Another would
be if you go to open a file you just closed and get 'EISDIR'. (It can
happen, maybe some other program removed the file and replaced it with
a directory.)

Some unexpected errors just don't make logical sense. If 'socket'
returns 'EISDIR', what happened? You have no idea.

The best way to handle unexpected errors is sometimes by the logical
scope of the function that returned the unexpected error. If the error
relates to a particular file, then abort whatever that file is
associated with. If it's associated with a particular client, abort
that client. If it's associated with a particular service for a server
that provides multiple services, stop providing that service.

Of course, a server that barfs with an unexpected error on every
client is not useful. In many cases, the best thing to do on an
unexpected error *is* to simply terminate. (Notifying or logging the
error, if possible.)

Unexpected errors should always be logged if the programmer does not
know what the error means!

DS

0 new messages