-
Notifications
You must be signed in to change notification settings - Fork 909
/
ro_timer.c
554 lines (476 loc) · 15.9 KB
/
ro_timer.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
/*
* File: ro_timer.c
* Author: Jason Penton
*
* Created on 06 April 2011, 1:37 PM
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include "../../core/mem/shm_mem.h"
#include "../ims_dialog/dlg_load.h"
#include "ro_timer.h"
#include "ro_session_hash.h"
#include "ims_ro.h"
#include "ro_db_handler.h"
#include "ims_charging_mod.h"
#include "ims_charging_stats.h"
#include "../../core/counters.h"
extern int interim_request_credits;
extern int ro_timer_buffer;
extern int ro_db_mode;
extern ims_dlg_api_t dlgb;
extern struct ims_charging_counters_h ims_charging_cnts_h;
/*! global dialog timer */
struct ro_timer *roi_timer = 0;
/*! global dialog timer handler */
ro_timer_handler timer_hdl = 0;
/*!
* \brief Initialize the ro_session timer handler
* Initialize the ro_session timer handler, allocate the lock and a global
* timer in shared memory. The global timer handler will be set on success.
* \param hdl ro_session timer handler
* \return 0 on success, -1 on failure
*/
int init_ro_timer(ro_timer_handler hdl)
{
roi_timer = (struct ro_timer *)shm_malloc(sizeof(struct ro_timer));
if(roi_timer == 0) {
LM_ERR("no more shm mem\n");
return -1;
}
memset(roi_timer, 0, sizeof(struct ro_timer));
roi_timer->first.next = roi_timer->first.prev = &(roi_timer->first);
roi_timer->lock = lock_alloc();
if(roi_timer->lock == 0) {
LM_ERR("failed to alloc lock\n");
goto error0;
}
if(lock_init(roi_timer->lock) == 0) {
LM_ERR("failed to init lock\n");
goto error1;
}
timer_hdl = hdl;
return 0;
error1:
lock_dealloc(roi_timer->lock);
error0:
shm_free(roi_timer);
roi_timer = 0;
return -1;
}
/*!
* \brief Destroy global ro_session timer
*/
void destroy_ro_timer(void)
{
if(roi_timer == 0)
return;
lock_destroy(roi_timer->lock);
lock_dealloc(roi_timer->lock);
shm_free(roi_timer);
roi_timer = 0;
}
/*!
* \brief Helper function for insert_ro_session_timer
* \see insert_ro_session_timer
* \param tl ro_session timer list
*/
static inline void insert_ro_timer_unsafe(struct ro_tl *tl)
{
struct ro_tl *ptr;
/* insert in sorted order */
for(ptr = roi_timer->first.prev; ptr != &roi_timer->first;
ptr = ptr->prev) {
if(ptr->timeout <= tl->timeout)
break;
}
LM_DBG("inserting %p for %d\n", tl, tl->timeout);
LM_DBG("BEFORE ptr [%p], ptr->next [%p], ptr->next->prev [%p]\n", ptr,
ptr->next, ptr->next->prev);
tl->prev = ptr;
tl->next = ptr->next;
tl->prev->next = tl;
tl->next->prev = tl;
LM_DBG("AFTER tl->prev [%p], tl->next [%p]\n", tl->prev, tl->next);
}
/*!
* \brief Insert a ro_session timer to the list
* \param tl ro_session timer list
* \param interval timeout value in seconds
* \return 0 on success, -1 when the input timer list is invalid
*/
int insert_ro_timer(struct ro_tl *tl, int interval)
{
lock_get(roi_timer->lock);
LM_DBG("inserting timer for interval [%i]\n", interval);
if(tl->next != 0 || tl->prev != 0) {
lock_release(roi_timer->lock);
LM_CRIT("Trying to insert a bogus ro tl=%p tl->next=%p tl->prev=%p\n",
tl, tl->next, tl->prev);
return -1;
}
tl->timeout = get_ticks() + interval;
insert_ro_timer_unsafe(tl);
LM_DBG("TIMER inserted\n");
lock_release(roi_timer->lock);
return 0;
}
/*!
* \brief Helper function for remove_ro_session_timer
* \param tl ro_session timer list
* \see remove_ro_session_timer
*/
static inline void remove_ro_timer_unsafe(struct ro_tl *tl)
{
tl->prev->next = tl->next;
tl->next->prev = tl->prev;
}
/*!
* \brief Remove a ro_session timer from the list
* \param tl ro_session timer that should be removed
* \return 1 when the input timer is empty, 0 when the timer was removed,
* -1 when the input timer list is invalid
*/
int remove_ro_timer(struct ro_tl *tl)
{
lock_get(roi_timer->lock);
if(tl->prev == NULL && tl->timeout == 0) {
lock_release(roi_timer->lock);
return 1;
}
if(tl->prev == NULL || tl->next == NULL) {
LM_CRIT("bogus tl=%p tl->prev=%p tl->next=%p\n", tl, tl->prev,
tl->next);
lock_release(roi_timer->lock);
return -1;
}
LM_DBG("TIMER [%p] REMOVED\n", tl);
remove_ro_timer_unsafe(tl);
tl->next = NULL;
tl->prev = NULL;
tl->timeout = 0;
lock_release(roi_timer->lock);
return 0;
}
/*!
* \brief Update a ro_session timer on the list
* \param tl dialog timer
* \param timeout new timeout value in seconds
* \return 0 on success, -1 when the input list is invalid
* \note the update is implemented as a remove, insert
*/
int update_ro_timer(struct ro_tl *tl, int timeout)
{
lock_get(roi_timer->lock);
LM_DBG("Updating ro timer [%p] with timeout [%d]\n", tl, timeout);
if(tl->next) {
if(tl->prev == 0) {
lock_release(roi_timer->lock);
return -1;
}
remove_ro_timer_unsafe(tl);
}
tl->timeout = get_ticks() + timeout;
insert_ro_timer_unsafe(tl);
lock_release(roi_timer->lock);
return 0;
}
/*!
* \brief Helper function for ro_timer_routine
* \param time time for expiration check
* \return list of expired credit reservations on sessions on success, 0 on failure
*/
static inline struct ro_tl *get_expired_ro_sessions(unsigned int time)
{
struct ro_tl *tl, *end, *ret;
lock_get(roi_timer->lock);
LM_DBG("my ticks are [%d]\n", time);
if(roi_timer->first.next == &(roi_timer->first)
|| roi_timer->first.next->timeout > time) {
lock_release(roi_timer->lock);
return 0;
}
end = &roi_timer->first;
tl = roi_timer->first.next;
LM_DBG("start with tl=%p tl->prev=%p tl->next=%p (%d) at %d and end with "
"end=%p end->prev=%p end->next=%p\n",
tl, tl->prev, tl->next, tl->timeout, time, end, end->prev,
end->next);
while(tl != end && tl->timeout <= time) {
LM_DBG("getting tl=%p tl->prev=%p tl->next=%p with %d\n", tl, tl->prev,
tl->next, tl->timeout);
tl->prev = 0;
tl->timeout = 0;
tl = tl->next;
}
LM_DBG("end with tl=%p tl->prev=%p tl->next=%p and "
"d_timer->first.next->prev=%p\n",
tl, tl->prev, tl->next, roi_timer->first.next->prev);
if(tl == end && roi_timer->first.next->prev) {
ret = 0;
} else {
ret = roi_timer->first.next;
tl->prev->next = 0;
roi_timer->first.next = tl;
tl->prev = &roi_timer->first;
}
lock_release(roi_timer->lock);
return ret;
}
/*!
* \brief Timer routine for expiration of credit reservations
* Timer handler for expiration of credit reservations on a session, runs the global timer handler on them.
* \param time for expiration checks
* \param attr unused
*/
void ro_timer_routine(unsigned int ticks, void *attr)
{
struct ro_tl *tl, *ctl;
LM_DBG("getting expired ro-sessions\n");
tl = get_expired_ro_sessions(ticks);
while(tl) {
ctl = tl;
tl = tl->next;
ctl->next = NULL;
LM_DBG("Ro Session Timer firing: tl=%p next=%p\n", ctl, tl);
timer_hdl(ctl);
}
}
void resume_ro_session_ontimeout(
struct interim_ccr *i_req, int timeout_or_error)
{
time_t now = get_current_time_micro();
long used_secs;
struct ro_session_entry *ro_session_entry = NULL;
int call_terminated = 0;
if(!i_req) {
LM_ERR("This is so wrong: i_req is NULL\n");
return;
}
ro_session_entry = &(ro_session_table->entries[i_req->ro_session->h_entry]);
ro_session_lock(ro_session_table, ro_session_entry);
LM_DBG("credit=%d credit_valid_for=%d\n", i_req->new_credit,
i_req->credit_valid_for);
used_secs = rint(
(now
- ((timeout_or_error == 1
&& i_req->ro_session->last_event_timestamp_backup
> 0)
? i_req->ro_session
->last_event_timestamp_backup
: i_req->ro_session->last_event_timestamp))
/ (float)1000000);
/* check to make sure diameter server is giving us sane values */
if(i_req->credit_valid_for != 0
&& i_req->new_credit > i_req->credit_valid_for) {
LM_WARN("That's weird, Diameter server gave us credit with a lower "
"validity period :D. Setting reserved time to validity period "
"instead \n");
i_req->new_credit = i_req->credit_valid_for;
}
if(i_req->new_credit > 0) {
//now insert the new timer
i_req->ro_session->last_event_timestamp = get_current_time_micro();
i_req->ro_session->event_type = answered;
i_req->ro_session->valid_for = i_req->credit_valid_for;
int ret = 0;
if(i_req->is_final_allocation) {
LM_DBG("This is a final allocation and call will end in %i "
"seconds\n",
i_req->new_credit);
i_req->ro_session->event_type = no_more_credit;
ret = insert_ro_timer(&i_req->ro_session->ro_tl, i_req->new_credit);
} else {
int timer_timeout = i_req->new_credit;
if(i_req->new_credit > ro_timer_buffer /*TIMEOUTBUFFER*/) {
// We haven't finished using our 1st block of units, and we need to set the timer to
// (new_credit - ro_timer_buffer[5 secs]) to ensure we get new credit before our previous
// reservation is exhausted. This will only be done the first time, because the timer
// will always be fired 5 seconds before we run out of time thanks to this operation
timer_timeout = i_req->new_credit - ro_timer_buffer;
}
ret = insert_ro_timer(&i_req->ro_session->ro_tl, timer_timeout);
}
// update to the new block of units we got
i_req->ro_session->reserved_secs = i_req->new_credit;
if(ret != 0) {
LM_CRIT("unable to insert timer for Ro Session [%.*s]\n",
i_req->ro_session->ro_session_id.len,
i_req->ro_session->ro_session_id.s);
} else {
ref_ro_session(i_req->ro_session, 1, 0);
}
i_req->ro_session->flags |= RO_SESSION_FLAG_CHANGED;
if(ro_db_mode == DB_MODE_REALTIME) {
if(update_ro_dbinfo_unsafe(i_req->ro_session) != 0) {
LM_ERR("Failed to update Ro session in DB... continuing\n");
}
}
} else {
/* just put the timer back in with however many seconds are left (if any!!! in which case we need to kill */
/* also update the event type to no_more_credit to save on processing the next time we get here */
i_req->ro_session->event_type = no_more_credit;
if(!timeout_or_error)
i_req->ro_session->last_event_timestamp = get_current_time_micro();
int whatsleft = i_req->ro_session->reserved_secs - used_secs;
if(whatsleft <= 0) {
// TODO we need to handle this situation more precisely.
// in case CCR times out, we get a call shutdown but the error message assumes it was due to a lack of credit.
//
LM_WARN("Immediately killing call due to no more credit *OR* no "
"CCA received (timeout) after reservation request\n");
//
// we need to unlock the session or else we might get a deadlock on dlg_terminated() dialog callback.
// Do not unref the session because it will be made inside the dlg_terminated() function.
//
//unref_ro_session_unsafe(i_req->ro_session, 1, ro_session_entry);
ro_session_unlock(ro_session_table, ro_session_entry);
dlgb.lookup_terminate_dlg(i_req->ro_session->dlg_h_entry,
i_req->ro_session->dlg_h_id, NULL);
call_terminated = 1;
} else {
LM_DBG("No more credit for user - letting call run out of money in "
"[%i] seconds\n",
whatsleft);
int ret = insert_ro_timer(&i_req->ro_session->ro_tl, whatsleft);
if(ret != 0) {
LM_CRIT("unable to insert timer for Ro Session [%.*s]\n",
i_req->ro_session->ro_session_id.len,
i_req->ro_session->ro_session_id.s);
} else {
ref_ro_session(i_req->ro_session, 1, 0);
}
}
}
//
// if call was forcefully terminated, the lock was released before dlgb.lookup_terminate_dlg() function call.
//
if(!call_terminated) {
unref_ro_session(i_req->ro_session, 1,
0); //unref from the initial timer that fired this event.
ro_session_unlock(ro_session_table, ro_session_entry);
}
shm_free(i_req);
LM_DBG("Exiting async ccr interim nicely\n");
}
/* this is the function called when a we need to request more funds/credit. We need to try and reserve more credit.
* If we can't we need to put a new timer to kill the call at the appropriate time
*/
void ro_session_ontimeout(struct ro_tl *tl)
{
time_t now, call_time;
long used_secs;
int adjustment;
str default_out_of_credit_hdrs = {"Reason: outofcredit\r\n", 21};
LM_DBG("We have a fired timer [p=%p] and tl=[%i].\n", tl, tl->timeout);
/* find the session id for this timer*/
struct ro_session *ro_session = ((struct ro_session
*)((char *)(tl)
- (unsigned long)(&((struct ro_session *)0)->ro_tl)));
LM_DBG("offset for ro_tl is [%lu] and ro_session id is [%.*s]\n",
(unsigned long)(&((struct ro_session *)0)->ro_tl),
ro_session->ro_session_id.len, ro_session->ro_session_id.s);
if(!ro_session) {
LM_ERR("Can't find a session. This is bad\n");
return;
}
LM_DBG("event-type=%d\n", ro_session->event_type);
// if (!ro_session->active) {
// LM_ALERT("Looks like this session was terminated while requesting more units\n");
// goto exit;
// return;
// }
if(ro_session->is_final_allocation) {
now = get_current_time_micro();
used_secs = now - ro_session->last_event_timestamp;
if((ro_session->reserved_secs - used_secs) > 0) {
update_ro_timer(&ro_session->ro_tl,
(ro_session->reserved_secs - used_secs));
return;
} else {
ro_session->event_type = no_more_credit;
}
}
switch(ro_session->event_type) {
case answered:
now = get_current_time_micro();
used_secs = rint(
(now - ro_session->last_event_timestamp) / (float)1000000);
call_time = rint((now - ro_session->start_time) / (float)1000000);
if((used_secs + ro_session->billed) < (call_time)) {
adjustment = call_time - (used_secs + ro_session->billed);
LM_DBG("Making adjustment for Ro interim timer by adding %d "
"seconds\n",
adjustment);
used_secs += adjustment;
}
counter_add(ims_charging_cnts_h.billed_secs, used_secs);
if(ro_session->callid.s != NULL
&& ro_session->ro_session_id.s != NULL) {
LM_DBG("Found a session to re-apply for timing [%.*s] and user "
"is [%.*s]\n",
ro_session->ro_session_id.len,
ro_session->ro_session_id.s,
ro_session->asserted_identity.len,
ro_session->asserted_identity.s);
LM_DBG("Call session has been active for %i seconds. The last "
"reserved secs was [%i] and the last event was [%i "
"seconds] ago\n",
(unsigned int)call_time,
(unsigned int)ro_session->reserved_secs,
(unsigned int)used_secs);
LM_DBG("Call session [p=%p]: we will now make a request for "
"another [%i] of credit with a usage of [%i] seconds "
"from the last bundle.\n",
ro_session,
interim_request_credits /* new reservation request amount */
,
(unsigned int)
used_secs /* charged seconds from previous reservation */);
// Apply for more credit.
//
// The function call will return immediately and we will receive the reply asynchronously via a callback
ro_session->billed += used_secs;
send_ccr_interim(ro_session, (unsigned int)used_secs,
interim_request_credits);
return;
} else {
LM_ERR("Hmmm, the session we have either doesn't have all the "
"data or something else has gone wrong.\n");
/* put the timer back so the call will be killed according to previous timeout. */
ro_session->event_type = unknown_error;
int ret = insert_ro_timer(&ro_session->ro_tl,
(ro_session->reserved_secs - used_secs) / 1000000);
if(ret != 0) {
LM_CRIT("unable to insert timer for Ro Session [%.*s]\n",
ro_session->ro_session_id.len,
ro_session->ro_session_id.s);
} else {
ref_ro_session(ro_session, 1, 0);
return;
}
LM_ERR("Immediately killing call due to unknown error\n");
}
break;
case delayed_delete:
destroy_ro_session(ro_session);
return;
case pending:
/* call is not answered yet. No point asking more credit. Just wait for dialog to progress somehow */
return;
default:
LM_ERR("Diameter call session - event [%d]\n",
ro_session->event_type);
if(ro_session->event_type == no_more_credit)
LM_INFO("Call/session must be ended - no more funds.\n");
else if(ro_session->event_type == unknown_error)
LM_ERR("last event caused an error. We will now tear down this "
"session.\n");
}
counter_inc(ims_charging_cnts_h.killed_calls);
dlgb.lookup_terminate_dlg(ro_session->dlg_h_entry, ro_session->dlg_h_id,
&default_out_of_credit_hdrs);
return;
}