Phantasmal MUD Lib for DGD
|
Phantasmal Site > DGD > DGD LPC Reference > Atomic Functions Atomic Functions in DGDDate: Sun, 17 Sep 2000 15:11:07 +0200 (CEST) From: "Felix A. Croes" Subject: Re: [DGD]atomic functions Stephen Schmidt 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 From: dgd at list.imaginary.com (Par Winzell) Date: Mon Oct 21 22:31:01 2002 Subject: [DGD] Atomics > I am trying to use atomic_error() to log the portion > of the stack trace which occur within an atomic > function. Unfortunately, atomic_error() also seems to > be called from within an atomic function, which means > I can't write to a log file from within this function, > I can't set any variables to indicate to a future > function what I want logged (the variables get rolled > back when the atomic exits), and I can't callout to > write to the log file within the atomic (because the > callout will be removed when the atomic exits). Right. I believe the idea is that atomic really does roll back all state, and if you want to trick it you have to go slightly out of your way to do it. > Is there any way I can write data from atomic_error() > to a log, or do I have to use DRIVER->message() to > write it to stderr? We make use of the fact that the string sent to error() actually does survive the atomic rollback. We catch runtime errors, package up the data we want to survive the rollback as ASCII, and stuff it into the string. Then we rethrow this string with error(). The atomic rollback occurs, but runtime_error() recognizes the specially formated error string, unpacks the data, and happily exports it to functions that want to dig into it. It can really give you a headache, though. Zell From: dgd at list.imaginary.com (Felix A. Croes) Date: Tue Feb 17 17:06:00 2004 Subject: [DGD] Atomic Errorage Michael McKiel wrote: >[...] > I didn't seem to be getting any "atomic" errors after putting what will > cause a runtime error into the function do_command, so I checked the > error stream (stderr?) from the linux shell. > > And got this: > Bad argument 1 for kfun explode [atomic] > 32 do_command /command/admin/ttt > Bad argument 1 for kfun explode > 273 receive_message /system/user (#18) > 327 command /system/player (#19) > Bad argument 1 for kfun explode > Object: /system/player#19, program: /system/player, line 327 > > So the atomic error is being noticed/detected...but it barrels on thru > to runtime_error()... > > So is there more that needs to be done to make atomic work? or is that what > it's supposed to do? I had figured it shouldn't pass thru to runtime_error() It's working properly. The error is not supposed to disappear; only the changes made by the atomic function should. Regards, Dworkin |