catching errors from linked processes: simplest way?

classic Classic list List threaded Threaded
11 messages Options
Reply | Threaded
Open this post in threaded view
|

catching errors from linked processes: simplest way?

Chris Pressey
OK, so, I'm actually trying to write 'aggressively simple' code now,
without open cases or inline error handling, and I've come across a case
that I can't see a simple way to handle.  (I can see a couple of
somewhat complicated ways to handle it, but wouldn't that defeat the
purpose :)

When I run the attached program I get the following output:

1> test3:start().
all OK
got "11"
ok
got "21"
got "31"
got "51"
got "61"
got "81"
got "91"
got "181"

Pretty much exactly what I expected.  So far, so good.

If I change line 7 to
  case catch client(Pid, [10, 20, 30, 50, 60, atom, 90, 180]) of
Then when I run it I get

1> test3:start().
not OK: {'EXIT',{badarith,[{test3,client,2},
                           {test3,start,0},
                           {erl_eval,expr,3},
                           {erl_eval,exprs,4},
                           {shell,eval_loop,2}]}}
got "11"
got "21"
ok
got "31"
got "51"
got "61"

Also what I expected.

But if I now change line 17 to
  Pid ! Head,
I get

1> test3:start().
all OK
got "10"
ok
got "20"
got "30"
got "50"
got "60"
2>
=ERROR REPORT==== 22-Apr-2003::00:25:16 ===
Error in process <0.30.0> with exit value:
{badarg,[{erlang,integer_to_list,[atom]},{test3,loop,0}]}
** exited: {badarg,[{erlang,integer_to_list,[atom]},{test3,loop,0}]} **

Not exactly what I'd prefer to happen!

I guess what I'm thinking is, wouldn't it be great if 'catch' could
catch errors from linked processes.  I guess I'm also thinking that
someone's probably thought of this before, so there's probably a simple
way to do it, of which I'm unaware.

The complicated ways to do it that I can think of are:
- make the client process trap exits and use a receive loop in the
client to get them and re-throw them in the client process;
- monitor the server from the client process and use a receive loop in
the client to throw errors in the client process when the server bombs;
- catch errors in the sever and explicitly pass an {error, Reason} value
back from the server (exactly what I'm now trying to avoid)

All of these would require me putting a 'receive' in the client, which
is less than elegantly simple - it goes against the grain of what I'm
now trying to do, i.e. eliminate explicit, inline error-handling code
from the main logic.  If I have to resort to it, I will, but if it turns
out I don't have to, that'd be great.

Any suggestions would be appreciated.

Thanks,
-Chris


-------------- next part --------------
An embedded and charset-unspecified text was scrubbed...
Name: test3.erl
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20030422/4f2f0791/attachment.ksh>

Reply | Threaded
Open this post in threaded view
|

catching errors from linked processes: simplest way?

Vladimir Sekissov
Good day,

cpressey> I guess what I'm thinking is, wouldn't it be great if 'catch' could
cpressey> catch errors from linked processes.  I guess I'm also thinking that
cpressey> someone's probably thought of this before, so there's probably a simple

You are catching exit message , it is in your mailbox:

start() ->
  Pid = spawn_link(?MODULE, server, []),
  case catch client(Pid, [10, 20, 30, 50, abba, 80, 90, 180]) of
    ok ->
      io:fwrite("all OK~n"),
      receive
        {'EXIT', Pid, Reason} ->
          io:fwrite("not OK: ~p~n", [Reason])
      after 0 ->
          ok
      end;
    Else ->
      io:fwrite("not OK: ~p~n", [Else])
  end.

3> test3:start().
not OK: {'EXIT',{badarith,[{test3,client,2},
                           {test3,start,0},
                           {erl_eval,expr,3},
                           {erl_eval,exprs,4},
                           {shell,eval_loop,2}]}}
got "11"
...

Best Regards,
Vladimir Sekissov


Reply | Threaded
Open this post in threaded view
|

catching errors from linked processes: simplest way?

Lennart Ohman
In reply to this post by Chris Pressey
Hi

Chris Pressey wrote:
> OK, so, I'm actually trying to write 'aggressively simple' code now,

Good! :-)

The "aggressive way" when having several processes is to have one (or
possibly several if it is a complicated system) process which supervises
the other(s). In this way you move all the error handling, if you need it
at all, away from the code implementing the logic.

You should then try to write the code in a way that the logic works fine
without the supervision (during normal input). The supervision is then
only an add-on to provide robustness.

/Lennart


