1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ 2 /* expirelist.c List of items that expire 3 * 4 * Copyright (C) 2003 Red Hat, Inc. 5 * 6 * Licensed under the Academic Free License version 2.1 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 2 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 21 * 22 */ 23 24 #include <config.h> 25 #include "expirelist.h" 26 #include "test.h" 27 #include <dbus/dbus-internals.h> 28 #include <dbus/dbus-mainloop.h> 29 #include <dbus/dbus-timeout.h> 30 31 struct BusExpireList 32 { 33 DBusList *items; /**< List of BusExpireItem */ 34 DBusTimeout *timeout; 35 DBusLoop *loop; 36 BusExpireFunc expire_func; 37 void *data; 38 int expire_after; /**< Expire after milliseconds (thousandths) */ 39 }; 40 41 static dbus_bool_t expire_timeout_handler (void *data); 42 43 static void 44 call_timeout_callback (DBusTimeout *timeout, 45 void *data) 46 { 47 /* can return FALSE on OOM but we just let it fire again later */ 48 dbus_timeout_handle (timeout); 49 } 50 51 BusExpireList* 52 bus_expire_list_new (DBusLoop *loop, 53 int expire_after, 54 BusExpireFunc expire_func, 55 void *data) 56 { 57 BusExpireList *list; 58 59 list = dbus_new0 (BusExpireList, 1); 60 if (list == NULL) 61 return NULL; 62 63 list->expire_func = expire_func; 64 list->data = data; 65 list->loop = loop; 66 list->expire_after = expire_after; 67 68 list->timeout = _dbus_timeout_new (100, /* irrelevant */ 69 expire_timeout_handler, 70 list, NULL); 71 if (list->timeout == NULL) 72 goto failed; 73 74 _dbus_timeout_set_enabled (list->timeout, FALSE); 75 76 if (!_dbus_loop_add_timeout (list->loop, 77 list->timeout, 78 call_timeout_callback, NULL, NULL)) 79 goto failed; 80 81 return list; 82 83 failed: 84 if (list->timeout) 85 _dbus_timeout_unref (list->timeout); 86 87 dbus_free (list); 88 89 return NULL; 90 } 91 92 void 93 bus_expire_list_free (BusExpireList *list) 94 { 95 _dbus_assert (list->items == NULL); 96 97 _dbus_loop_remove_timeout (list->loop, list->timeout, 98 call_timeout_callback, NULL); 99 100 _dbus_timeout_unref (list->timeout); 101 102 dbus_free (list); 103 } 104 105 void 106 bus_expire_timeout_set_interval (DBusTimeout *timeout, 107 int next_interval) 108 { 109 if (next_interval >= 0) 110 { 111 _dbus_timeout_set_interval (timeout, 112 next_interval); 113 _dbus_timeout_set_enabled (timeout, TRUE); 114 115 _dbus_verbose ("Enabled an expire timeout with interval %d\n", 116 next_interval); 117 } 118 else if (dbus_timeout_get_enabled (timeout)) 119 { 120 _dbus_timeout_set_enabled (timeout, FALSE); 121 122 _dbus_verbose ("Disabled an expire timeout\n"); 123 } 124 else 125 _dbus_verbose ("No need to disable this expire timeout\n"); 126 } 127 128 void 129 bus_expire_list_recheck_immediately (BusExpireList *list) 130 { 131 _dbus_verbose ("setting interval on expire list to 0 for immediate recheck\n"); 132 133 bus_expire_timeout_set_interval (list->timeout, 0); 134 } 135 136 static int 137 do_expiration_with_current_time (BusExpireList *list, 138 long tv_sec, 139 long tv_usec) 140 { 141 DBusList *link; 142 int next_interval, min_wait_time, items_to_expire; 143 144 next_interval = -1; 145 min_wait_time = 3600 * 1000; /* this is reset anyway if used */ 146 items_to_expire = 0; 147 148 link = _dbus_list_get_first_link (&list->items); 149 while (link != NULL) 150 { 151 DBusList *next = _dbus_list_get_next_link (&list->items, link); 152 double elapsed; 153 BusExpireItem *item; 154 155 item = link->data; 156 157 elapsed = ELAPSED_MILLISECONDS_SINCE (item->added_tv_sec, 158 item->added_tv_usec, 159 tv_sec, tv_usec); 160 161 if (((item->added_tv_sec == 0) && (item->added_tv_usec == 0)) || 162 ((list->expire_after > 0) && (elapsed >= (double) list->expire_after))) 163 { 164 _dbus_verbose ("Expiring an item %p\n", item); 165 166 /* If the expire function fails, we just end up expiring 167 * this item next time we walk through the list. This would 168 * be an indeterminate time normally, so we set up the 169 * next_interval to be "shortly" (just enough to avoid 170 * a busy loop) 171 */ 172 if (!(* list->expire_func) (list, link, list->data)) 173 { 174 next_interval = _dbus_get_oom_wait (); 175 break; 176 } 177 } 178 else if (list->expire_after > 0) 179 { 180 double to_wait; 181 182 items_to_expire = 1; 183 to_wait = (double) list->expire_after - elapsed; 184 if (min_wait_time > to_wait) 185 min_wait_time = to_wait; 186 } 187 188 link = next; 189 } 190 191 if (next_interval < 0 && items_to_expire) 192 next_interval = min_wait_time; 193 194 return next_interval; 195 } 196 197 static void 198 bus_expirelist_expire (BusExpireList *list) 199 { 200 int next_interval; 201 202 next_interval = -1; 203 204 if (list->items != NULL) 205 { 206 long tv_sec, tv_usec; 207 208 _dbus_get_current_time (&tv_sec, &tv_usec); 209 210 next_interval = do_expiration_with_current_time (list, tv_sec, tv_usec); 211 } 212 213 bus_expire_timeout_set_interval (list->timeout, next_interval); 214 } 215 216 static dbus_bool_t 217 expire_timeout_handler (void *data) 218 { 219 BusExpireList *list = data; 220 221 _dbus_verbose ("Running\n"); 222 223 /* note that this may remove the timeout */ 224 bus_expirelist_expire (list); 225 226 return TRUE; 227 } 228 229 void 230 bus_expire_list_remove_link (BusExpireList *list, 231 DBusList *link) 232 { 233 _dbus_list_remove_link (&list->items, link); 234 } 235 236 dbus_bool_t 237 bus_expire_list_remove (BusExpireList *list, 238 BusExpireItem *item) 239 { 240 return _dbus_list_remove (&list->items, item); 241 } 242 243 void 244 bus_expire_list_unlink (BusExpireList *list, 245 DBusList *link) 246 { 247 _dbus_list_unlink (&list->items, link); 248 } 249 250 dbus_bool_t 251 bus_expire_list_add (BusExpireList *list, 252 BusExpireItem *item) 253 { 254 dbus_bool_t ret; 255 256 ret = _dbus_list_prepend (&list->items, item); 257 if (ret && !dbus_timeout_get_enabled (list->timeout)) 258 bus_expire_timeout_set_interval (list->timeout, 0); 259 260 return ret; 261 } 262 263 void 264 bus_expire_list_add_link (BusExpireList *list, 265 DBusList *link) 266 { 267 _dbus_assert (link->data != NULL); 268 269 _dbus_list_prepend_link (&list->items, link); 270 271 if (!dbus_timeout_get_enabled (list->timeout)) 272 bus_expire_timeout_set_interval (list->timeout, 0); 273 } 274 275 DBusList* 276 bus_expire_list_get_first_link (BusExpireList *list) 277 { 278 return _dbus_list_get_first_link (&list->items); 279 } 280 281 DBusList* 282 bus_expire_list_get_next_link (BusExpireList *list, 283 DBusList *link) 284 { 285 return _dbus_list_get_next_link (&list->items, link); 286 } 287 288 dbus_bool_t 289 bus_expire_list_contains_item (BusExpireList *list, 290 BusExpireItem *item) 291 { 292 return _dbus_list_find_last (&list->items, item) != NULL; 293 } 294 295 #ifdef DBUS_BUILD_TESTS 296 297 typedef struct 298 { 299 BusExpireItem item; 300 int expire_count; 301 } TestExpireItem; 302 303 static dbus_bool_t 304 test_expire_func (BusExpireList *list, 305 DBusList *link, 306 void *data) 307 { 308 TestExpireItem *t; 309 310 t = (TestExpireItem*) link->data; 311 312 t->expire_count += 1; 313 314 return TRUE; 315 } 316 317 static void 318 time_add_milliseconds (long *tv_sec, 319 long *tv_usec, 320 int milliseconds) 321 { 322 *tv_sec = *tv_sec + milliseconds / 1000; 323 *tv_usec = *tv_usec + milliseconds * 1000; 324 if (*tv_usec >= 1000000) 325 { 326 *tv_usec -= 1000000; 327 *tv_sec += 1; 328 } 329 } 330 331 dbus_bool_t 332 bus_expire_list_test (const DBusString *test_data_dir) 333 { 334 DBusLoop *loop; 335 BusExpireList *list; 336 long tv_sec, tv_usec; 337 long tv_sec_not_expired, tv_usec_not_expired; 338 long tv_sec_expired, tv_usec_expired; 339 long tv_sec_past, tv_usec_past; 340 TestExpireItem *item; 341 int next_interval; 342 dbus_bool_t result = FALSE; 343 344 345 loop = _dbus_loop_new (); 346 _dbus_assert (loop != NULL); 347 348 #define EXPIRE_AFTER 100 349 350 list = bus_expire_list_new (loop, EXPIRE_AFTER, 351 test_expire_func, NULL); 352 _dbus_assert (list != NULL); 353 354 _dbus_get_current_time (&tv_sec, &tv_usec); 355 356 tv_sec_not_expired = tv_sec; 357 tv_usec_not_expired = tv_usec; 358 time_add_milliseconds (&tv_sec_not_expired, 359 &tv_usec_not_expired, EXPIRE_AFTER - 1); 360 361 tv_sec_expired = tv_sec; 362 tv_usec_expired = tv_usec; 363 time_add_milliseconds (&tv_sec_expired, 364 &tv_usec_expired, EXPIRE_AFTER); 365 366 367 tv_sec_past = tv_sec - 1; 368 tv_usec_past = tv_usec; 369 370 item = dbus_new0 (TestExpireItem, 1); 371 372 if (item == NULL) 373 goto oom; 374 375 item->item.added_tv_sec = tv_sec; 376 item->item.added_tv_usec = tv_usec; 377 if (!bus_expire_list_add (list, &item->item)) 378 _dbus_assert_not_reached ("out of memory"); 379 380 next_interval = 381 do_expiration_with_current_time (list, tv_sec_not_expired, 382 tv_usec_not_expired); 383 _dbus_assert (item->expire_count == 0); 384 _dbus_verbose ("next_interval = %d\n", next_interval); 385 _dbus_assert (next_interval == 1); 386 387 next_interval = 388 do_expiration_with_current_time (list, tv_sec_expired, 389 tv_usec_expired); 390 _dbus_assert (item->expire_count == 1); 391 _dbus_verbose ("next_interval = %d\n", next_interval); 392 _dbus_assert (next_interval == -1); 393 394 next_interval = 395 do_expiration_with_current_time (list, tv_sec_past, 396 tv_usec_past); 397 _dbus_assert (item->expire_count == 1); 398 _dbus_verbose ("next_interval = %d\n", next_interval); 399 _dbus_assert (next_interval == 1000 + EXPIRE_AFTER); 400 401 bus_expire_list_remove (list, &item->item); 402 dbus_free (item); 403 404 bus_expire_list_free (list); 405 _dbus_loop_unref (loop); 406 407 result = TRUE; 408 409 oom: 410 return result; 411 } 412 413 #endif /* DBUS_BUILD_TESTS */ 414