1 /*---------------------------------------------------------------------------
2  * Heartbeat Management
3  *
4  *---------------------------------------------------------------------------
5  * This module holds the datastructures and function related to the
6  * handling of heartbeats.
7  *
8  * Objects with active heartbeats are referenced from a list which
9  * is sorted in ascending order of the object pointers. However, these
10  * object pointers do not count as 'refs'. The sorting prevents that
11  * objects with a rapid change of the heartbeat status are called more
12  * often than others.
13  *
14  * The backend will call call_heart_beat() in every cycle right after
15  * starting a new alarm(). The function evaluates as many heartbeats
16  * as possible before the alarm sets comm_time_to_call_heart_beat,
17  * and then returns. If some heartbeats are left unprocessed, the first
18  * of them is remembered in a pointer so that the next call can
19  * continue from there.
20  *
21  * However, no heartbeats are executed at all if there is no player
22  * in the game.
23  *
24  * TODO: Make the list a skiplist or similar.
25  * TODO: Add an object flag O_IN_HB_LIST so that several toggles of the
26  * TODO:: heart beat status only toggle O_HEARTBEAT, but leave the object
27  * TODO:: in the list until call_heart_beat() can remove it. This would
28  * TODO:: also remove the need for a double-linked or skiplist, but
29  * TODO:: require the object-pointer to count as ref and it could let
30  * TODO:: keep destructed objects in the list for a while.
31  *---------------------------------------------------------------------------
32  */
33 
34 #include "driver.h"
35 #include "typedefs.h"
36 
37 #include <stddef.h>
38 #include <stdio.h>
39 #include <sys/types.h>
40 #include <math.h>
41 
42 #include "heartbeat.h"
43 
44 #include "actions.h"
45 #include "array.h"
46 #include "backend.h"
47 #include "comm.h"
48 #include "exec.h"
49 #include "gcollect.h"
50 #include "interpret.h"
51 #include "mstrings.h"
52 #include "object.h"
53 #include "sent.h"
54 #include "simulate.h"
55 #include "strfuns.h"
56 #include "svalue.h"
57 #include "wiz_list.h"
58 #include "xalloc.h"
59 
60 #include "i-eval_cost.h"
61 
62 #include "../mudlib/sys/debug_info.h"
63 
64 /*-------------------------------------------------------------------------*/
65 
66 /* Listnode for one object with a heartbeat
67  * It is no use pooling the nodes to reduce allocation overhead, as
68  * the average heartbeat use is usually much lower than the peak usage.
69  */
70 
71 struct hb_info {
72     struct hb_info * next;  /* next node in list */
73     struct hb_info * prev;  /* previous node in list */
74     mp_int           tlast; /* time of last heart_beat */
75     object_t       * obj;   /* the object itself */
76 };
77 
78 
79 /*-------------------------------------------------------------------------*/
80 
81 object_t *current_heart_beat = NULL;
82   /* The object whose heart_beat() is currently executed. It is NULL outside
83    * of heartbeat executions.
84    *
85    * interpret.c needs to know this for the heart-beat tracing, and
86    * simulate.c test this in the errorf() function to react properly.
87    */
88 
89 static struct hb_info * hb_list = NULL;
90   /* Head of the list of heart_beat infos.
91    */
92 
93 static struct hb_info * next_hb = NULL;
94   /* Next hb_info whose objects' heartbeat must be executed.
95    * If NULL, the first info in the list is meant.
96    */
97 
98 #if defined(DEBUG)
99 mp_int num_hb_objs = 0;
100 #else
101 static mp_int num_hb_objs = 0;
102 #endif
103   /* Number of objects with a heart beat.
104    */
105 
106 static mp_int hb_num_done;
107   /* Number of heartbeats done in last process_objects().
108    */
109 
110 static long avg_num_hb_objs = 0;
111 static long avg_num_hb_done = 0;
112   /* Decaying average of num_hb_objs and hb_num_done.
113    */
114 
115 static long num_hb_calls = 0;
116   /* Number of calls to call_heart_beat() with active heartbeats.
117    */
118 
119 static long total_hb_calls = 0;
120   /* Total number of calls to call_heart_beat().
121    */
122 
123 /*-------------------------------------------------------------------------*/
124 void
call_heart_beat(void)125 call_heart_beat (void)
126 
127 /* Call the heart_beat() lfun in all registered heart beat objects; or at
128  * at least call as many as possible until the next alarm timeout (as
129  * registered in comm_time_to_call_heart_beat) occurs. If a timeout occurs,
130  * next_hb will point to the next object with a due heartbeat.
131  *
132  * If the object in question (or one of its shadows) is living, command_giver
133  * is set to the object, else it is set to NULL. If there are no players
134  * in the game, no heart_beat() will be called (but the call outs will!).
135  *
136  * The function does not change the time_to_call-flags or messes with alarm().
137  * It may be aborted prematurely if an error occurs during the heartbeat.
138  */
139 
140 {
141     struct hb_info *this;
142       /* Current list pointer, static so that longjmp() won't clobber it */
143 
144     static mp_int num_hb_to_do;
145       /* For statistics only */
146 
147     struct error_recovery_info error_recovery_info;
148 
149     /* Housekeeping */
150 
151     current_interactive = NULL;
152     total_hb_calls++;
153 
154     /* Set this new round through the hb list */
155     hb_num_done = 0;
156 
157     if (num_player < 1 || !num_hb_objs)
158     {
159         next_hb = NULL;
160         return;
161     }
162 
163     num_hb_to_do = num_hb_objs;
164     num_hb_calls++;
165 
166     /* Activate the local error recovery context */
167 
168     error_recovery_info.rt.last = rt_context;
169     error_recovery_info.rt.type = ERROR_RECOVERY_BACKEND;
170     rt_context = (rt_context_t *)&error_recovery_info.rt;
171 
172     if (setjmp(error_recovery_info.con.text))
173     {
174         /* An error occured: recover. The guilt heartbeat has already
175          * been removed by simulate::error().
176          */
177         mark_end_evaluation();
178         clear_state();
179         debug_message("%s Error in heartbeat.\n", time_stamp());
180     }
181 
182     /* Set this to the next hb to be execute.
183      * This is the loop invariant.
184      */
185     this = next_hb;
186 
187     while (num_hb_objs && !comm_time_to_call_heart_beat)
188     {
189         object_t * obj;
190 
191         /* If 'this' object is NULL, we reached the end of the
192          * list and have to wrap around.
193          */
194         if (!this)
195         {
196 #ifdef DEBUG
197             if (!hb_list)
198                 fatal("hb_list is NULL, but num_hb_objs is %ld\n", (long)num_hb_objs);
199 #endif
200             this = hb_list;
201         }
202 
203         /* Check the time of the last heartbeat to see, if we
204          * processed all objects.
205          */
206         if (current_time < this->tlast + heart_beat_interval)
207             break;
208 
209         this->tlast = current_time;
210 
211         obj = this->obj;
212         next_hb = this->next;
213 
214         hb_num_done++;
215 
216 #ifdef DEBUG
217         if (!(obj->flags & O_HEART_BEAT))
218             fatal("Heart beat not set in object '%s' on heart beat list!\n"
219                  , get_txt(obj->name)
220                  );
221         if (obj->flags & O_SWAPPED)
222             fatal("Heart beat in swapped object '%s'.\n", get_txt(obj->name));
223         if (obj->flags & O_DESTRUCTED)
224             fatal("Heart beat in destructed object '%s'.\n", get_txt(obj->name));
225 #endif
226 
227         if (obj->prog->heart_beat == -1)
228         {
229             /* Swapped? No heart_beat()-lfun? Turn off the heart.
230              */
231 
232             obj->flags &= ~O_HEART_BEAT;
233             num_hb_objs--;
234             if (this->prev)
235                 this->prev->next = this->next;
236             if (this->next)
237                 this->next->prev = this->prev;
238             if (this == hb_list)
239                 hb_list = this->next;
240 
241             xfree(this);
242         }
243         else
244         {
245             /* Prepare to call <ob>->heart_beat().
246              */
247             current_prog = obj->prog;
248             current_object = obj;
249             current_heart_beat = obj;
250 
251             /* Determine the command_giver. Usually it's the object
252              * itself, but it may be one of the shadows if there are
253              * some. In any case it must be a living.
254              */
255             command_giver = obj;
256             if (command_giver->flags & O_SHADOW)
257             {
258                 shadow_t *shadow_sent;
259 
260                 while(shadow_sent = O_GET_SHADOW(command_giver),
261                       shadow_sent->shadowing)
262                 {
263                     command_giver = shadow_sent->shadowing;
264                 }
265 
266                 if (!(command_giver->flags & O_ENABLE_COMMANDS))
267                 {
268                     command_giver = NULL;
269                     trace_level = 0;
270                 }
271                 else
272                 {
273                     trace_level = shadow_sent->ip
274                                   ? shadow_sent->ip->trace_level
275                                   : 0;
276                 }
277             }
278             else
279             {
280                 if (!(command_giver->flags & O_ENABLE_COMMANDS))
281                     command_giver = NULL;
282                 trace_level = 0;
283             }
284 
285             mark_start_evaluation();
286             obj->user->heart_beats++;
287             CLEAR_EVAL_COST;
288             RESET_LIMITS;
289             call_function(obj->prog, obj->prog->heart_beat);
290             mark_end_evaluation();
291 
292         } /* if (object has heartbeat) */
293 
294         /* Step to next object with heart beat */
295         this = next_hb;
296     } /* while (not done) */
297 
298     rt_context = error_recovery_info.rt.last;
299 
300     /* Update stats */
301     avg_num_hb_objs += num_hb_to_do - (avg_num_hb_objs >> 10);
302     avg_num_hb_done += hb_num_done  - (avg_num_hb_done >> 10);
303 
304     current_heart_beat = NULL;
305 } /* call_heart_beat() */
306 
307 /*-------------------------------------------------------------------------*/
308 int
set_heart_beat(object_t * ob,Bool to)309 set_heart_beat (object_t *ob, Bool to)
310 
311 /* EFUN set_heart_beat() and internal use.
312  *
313  * Add (<to> != 0) or remove (<to> == 0) object <ob> to/from the list
314  * of heartbeat objects, thus activating/deactivating its heart beat.
315  * Return 0 on failure (including calls for destructed objects or if
316  * the object is already in the desired state) and 1 on success.
317  *
318  * The function is aware that it might be called from within a heart_beat()
319  * and adjusts the correct pointers if that is so.
320  */
321 
322 {
323     /* Safety checks */
324     if (ob->flags & O_DESTRUCTED)
325         return 0;
326     if (to)
327         to = O_HEART_BEAT; /* ...so that the following comparison works */
328     if (to == (ob->flags & O_HEART_BEAT))
329         return 0;
330 
331     if (to)  /* Add a new heartbeat */
332     {
333         struct hb_info *new;
334 
335         /* Get a new node */
336         new = xalloc(sizeof(*new));
337 
338         new->tlast = current_time;
339         new->obj = ob;
340 
341         /* Insert the new node at the proper place in the list */
342         if (!hb_list || !next_hb || !next_hb->prev)
343         {
344             new->next = hb_list;
345             new->prev = NULL;
346             if (hb_list)
347                 hb_list->prev = new;
348             hb_list = new;
349 
350 	    /* Handle all the other objects first. */
351 	    if(!next_hb)
352 		next_hb = new->next;
353         }
354         else
355         {
356 	    /* Insert it just before next_hb, so this is the last object. */
357             new->next = next_hb;
358             new->prev = next_hb->prev;
359             next_hb->prev->next = new;
360 	    next_hb->prev = new;
361         }
362 
363         num_hb_objs++;
364         ob->flags |= O_HEART_BEAT;
365     }
366     else  /* remove an existing heartbeat */
367     {
368         struct hb_info *this;
369 
370         /* Remove the node from the list */
371         for (this = hb_list; this && this->obj != ob; this = this->next)
372             NOOP;
373 #ifdef DEBUG
374         if (!this)
375             fatal("Object '%s' not found in heart beat list.\n", get_txt(ob->name));
376 #endif
377         if (this->next)
378             this->next->prev = this->prev;
379         if (this->prev)
380             this->prev->next = this->next;
381         if (this == hb_list)
382             hb_list = this->next;
383         if (this == next_hb)
384             next_hb = this->next;
385 
386         xfree(this);
387 
388         num_hb_objs--;
389         ob->flags &= ~O_HEART_BEAT;
390     }
391 
392     /* That's it */
393     return 1;
394 } /* set_heart_beat() */
395 
396 /*-------------------------------------------------------------------------*/
397 #ifdef GC_SUPPORT
398 
399 void
count_heart_beat_refs(void)400 count_heart_beat_refs (void)
401 
402 /* Count the reference to the hb_list array in a garbage collection.
403  */
404 
405 {
406     struct hb_info *this;
407 
408     for (this = hb_list; this != NULL; this = this->next)
409         note_malloced_block_ref(this);
410 }
411 #endif
412 
413 /*-------------------------------------------------------------------------*/
414 int
heart_beat_status(strbuf_t * sbuf,Bool verbose)415 heart_beat_status (strbuf_t * sbuf, Bool verbose)
416 
417 /* If <verbose> is true, print the heart beat status information directly
418  * to the current interactive user. In any case, return the amount of
419  * memory used by the heart beat functions.
420  */
421 
422 {
423     if (verbose) {
424         strbuf_add(sbuf, "\nHeart beat information:\n");
425         strbuf_add(sbuf, "-----------------------\n");
426         strbuf_addf(sbuf, "Number of objects with heart beat: %ld, "
427                           "beats: %ld\n"
428                    , (long)num_hb_objs, (long)num_hb_calls);
429 #if defined(__MWERKS__) && !defined(WARN_ALL)
430 #    pragma warn_largeargs off
431 #endif
432         strbuf_addf(sbuf, "HB calls completed in last cycle:  %ld (%.2f%%)\n"
433                    , (long)hb_num_done
434                    , num_hb_objs && hb_num_done <= num_hb_objs
435                      ? 100.0 * (float)hb_num_done / (float)num_hb_objs
436                      : 100.0
437                    );
438         strbuf_addf(sbuf
439                    , "Average of HB calls completed:     %.2f%%\n"
440                    , avg_num_hb_objs ?
441                      100 * (double) avg_num_hb_done / avg_num_hb_objs :
442                      100.0
443                    );
444 #if defined(__MWERKS__)
445 #    pragma warn_largeargs reset
446 #endif
447     }
448     return num_hb_objs * sizeof(struct hb_info);
449 } /* heart_beat_status() */
450 
451 /*-------------------------------------------------------------------------*/
452 void
hbeat_dinfo_status(svalue_t * svp,int value)453 hbeat_dinfo_status (svalue_t *svp, int value)
454 
455 /* Return the heartbeat information for debug_info(DINFO_DATA, DID_STATUS).
456  * <svp> points to the svalue block for the result, this function fills in
457  * the spots for the object table.
458  * If <value> is -1, <svp> points indeed to a value block; other it is
459  * the index of the desired value and <svp> points to a single svalue.
460  */
461 
462 {
463 #define ST_NUMBER(which,code) \
464     if (value == -1) svp[which].u.number = code; \
465     else if (value == which) svp->u.number = code
466 
467 #define ST_DOUBLE(which,code) \
468     if (value == -1) { \
469         svp[which].type = T_FLOAT; \
470         STORE_DOUBLE(svp+which, code); \
471     } else if (value == which) { \
472         svp->type = T_FLOAT; \
473         STORE_DOUBLE(svp, code); \
474     }
475 
476     STORE_DOUBLE_USED;
477 
478     ST_NUMBER(DID_ST_HBEAT_OBJS, num_hb_objs);
479     ST_NUMBER(DID_ST_HBEAT_CALLS, num_hb_calls);
480     ST_NUMBER(DID_ST_HBEAT_CALLS_TOTAL, total_hb_calls);
481     ST_NUMBER(DID_ST_HBEAT_SLOTS, num_hb_objs);
482     ST_NUMBER(DID_ST_HBEAT_SIZE, num_hb_objs * sizeof(struct hb_info));
483     ST_NUMBER(DID_ST_HBEAT_PROCESSED, hb_num_done);
484     ST_DOUBLE(DID_ST_HBEAT_AVG_PROC
485              , avg_num_hb_objs
486                ? ((double)avg_num_hb_done / avg_num_hb_objs)
487                : 1.0
488              );
489 
490 #undef ST_NUMBER
491 #undef ST_DOUBLE
492 } /* hbeat_dinfo_status() */
493 
494 /*=========================================================================*/
495 
496 /*                               EFUNS                                     */
497 
498 /*-------------------------------------------------------------------------*/
499 svalue_t *
f_set_heart_beat(svalue_t * sp)500 f_set_heart_beat (svalue_t *sp)
501 
502 /* EFUN set_heart_beat()
503  *
504  *   int set_heart_beat(int flag)
505  *
506  * Enable or disable heart beat. The driver will apply
507  * the lfun heart_beat() to the current object every 2 seconds,
508  * if it is enabled. If the heart beat is not needed for the
509  * moment, then do disable it. This will reduce system overhead.
510  *
511  * Return true for success, and false for failure.
512  *
513  * Disabling an already disabled heart beat (and vice versa
514  * enabling and enabled heart beat) counts as failure.
515  */
516 
517 {
518     int i;
519 
520     i = set_heart_beat(current_object, sp->u.number != 0);
521     sp->u.number = i;
522 
523     return sp;
524 } /* f_set_heart_beat() */
525 
526 /*-------------------------------------------------------------------------*/
527 svalue_t *
f_heart_beat_info(svalue_t * sp)528 f_heart_beat_info (svalue_t *sp)
529 
530 /* EFUN heart_beat_info()
531  *
532  * Create a vector of all objects with a heart beat and push it
533  * onto the stack. The resulting vector may be empty.
534  *
535  * This efun is expensive!
536  * TODO: Invent something like a hash table where all objects are store
537  * TODO:: which had a heartbeat at least once. Repeated starts and stops
538  * TODO:: of the heartbeat would be cheap, also deletion when an object
539  * TODO:: is destructed.
540  */
541 
542 {
543     int i;
544     vector_t *vec;
545     svalue_t *v;
546     struct hb_info *this;
547 
548     vec = allocate_array(i = num_hb_objs);
549     for (v = vec->item, this = hb_list; i >= 0 && this; this = this->next)
550     {
551 #ifdef DEBUG
552         if (this->obj->flags & O_DESTRUCTED)  /* TODO: Can't happen. */
553             continue;
554 #endif
555         put_ref_object(v, this->obj, "heart_beat_info");
556         v++;
557         i--;
558     }
559 
560     push_array(sp, vec);
561     return sp;
562 } /* f_heart_beat_info() */
563 
564 /***************************************************************************/
565 
566