-------------------------------------------------------------
Lennart Ohman                   phone   : +46-8-587 623 27
Sjoland & Thyselius Telecom AB  cellular: +46-70-552 6735
Sehlstedtsgatan 6               fax     : +46-8-667 8230
SE-115 28 STOCKHOLM, SWEDEN     email   : lennart.ohman



Reply | Threaded
Open this post in threaded view
|

catching errors from linked processes: simplest way?

Vladimir Sekissov
In reply to this post by Vladimir Sekissov
I'm awfully sorry.

Please ignore my last post.
Not yet fully awaken.

Best Regards,
Vladimir Sekissov


Reply | Threaded
Open this post in threaded view
|

catching errors from linked processes: simplest way?

Chris Pressey
In reply to this post by Lennart Ohman
On Tue, 22 Apr 2003 08:30:14 +0200
Lennart ?hman <lennart.ohman> wrote:

> Hi
>
> Chris Pressey wrote:
> > OK, so, I'm actually trying to write 'aggressively simple' code now,
>
> Good! :-)
>
> The "aggressive way" when having several processes is to have one (or
> possibly several if it is a complicated system) process which
> supervises the other(s). In this way you move all the error handling,
> if you need it at all, away from the code implementing the logic.
>
> You should then try to write the code in a way that the logic works
> fine without the supervision (during normal input). The supervision is
> then only an add-on to provide robustness.
>
> /Lennart

OK!  I wrote a supervisor process to catch errors from both the client
and the server, and it works fairly well.

However, I got to another sticking point.  I have several functions that
only make sense under a certain condition, let's call it A.  The
functions used to look like this:

  foo(A, B) ->
    case A of
      true ->
        bar(B);
      false ->
        {error, only_applies_under_condition_a}
    end.

Now, they look like this:

  foo(A, B) ->
    A = true, bar(B).

which is *much* easier to read.  :)

However, the error message has gone from a nice, informative one, to the
generic {badmatch, true}, which is not all that great (I'd like to be
able to give the (probably non-Erlang-literate) user a message as to
exactly why it crashed.)

I can think of two ways to handle this: either translate the error in
the client, or translate the error in the supervisor.

If I do the translation in the client, I would have:

  foo(A, B) ->
    case A of
      true ->
        bar(B);
      false ->
        throw(only_applies_under_condition_a)
    end.

which is hardly any improvement over the original.

If I do the translation in the supervisor, I would have something like:

  process_flag(trap_exits, true),
  receive
    ...
    {'EXIT', Client, {{badmatch, true}, [{module,foo,2} | Tail]}} ->
      io:fwrite("Client code violated condition A~n");
    ...
  end.

This is worrisome.

First, if I change the implementation of the client, say if I move what
foo does to another function, then I have to change the supervisor code
to reflect it.

Second, the supervisor is inferring (guessing!) that condition A was
violated because it knows that it's the only one in foo.  If there were
two or more invariants in foo, it wouldn't know which one was violated.

I guess the underlying 'problem' is that there's no way (that I'm aware
of) to pass along some state when an error occurs, without causing that
error yourself.  I think such a thing would be really very handy,
especially when I consider things like encountering an error when
parsing a text file - you would want the supervisor to know which line,
column, and token you choked on.

Or is this just 'too aggressive'?  It seems there is a time and place
for error return values instead of crashing, and this may be it.

-Chris


Reply | Threaded
Open this post in threaded view
|

catching errors from linked processes: simplest way?

Chris Pressey
On Tue, 22 Apr 2003 15:35:16 -0500
Chris Pressey <cpressey> wrote:

> If I do the translation in the client, I would have:
>
>   foo(A, B) ->
>     case A of
>       true ->
>         bar(B);
>       false ->
>         throw(only_applies_under_condition_a)
>     end.
>
> which is hardly any improvement over the original.

Ah!  I think I see a compromise: notify the supervisor of the nature of
any potential upcoming errors.  So the code becomes something like:

  foo(A, B) ->
    supervisor:potential_error(only_applies_under_condition_a, [A,B]),
    A = true, bar(B).

where supervisor:potential_error/2 sends a message to the supervisor,
which stores it in a dictionary using the pid the message came from as
the key, and which retrieves it when an error actually does occur.

The code is still fairly easy to read, and the supervisor doesn't need
to know about the structure of the code it's supervising, yet it can
know some details about what state the process was in when it crashed.

Sweet!

-Chris


Reply | Threaded
Open this post in threaded view
|

catching errors from linked processes: simplest way?

Vance Shipley-2

It seems like a workable solution however I don't find it elegant.

