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 BusExpireList* 44 bus_expire_list_new (DBusLoop *loop, 45 int expire_after, 46 BusExpireFunc expire_func, 47 void *data) 48 { 49 BusExpireList *list; 50 51 list = dbus_new0 (BusExpireList, 1); 52 if (list == NULL) 53 return NULL; 54 55 list->expire_func = expire_func; 56 list->data = data; 57 list->loop = loop; 58 list->expire_after = expire_after; 59 60 list->timeout = _dbus_timeout_new (100, /* irrelevant */ 61 expire_timeout_handler, 62 list, NULL); 63 if (list->timeout == NULL) 64 goto failed; 65 66 _dbus_timeout_set_enabled (list->timeout, FALSE); 67 68 if (!_dbus_loop_add_timeout (list->loop, list->timeout)) 69 goto failed; 70 71 return list; 72 73 failed: 74 if (list->timeout) 75 _dbus_timeout_unref (list->timeout); 76 77 dbus_free (list); 78 79 return NULL; 80 } 81 82 void 83 bus_expire_list_free (BusExpireList *list) 84 { 85 _dbus_assert (list->items == NULL); 86 87 _dbus_loop_remove_timeout (list->loop, list->timeout); 88 89 _dbus_timeout_unref (list->timeout); 90 91 dbus_free (list); 92 } 93 94 void 95 bus_expire_timeout_set_interval (DBusTimeout *timeout, 96 int next_interval) 97 { 98 if (next_interval >= 0) 99 { 100 _dbus_timeout_set_interval (timeout, 101 next_interval); 102 _dbus_timeout_set_enabled (timeout, TRUE); 103 104 _dbus_verbose ("Enabled an expire timeout with interval %d\n", 105 next_interval); 106 } 107 else if (dbus_timeout_get_enabled (timeout)) 108 { 109 _dbus_timeout_set_enabled (timeout, FALSE); 110 111 _dbus_verbose ("Disabled an expire timeout\n"); 112 } 113 else 114 _dbus_verbose ("No need to disable this expire timeout\n"); 115 } 116 117 void 118 bus_expire_list_recheck_immediately (BusExpireList *list) 119 { 120 _dbus_verbose ("setting interval on expire list to 0 for immediate recheck\n"); 121 122 bus_expire_timeout_set_interval (list->timeout, 0); 123 } 124 125 static int 126 do_expiration_with_monotonic_time (BusExpireList *list, 127 long tv_sec, 128 long tv_usec) 129 { 130 DBusList *link; 131 int next_interval, min_wait_time, items_to_expire; 132 133 next_interval = -1; 134 min_wait_time = 3600 * 1000; /* this is reset anyway if used */ 135 items_to_expire = 0; 136 137 link = _dbus_list_get_first_link (&list->items); 138 while (link != NULL) 139 { 140 DBusList *next = _dbus_list_get_next_link (&list->items, link); 141 double elapsed; 142 BusExpireItem *item; 143 144 item = link->data; 145 146 elapsed = ELAPSED_MILLISECONDS_SINCE (item->added_tv_sec, 147 item->added_tv_usec, 148 tv_sec, tv_usec); 149 150 if (((item->added_tv_sec == 0) && (item->added_tv_usec == 0)) || 151 ((list->expire_after > 0) && (elapsed >= (double) list->expire_after))) 152 { 153 _dbus_verbose ("Expiring an item %p\n", item); 154 155 /* If the expire function fails, we just end up expiring 156 * this item next time we walk through the list. This would 157 * be an indeterminate time normally, so we set up the 158 * next_interval to be "shortly" (just enough to avoid 159 * a busy loop) 160 */ 161 if (!(* list->expire_func) (list, link, list->data)) 162 { 163 next_interval = _dbus_get_oom_wait (); 164 break; 165 } 166 } 167 else if (list->expire_after > 0) 168 { 169 double to_wait; 170 171 items_to_expire = 1; 172 to_wait = (double) list->expire_after - elapsed; 173 if (min_wait_time > to_wait) 174 min_wait_time = to_wait; 175 } 176 177 link = next; 178 } 179 180 if (next_interval < 0 && items_to_expire) 181 next_interval = min_wait_time; 182 183 return next_interval; 184 } 185 186 static void 187 bus_expirelist_expire (BusExpireList *list) 188 { 189 int next_interval; 190 191 next_interval = -1; 192 193 if (list->items != NULL) 194 { 195 long tv_sec, tv_usec; 196 197 _dbus_get_monotonic_time (&tv_sec, &tv_usec); 198 199 next_interval = do_expiration_with_monotonic_time (list, tv_sec, tv_usec); 200 } 201 202 bus_expire_timeout_set_interval (list->timeout, next_interval); 203 } 204 205 static dbus_bool_t 206 expire_timeout_handler (void *data) 207 { 208 BusExpireList *list = data; 209 210 _dbus_verbose ("Running\n"); 211 212 /* note that this may remove the timeout */ 213 bus_expirelist_expire (list); 214 215 return TRUE; 216 } 217 218 void 219 bus_expire_list_remove_link (BusExpireList *list, 220 DBusList *link) 221 { 222 _dbus_list_remove_link (&list->items, link); 223 } 224 225 dbus_bool_t 226 bus_expire_list_remove (BusExpireList *list, 227 BusExpireItem *item) 228 { 229 return _dbus_list_remove (&list->items, item); 230 } 231 232 void 233 bus_expire_list_unlink (BusExpireList *list, 234 DBusList *link) 235 { 236 _dbus_list_unlink (&list->items, link); 237 } 238 239 dbus_bool_t 240 bus_expire_list_add (BusExpireList *list, 241 BusExpireItem *item) 242 { 243 dbus_bool_t ret; 244 245 ret = _dbus_list_prepend (&list->items, item); 246 if (ret && !dbus_timeout_get_enabled (list->timeout)) 247 bus_expire_timeout_set_interval (list->timeout, 0); 248 249 return ret; 250 } 251 252 void 253 bus_expire_list_add_link (BusExpireList *list, 254 DBusList *link) 255 { 256 _dbus_assert (link->data != NULL); 257 258 _dbus_list_prepend_link (&list->items, link); 259 260 if (!dbus_timeout_get_enabled (list->timeout)) 261 bus_expire_timeout_set_interval (list->timeout, 0); 262 } 263 264 DBusList* 265 bus_expire_list_get_first_link (BusExpireList *list) 266 { 267 return _dbus_list_get_first_link (&list->items); 268 } 269 270 DBusList* 271 bus_expire_list_get_next_link (BusExpireList *list, 272 DBusList *link) 273 { 274 return _dbus_list_get_next_link (&list->items, link); 275 } 276 277 dbus_bool_t 278 bus_expire_list_contains_item (BusExpireList *list, 279 BusExpireItem *item) 280 { 281 return _dbus_list_find_last (&list->items, item) != NULL; 282 } 283 284 #ifdef DBUS_BUILD_TESTS 285 286 typedef struct 287 { 288 BusExpireItem item; 289 int expire_count; 290 } TestExpireItem; 291 292 static dbus_bool_t 293 test_expire_func (BusExpireList *list, 294 DBusList *link, 295 void *data) 296 { 297 TestExpireItem *t; 298 299 t = (TestExpireItem*) link->data; 300 301 t->expire_count += 1; 302 303 return TRUE; 304 } 305 306 static void 307 time_add_milliseconds (long *tv_sec, 308 long *tv_usec, 309 int milliseconds) 310 { 311 *tv_sec = *tv_sec + milliseconds / 1000; 312 *tv_usec = *tv_usec + milliseconds * 1000; 313 if (*tv_usec >= 1000000) 314 { 315 *tv_usec -= 1000000; 316 *tv_sec += 1; 317 } 318 } 319 320 dbus_bool_t 321 bus_expire_list_test (const DBusString *test_data_dir) 322 { 323 DBusLoop *loop; 324 BusExpireList *list; 325 long tv_sec, tv_usec; 326 long tv_sec_not_expired, tv_usec_not_expired; 327 long tv_sec_expired, tv_usec_expired; 328 long tv_sec_past, tv_usec_past; 329 TestExpireItem *item; 330 int next_interval; 331 dbus_bool_t result = FALSE; 332 333 334 loop = _dbus_loop_new (); 335 _dbus_assert (loop != NULL); 336 337 #define EXPIRE_AFTER 100 338 339 list = bus_expire_list_new (loop, EXPIRE_AFTER, 340 test_expire_func, NULL); 341 _dbus_assert (list != NULL); 342 343 _dbus_get_monotonic_time (&tv_sec, &tv_usec); 344 345 tv_sec_not_expired = tv_sec; 346 tv_usec_not_expired = tv_usec; 347 time_add_milliseconds (&tv_sec_not_expired, 348 &tv_usec_not_expired, EXPIRE_AFTER - 1); 349 350 tv_sec_expired = tv_sec; 351 tv_usec_expired = tv_usec; 352 time_add_milliseconds (&tv_sec_expired, 353 &tv_usec_expired, EXPIRE_AFTER); 354 355 356 tv_sec_past = tv_sec - 1; 357 tv_usec_past = tv_usec; 358 359 item = dbus_new0 (TestExpireItem, 1); 360 361 if (item == NULL) 362 goto oom; 363 364 item->item.added_tv_sec = tv_sec; 365 item->item.added_tv_usec = tv_usec; 366 if (!bus_expire_list_add (list, &item->item)) 367 _dbus_assert_not_reached ("out of memory"); 368 369 next_interval = 370 do_expiration_with_monotonic_time (list, tv_sec_not_expired, 371 tv_usec_not_expired); 372 _dbus_assert (item->expire_count == 0); 373 _dbus_verbose ("next_interval = %d\n", next_interval); 374 _dbus_assert (next_interval == 1); 375 376 next_interval = 377 do_expiration_with_monotonic_time (list, tv_sec_expired, 378 tv_usec_expired); 379 _dbus_assert (item->expire_count == 1); 380 _dbus_verbose ("next_interval = %d\n", next_interval); 381 _dbus_assert (next_interval == -1); 382 383 next_interval = 384 do_expiration_with_monotonic_time (list, tv_sec_past, 385 tv_usec_past); 386 _dbus_assert (item->expire_count == 1); 387 _dbus_verbose ("next_interval = %d\n", next_interval); 388 _dbus_assert (next_interval == 1000 + EXPIRE_AFTER); 389 390 bus_expire_list_remove (list, &item->item); 391 dbus_free (item); 392 393 bus_expire_list_free (list); 394 _dbus_loop_unref (loop); 395 396 result = TRUE; 397 398 oom: 399 return result; 400 } 401 402 #endif /* DBUS_BUILD_TESTS */ 403