[DGD]atomic functions

Felix A. Croes felix at dworkin.nl
Sun Sep 17 15:11:07 CEST 2000


Stephen Schmidt <schmidsj at union.edu> wrote:

> For the sake of the undereducated among us out here on the
> mailing list, can someone explain what an "atomic function"
> might be? Much appreciated...

Atomic functions will undo all changes made within them
if an error occurs.  They either complete entirely or not
at all.

Suppose that you have some code similar to the following:

    void move(object destination)
    {
	environment = destination;
	destination->enter_inventory(this_object());
    }

Naturally, you'd like the environment of one object and the inventory
of another to be always consistent.  Therefore an error that prevents
the second from happening would be an annoyance; in the worst case,
the existing inconsistency could in itself cause errors and further
inconsistencies in other objects, and your mud could be in serious
trouble.

For normal LPC code, such an error could occur in two ways, even in
sound code.  The thread could run out of ticks after the first
statement of the function, but before the second.  Or the second
statement, which is a function call, could cause a stack overflow
error to occur.

There are several ways to prevent this.  For example, you could
check the available number of ticks at the start of the function,
and abort if there are not enough available to complete the job.
Furthermore, you can change the order of the statements so the
function call comes first; that way, if there is a stack overflow,
it always happens at the beginning, and not halfway through a move.

My first attempt to deal with this problem in a generalized way
was to introduce the rlimits language construct. The following
does always succeed:

    void move(object destination)
    {
	rlimits (-1; -1) {
	    environment = destination;
	    destination->enter_inventory(this_object());
	}
    }

That is, when you set the available stack space and available ticks
to infinite before you make the move, you are certain not to run
out of either.

Rlimits has two problems.  First, it is dangerous.  Infinite
recursion without a maximum call depth will cause your mud to
crash; an infinite loop without a ticks limit will cause your
mud to hang.

Second, the effect is too inclusive.  If enter_inventory() in the
destination object attempts to notify each object already in its
inventory about the new entree, all those notifications will run
with infinite ticks and stack as well, which means that all the
notification functions have to be just as careful about looping and
recursion.


The new way is to use atomic functions:

    atomic void move(object destination)
    {
	environment = destination;
	destination->enter_inventory(this_object());
    }

Should an error occur halfway through this function, all of the
changes made within the function are undone; "environment" will
be set to its previous value.  This also applies to functions
called from the atomic function; if the destination uses
notification functions and one of these errors out, then the
entire move, including all notifications, will be undone.

The atomic effect only applies to errors that would abort execution
of the atomic function; an error that is caught within the atomic
function does not do so, and therefore causes no rollback.

Atomic functions can safely be nested.  You can make the entire
thread atomic, if you like.  Sub-portions of an atomic thread can be
atomic in their own right.

The use of atomic functions is unrestricted, unlike rlimits.  They
are safe even for guest programmers.  The worst thing that can happen
is nothing. :-)

Of course, atomic functions have their own cost.  You cannot do any
file write operations during atomic execution.  Available ticks are
depleted twice as fast during execution of an atomic function.  I
will probably have to fine-tune this later, since I haven't taken
the cost of switching from non-atomic to atomic code and vice versa
into account yet; in one case, a thread that had thousands of such
switches took 10 times as long to finish as when the same thread was
made atomic in its entirety.

There are other uses for atomic functions.  For example, suppose
that you are halfway through a very complex change, and discover at
that point that you cannot continue normally.  Rather than manually
undoing all changes made, you can just make the entire operation
atomic, and throw an error yourself.

At present, you cannot perform socket operations within atomic code
yet; I am working on this.


> "Bill Gates' biggest fear is not that some kid is brewing up the next killer
> app in his garage in Kenosha. His biggest fear is that some kid will brew up
> the next killer app in his garage in Kenosha and Microsoft won't own it."
> 	Seattle Times, 4/1-7 2000

Talking about killer apps: within the context of MUD programming
languages, especially for persistent MUDs, I think atomic functions
are going to be one of the great enablers.

Regards,
Dworkin

List config page:  http://list.imaginary.com/mailman/listinfo/dgd



More information about the DGD mailing list