It seems to me that since the problem you are trying to solve is an
error handling one it should be solved in the error handler.  If the
error handler needs to have knowledge of the structure of the code
to do the error handling the way you want then so be it.

        -Vance

On Tue, Apr 22, 2003 at 04:44:32PM -0500, Chris Pressey wrote:
}  
}  Ah!  I think I see a compromise: notify the supervisor of the nature of
}  any potential upcoming errors.  So the code becomes something like:
}  
}    foo(A, B) ->
}      supervisor:potential_error(only_applies_under_condition_a, [A,B]),
}      A = true, bar(B).
}  
}  where supervisor:potential_error/2 sends a message to the supervisor,
}  which stores it in a dictionary using the pid the message came from as
}  the key, and which retrieves it when an error actually does occur.
}  
}  The code is still fairly easy to read, and the supervisor doesn't need
}  to know about the structure of the code it's supervising, yet it can
}  know some details about what state the process was in when it crashed.
}  
}  Sweet!
}  
}  -Chris


Reply | Threaded
Open this post in threaded view
|

catching errors from linked processes: simplest way?

Ulf Wiger-4
In reply to this post by Chris Pressey

How about writing a small library function:


assert(false, Msg) ->
   exit(Msg);
assert(true, _) ->
   true.


foo(A, B) ->
   assert(A, only_applies_under_condition_a),
   bar(B).


/Uffe

On Tue, 22 Apr 2003, Chris Pressey wrote:

>However, I got to another sticking point.  I have several functions that
>only make sense under a certain condition, let's call it A.  The
>functions used to look like this:
>
>  foo(A, B) ->
>    case A of
>      true ->
>        bar(B);
>      false ->
>        {error, only_applies_under_condition_a}
>    end.
>
>Now, they look like this:
>
>  foo(A, B) ->
>    A = true, bar(B).
>
>which is *much* easier to read.  :)
>
>However, the error message has gone from a nice,
>informative one, to the generic {badmatch, true}, which is
>not all that great (I'd like to be able to give the
>(probably non-Erlang-literate) user a message as to exactly
>why it crashed.)

--
Ulf Wiger, Senior Specialist,
   / / /   Architecture & Design of Carrier-Class Software
  / / /    Strategic Product & System Management
 / / /     Ericsson AB, Connectivity and Control Nodes



Reply | Threaded
Open this post in threaded view
|

catching errors from linked processes: simplest way?

Raimo Niskanen-7
In reply to this post by Chris Pressey
You could add some supervision to the client by using a toplevel catch.
Then you will get a stack dump. The following code also demonstrates a
line number macro trick with the process dictionary that is possible if
you have a toplevel catch in the client.

Runtime result:
21> test:top().
{'EXIT',{{badmatch,b},
          [{test,foo,0},
           {test,do,0},
           {test,top,0},
           {erl_eval,expr,3},
           {erl_eval,exprs,4},
           {shell,eval_loop,2}]}} at test:22
error

/ Raimo Niskanen, Erlang/OTP, Ericsson AB



-module(test).
-export([top/0]).

-define(line, put(location,{?MODULE,?LINE}),).

top() ->
     ?line
        case catch do() of
            {'EXIT',_} = Exit ->
                {Module,Line} = get(location),
                io:format("~p at ~w:~w~n", [Exit,Module,Line]),
                error;
            Result ->
                {ok,Result}
        end.

do() ->
     ?line foo(),
     ?line bar().

foo() ->
     ?line a=b.

bar() ->
     ok.



Chris Pressey wrote:

> On Tue, 22 Apr 2003 15:35:16 -0500
> Chris Pressey <cpressey> wrote:
>
>
>>If I do the translation in the client, I would have:
>>
>>  foo(A, B) ->
>>    case A of
>>      true ->
>>        bar(B);
>>      false ->
>>        throw(only_applies_under_condition_a)
>>    end.
>>
>>which is hardly any improvement over the original.
>
>
> Ah!  I think I see a compromise: notify the supervisor of the nature of
> any potential upcoming errors.  So the code becomes something like:
>
>   foo(A, B) ->
>     supervisor:potential_error(only_applies_under_condition_a, [A,B]),
>     A = true, bar(B).
>
> where supervisor:potential_error/2 sends a message to the supervisor,
> which stores it in a dictionary using the pid the message came from as
> the key, and which retrieves it when an error actually does occur.
>
> The code is still fairly easy to read, and the supervisor doesn't need
> to know about the structure of the code it's supervising, yet it can
> know some details about what state the process was in when it crashed.
>
> Sweet!
>
> -Chris



