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