[erlang-questions] Unexpected try/catch behaviour

Richard O'Keefe ok@REDACTED
Wed Feb 24 01:25:33 CET 2010


On Feb 24, 2010, at 1:05 PM, Per Melin wrote:
> How about when you need to catch a specific event?
>
> try ets:slot(Table, SlotNumber) of
>    '$end_of_table' ->
>        ok;
>    Items ->
>        do_stuff(Items)
> catch
>    error:badarg ->
>        % We get badarg when the slot number was higher than the total
>        % number of slots. This can happen if someone deleted items  
> from
>        % the table while we were traversing it. No worries.
>        ok
> end.

That can be written just as well as

     case try   ets:slot(Table, Slot_Number)
	 catch error:badarg -> '$end_of_table'
	 end
       of '$end_of_table' -> ok
        ; Items           -> do_stuff(Items)
     end

in which not only will badargs inside do_stuff not be caught,
but the call to do_stuff is visibly *outside* the try, so it
is _obvious_ that they won't be caught.

The problem here is the definition of ets:slot/2:
	"Returns all objects in the I:th slot of the table Tab.
	 A table can be traversed by repeatedly calling the
	 function, starting with the first slot I = 0 and
	 ending when '$end_of_table' is returned.  The
	 function will fail with reason badarg if the I
	 argument is out of range."

A better definition would be to say that
	The function will fail with reason badarg if I is
	not a non-negative integer.  Sufficiently large
	values of I result in '$end_of_table' returns.
With that interface, the whole example would just be

	case ets:slot(Table, Slot_Number)
	  of '$end_of_table' -> ok
	   ; Items           -> do_stuff(Items)
	end

In erl_db_tree.c, for example, I _think_

     if (is_not_small(slot_term) ||
         ((slot = signed_val(slot_term)) < 0) ||
         (slot > tb->common.nitems))
         return DB_ERROR_BADPARAM;

     if (slot == tb->common.nitems) {
         *ret = am_EOT;
         return DB_ERROR_NONE;
     }

should change to

     if (is_not_small(slot_term) ||
         ((slot = signed_val(slot_term)) < 0))
	return DB_ERROR_BADPARAM;

     if (slot >= tb_common.nitems) {
	*ret = am_EOT;
	return DB_ERROR_NONE;
     }

In erl_db_hash.c, I _think_

     if (is_not_small(slot_term) ||
         ((slot = signed_val(slot_term)) < 0) ||
         (slot > tb->nactive))
         return DB_ERROR_BADPARAM;

     if (slot == tb->nactive) {
         *ret = am_EOT;
         return DB_ERROR_NONE;
     }

should change to

     if (is_not_small(slot_term) ||
	((slot = signed_val(slot_term)) < 0))
	return DB_ERROR_BADPARAM;

     if (slot >= tb->nactive) {
	*ret = am_EOT;
	return DB_ERROR_NONE;
     }

The reason for this change is the one you give, that someone
else might have been changing the table.  If that someone else
adds stuff to the table so that it grows, nothing bad happens,
but if they delete, and the tree or hash table shrinks, you
are really out of luck.



More information about the erlang-questions mailing list