Reply | Threaded
Open this post in threaded view
|

catching errors from linked processes: simplest way?

Chris Pressey
In reply to this post by Vance Shipley-2
On Tue, 22 Apr 2003 18:23:39 -0400
Vance Shipley <vances> wrote:

> It seems to me that since the problem you are trying to solve is an
> error handling one it should be solved in the error handler.  If the
> error handler needs to have knowledge of the structure of the code
> to do the error handling the way you want then so be it.

I ended up using a combination of this (for generic errors, where no
real awareness of the structure of the code is needed) and Ulf's
suggestion for 'assert' (for specific errors.)  'assert' is simple and
clear, and it's not error handling so much as simply error-causing, so
it's quite OK to put in mainline code as far as I can see.

My program looks nice now, and behaves nicely too.

Thanks to everyone who helped me understand!

I'm still fuzzy on a few things.  Hard to put into words, my head is
still swimming:

When you start an application (or, actually, any function that hides
several processes,) aren't what you really doing, is starting that
application's supervisor?

That is, if any process in the application exits with an error, the
supervisor notices, shuts down all its processes, and returns the error
to you (the caller of the application)?  So that, from your perspective,
the application just looks like a regular function call, which you can
catch to handle any of the errors in any of the processes inside it?

(I'm thinking specifically of applications that have a finite time
domain and a measurable outcome here, not a long-lived application with
no definate end.)

I realize I might not be explaining it well, but it's something that
always confused me from trying to grasp the concept from the OTP docs.

Thanks again,
-Chris


Reply | Threaded
Open this post in threaded view
|

catching errors from linked processes: simplest way?

Lennart Ohman
Hi, glad you found a solution you are satisfies with.

Regarding starting an application, or as you put is, the
top-most supervisor of that supervision tree:

The idea in OTP is that we make a difference depending on
in which context a "member process" terminates.

During their start-phases (before all processes come past
their init-phases (leaving the function named init)):
Then you in the sence of making the function call to the
start-function will get an error-return value (not an exit
or similar). Such errors are referred to as start-errors and
reported as such if you run SASL. No process is allowed to
terminate during the init-phase.

After the successful start-up:
Then your start function-call has already returned {ok,Pid}.
If you used start-link or otherwised linked yourself to that
supervisor, you will get the exit-signal 'shutdown', indicating
that the supervisor terminated nicely (i.e cleaning up properly).

Processes may also terminate for "natural" reasons. OTP differs
between illegal and legal terminations. The permanence of a
process determines whether it is legal or illegal (a permanent
process may never terminate for instance).

Then you can of ocurse also have the scenario where you set
the restart intensity of your supervisor(s), to allow restarts
before it/they give up and terminate them self/ev.

/Lennart



Chris Pressey wrote:

> On Tue, 22 Apr 2003 18:23:39 -0400
> Vance Shipley <vances> wrote:
>
>
>>It seems to me that since the problem you are trying to solve is an
>>error handling one it should be solved in the error handler.  If the
>>error handler needs to have knowledge of the structure of the code
>>to do the error handling the way you want then so be it.
>
>
> I ended up using a combination of this (for generic errors, where no
> real awareness of the structure of the code is needed) and Ulf's
> suggestion for 'assert' (for specific errors.)  'assert' is simple and
> clear, and it's not error handling so much as simply error-causing, so
> it's quite OK to put in mainline code as far as I can see.
>
> My program looks nice now, and behaves nicely too.
>
> Thanks to everyone who helped me understand!
>
> I'm still fuzzy on a few things.  Hard to put into words, my head is
> still swimming:
>
> When you start an application (or, actually, any function that hides
> several processes,) aren't what you really doing, is starting that
> application's supervisor?
>
> That is, if any process in the application exits with an error, the
> supervisor notices, shuts down all its processes, and returns the error
> to you (the caller of the application)?  So that, from your perspective,
> the application just looks like a regular function call, which you can
> catch to handle any of the errors in any of the processes inside it?
>
> (I'm thinking specifically of applications that have a finite time
> domain and a measurable outcome here, not a long-lived application with
> no definate end.)
>
> I realize I might not be explaining it well, but it's something that
> always confused me from trying to grasp the concept from the OTP docs.
>
> Thanks again,
> -Chris


--
-------------------------------------------------------------
Lennart Ohman                   phone   : +46-8-587 623 27
Sjoland & Thyselius Telecom AB  cellular: +46-70-552 6735
Sehlstedtsgatan 6               fax     : +46-8-667 8230
SE-115 28 STOCKHOLM, SWEDEN     email   : lennart.ohman