Phantasmal MUD Lib for DGD
|
Phantasmal Site > DGD > Writing a Library > Rlimits Using rlimits() in your LibraryThe rlimits() DGD construct can be used to keep your MUD running despite infinite loops and infinite recursions in code. In combination with status(), it can be used for profiling your code and keeping track of how much processor time each wizard, or each function, is taking up. If you're not using the Kernel Library, you need to understand rlimits() intimately to avoid infinite recursions and loops. If you are using the Kernel Library, it can still be useful here and there. It turns out that rlimits() isn't actually a function, which is why it can have the odd syntax it does. Specifically, it gets used this way: rlimits(-1;-1) { ⁄* Some code goes here *⁄ } Rlimits has two parameters, both set to -1 above. The first is the limit on the stack depth, the second is the limit on the tick count. If the code inside the curly braces exceeds either limit, an error occurs. When either limit is -1, that means unlimited — no limit to stack depth, or no limit to tick count. The stack depth is a measure of how many functions are active at once. Functions call each other, and those call other functions, to greater and greater depth, and more data is allocated. As that happens, the stack depth gets closer and closer to the limit. This prevents an infinite (or just very deep) recursion inside the rlimits() statement. Ticks are a processor-independent measurement of how much time the code is likely to take. Felix calls them "a weighted measure of the number of instructions and the size of the datastructures manipulated by these instructions". Since DGD focuses strongly on compatibility, it's important that the measurement be cross-platform. Each operation in DGD takes a number of ticks, depending on what the operation is, and what data it is operating on. Some operations, like 32-bit integer addition, will always take the same number of ticks. Some operations, like parse_string(), will take a widely-varying number of ticks, depending on what the inputs to that operation are. The status() call returns an array of interesting information. One offset within this array is to the current tick count, which increases over time as operations occur. This can be used to determine how close to the tick limit your code is, and to profile code or 'bill' different operations for the number of ticks they use up. The use of rlimits() is to prevent infinite recursions or infinite loops in suspect code — code that you're not entirely sure will return in a reasonable amount of time, or at all. Any operation based on player data, for instance, is likely to be in this category since somebody might want to bring your MUD to a halt with a complicated operation. Your library may want to limit its own actions as well, so that faulty code on your part is less likely to crash the entire game. To be really certain, just set up an rlimits() call from every place that DGD calls into your code. That means the driver object and the user object, several calls in each. Good places to set rlimits() include:
You should also be careful to specifically limit any user-supplied code or builder-supplied code. If you have room scripts or other (relatively) inessential code, don't be shy about putting an rlimits() around it with a more restrictive set of limits. That keeps such code from burning through too many of your ticks. Rlimits often goes well with a catch() statement so that if the code runs too long or recurses too deep, you can log a more meaningful error about exactly what code caused the problem. You'll want to make sure that an rlimits() inside the one you put in place can't override you. Somebody can put an rlimits(-1;-1) somewhere inside the code you limited, and then the error won't be caused if they go into an infinite loop. Luckily, DGD will query your DRIVER object every time somebody attempts an rlimits() statement, so you'll need to make sure your rlimits() statement works (to limit them) and their doesn't work when they try to override you. See the Kernel Library for good examples of how to use rlimits(), including not letting the code you call override your limits. The MudOSalike package, by Frank Schmidt, is also a good example of rlimits(). Here is a bit of a tutorial, this one by Erwin Harte: To make sure a function started by a call_out() is wrapped inside such an rlimits construct, you would have to redefine call_out() in your auto-object, like this: static int call_out(string func, int delay, mixed args...) { return ::call_out("_F_call_out", delay, func, args); } nomask void _F_call_out(string func, mixed *args) { if (previous_program()) { rlimits (MAX_STACK; MAX_TICKS) { call_other(this_object(), func, args...); } } } The 'if (previous_program()) {' condition is to make sure the function cannot be abused by calling it directly, this way it will only call the given function with the parameters if it's the start of a thread. If you're thinking of making _F_call_out() a static function, think again, because that would make it an 'efun' since it's in the auto- object, and then you cannot call it from a call_out(). :-) From: DGD Mailing List (Felix A. Croes) Date: Sat Apr 5 14:20:01 2003 Subject: [DGD] Interaction of catch{} and rlimits{} Noah Lee Gibbs wrote: > Say you're within an rlimits{} construct, perhaps as a result of being > called by the Kernel Library's call_limited() function. You have limited > ticks available. > Say you're within a catch{} statement within that rlimits{}, and the > catch{} has one of those "do on error" sections. Now say in the body of > the catch{}, you run out of ticks. > You'd like to do the "do on error" stuff but you're out of ticks. > Should we always use an extra rlimits{..;-1} around any catch{} statement > where we *really* want the "on error" block to execute? When error recovery is essential, there are two things you can do. 1. You can make sure that something happens using rlimits: rlimits (0; -1) { catch { call_limited("foo"); } : { /* * this MUST complete to fully recover from an error */ bar(); } } There are several problems with this approach. First, when using the kernel library, only System objects can use rlimits (...; -1). Second, an infinite loop in bar() will hang your mud forever. Third, even though foo() is called with limits appropriate for the current object, those limits are reset for the call, thanks to the enclosing rlimits (..; -1). Fourth, the ticks spent in bar() will not be accounted for using the kernel library's resource management system. 2. You can make sure that nothing happened, using atomic functions: atomic void foo() { /* * an error inside this function will undo all the actions * performed inside this function */ } Atomic functions can be used by anyone. They have two drawbacks: first, you cannot do file-changing actions inside them. Second, they take twice the amount of ticks that would ordinarily be spent in that function. Therefore, it makes sense to keep the actions performed in these functions to a minimum. However, since the actual time spent inside atomic functions is usually far less than twice that of an equivalent non-atomic function, an alternative would be to take that overhead for granted, double all tick quotas, and use atomic functions all through your mudlib. The latter approach was taken by Skotos. Practically everything in a Skotos mud is performed atomically. Pick whichever solution suits you best. Regards, Dworkin From: DGD Mailing List (Felix A. Croes) Date: Mon Mar 29 05:13:01 2004 Subject: [DGD] rlimits Steve Wooster wrote: > I'm a little confused about exactly how rlimits works... I understand > that you put it around code to limit how many tick/etc that code can take > up, but I'm confused as to how rlimits within rlimits works... > > For example: (I've forgotten whether ticks or recursion depth comes first, > so I'll say "ticks" or "depth" after the numbers) > > void func() > { > rlimits (10 depth, 100 ticks) > { > code that uses 50 ticks; > rlimits (10 depth, -1 ticks) > { > code that uses 25 ticks; > } > // Are there 50 ticks remaining or only 25? > } > } 50 ticks remaining. > Now that I've typed this out, I think I know what I want to ask... When you > use an rlimits construct inside another one, does code inside the inner > rlimits it count towards the ticks for the outer rlimits? It seems like the > most logical answer would be "no", but I'd feel better if somebody > confirmed this. The inner ticks don't count for the outer rlimits. However, the mudlib has some say in this, since each use of rlimits() is checked at compile time and possibly also at runtime. The kernel library doesn't allow increasing the resource limits beyond what they currently are, <unless> the current object is a System object. Regards, Dworkin From: DGD Mailing List (Felix A. Croes) Date: Mon Mar 29 14:40:02 2004 Subject: [DGD] rlimits Steve Wooster wrote: > Could that allow code to do something like this without running out of > ticks? (assume depth comes first, ticks comes second in the argument list) The syntax is: rlimits (depth; ticks) { ... } > void func() > { > // Assume I have 100 ticks remaining. > // Assume rlimits uses 5 ticks. > rlimits (0,95) > { > Code that takes up 90 ticks; > } > rlimits (0,90) > { > Code that takes up 85 ticks; > } > // Are there still about 90 ticks left despite having > // executed 175 ticks worth of code? > } Yes. Regards, Dworkin |