1 /* -*- mode: C; c-file-style: "gnu" -*- */ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 * 22 */ 23 24 #include "expirelist.h" 25 #include "test.h" 26 #include <dbus/dbus-internals.h> 27 #include <dbus/dbus-mainloop.h> 28 #include <dbus/dbus-timeout.h> 29 30 static dbus_bool_t expire_timeout_handler (void *data); 31 32 static void 33 call_timeout_callback (DBusTimeout *timeout, 34 void *data) 35 { 36 /* can return FALSE on OOM but we just let it fire again later */ 37 dbus_timeout_handle (timeout); 38 } 39 40 BusExpireList* 41 bus_expire_list_new (DBusLoop *loop, 42 int expire_after, 43 BusExpireFunc expire_func, 44 void *data) 45 { 46 BusExpireList *list; 47 48 list = dbus_new0 (BusExpireList, 1); 49 if (list == NULL) 50 return NULL; 51 52 list->expire_func = expire_func; 53 list->data = data; 54 list->loop = loop; 55 list->expire_after = expire_after; 56 57 list->timeout = _dbus_timeout_new (100, /* irrelevant */ 58 expire_timeout_handler, 59 list, NULL); 60 if (list->timeout == NULL) 61 goto failed; 62 63 _dbus_timeout_set_enabled (list->timeout, FALSE); 64 65 if (!_dbus_loop_add_timeout (list->loop, 66 list->timeout, 67 call_timeout_callback, NULL, NULL)) 68 goto failed; 69 70 return list; 71 72 failed: 73 if (list->timeout) 74 _dbus_timeout_unref (list->timeout); 75 76 dbus_free (list); 77 78 return NULL; 79 } 80 81 void 82 bus_expire_list_free (BusExpireList *list) 83 { 84 _dbus_assert (list->items == NULL); 85 86 _dbus_loop_remove_timeout (list->loop, list->timeout, 87 call_timeout_callback, NULL); 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 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 expire timeout\n"); 112 } 113 else 114 _dbus_verbose ("No need to disable expire timeout\n"); 115 } 116 117 static int 118 do_expiration_with_current_time (BusExpireList *list, 119 long tv_sec, 120 long tv_usec) 121 { 122 DBusList *link; 123 int next_interval; 124 125 next_interval = -1; 126 127 link = _dbus_list_get_first_link (&list->items); 128 while (link != NULL) 129 { 130 DBusList *next = _dbus_list_get_next_link (&list->items, link); 131 double elapsed; 132 BusExpireItem *item; 133 134 item = link->data; 135 136 elapsed = ELAPSED_MILLISECONDS_SINCE (item->added_tv_sec, 137 item->added_tv_usec, 138 tv_sec, tv_usec); 139 140 if (elapsed >= (double) list->expire_after) 141 { 142 _dbus_verbose ("Expiring an item %p\n", item); 143 144 /* If the expire function fails, we just end up expiring 145 * this item next time we walk through the list. This would 146 * be an indeterminate time normally, so we set up the 147 * next_interval to be "shortly" (just enough to avoid 148 * a busy loop) 149 */ 150 if (!(* list->expire_func) (list, link, list->data)) 151 { 152 next_interval = _dbus_get_oom_wait (); 153 break; 154 } 155 } 156 else 157 { 158 /* We can end the loop, since the connections are in oldest-first order */ 159 next_interval = ((double)list->expire_after) - elapsed; 160 _dbus_verbose ("Item %p expires in %d milliseconds\n", 161 item, next_interval); 162 163 break; 164 } 165 166 link = next; 167 } 168 169 return next_interval; 170 } 171 172 static void 173 bus_expirelist_expire (BusExpireList *list) 174 { 175 int next_interval; 176 177 next_interval = -1; 178 179 if (list->items != NULL) 180 { 181 long tv_sec, tv_usec; 182 183 _dbus_get_current_time (&tv_sec, &tv_usec); 184 185 next_interval = do_expiration_with_current_time (list, tv_sec, tv_usec); 186 } 187 188 bus_expire_timeout_set_interval (list->timeout, next_interval); 189 } 190 191 static dbus_bool_t 192 expire_timeout_handler (void *data) 193 { 194 BusExpireList *list = data; 195 196 _dbus_verbose ("Running %s\n", _DBUS_FUNCTION_NAME); 197 198 /* note that this may remove the timeout */ 199 bus_expirelist_expire (list); 200 201 return TRUE; 202 } 203 204 #ifdef DBUS_BUILD_TESTS 205 206 typedef struct 207 { 208 BusExpireItem item; 209 int expire_count; 210 } TestExpireItem; 211 212 static dbus_bool_t 213 test_expire_func (BusExpireList *list, 214 DBusList *link, 215 void *data) 216 { 217 TestExpireItem *t; 218 219 t = (TestExpireItem*) link->data; 220 221 t->expire_count += 1; 222 223 return TRUE; 224 } 225 226 static void 227 time_add_milliseconds (long *tv_sec, 228 long *tv_usec, 229 int milliseconds) 230 { 231 *tv_sec = *tv_sec + milliseconds / 1000; 232 *tv_usec = *tv_usec + milliseconds * 1000; 233 if (*tv_usec >= 1000000) 234 { 235 *tv_usec -= 1000000; 236 *tv_sec += 1; 237 } 238 } 239 240 dbus_bool_t 241 bus_expire_list_test (const DBusString *test_data_dir) 242 { 243 DBusLoop *loop; 244 BusExpireList *list; 245 long tv_sec, tv_usec; 246 long tv_sec_not_expired, tv_usec_not_expired; 247 long tv_sec_expired, tv_usec_expired; 248 long tv_sec_past, tv_usec_past; 249 TestExpireItem *item; 250 int next_interval; 251 dbus_bool_t result = FALSE; 252 253 254 loop = _dbus_loop_new (); 255 _dbus_assert (loop != NULL); 256 257 #define EXPIRE_AFTER 100 258 259 list = bus_expire_list_new (loop, EXPIRE_AFTER, 260 test_expire_func, NULL); 261 _dbus_assert (list != NULL); 262 263 _dbus_get_current_time (&tv_sec, &tv_usec); 264 265 tv_sec_not_expired = tv_sec; 266 tv_usec_not_expired = tv_usec; 267 time_add_milliseconds (&tv_sec_not_expired, 268 &tv_usec_not_expired, EXPIRE_AFTER - 1); 269 270 tv_sec_expired = tv_sec; 271 tv_usec_expired = tv_usec; 272 time_add_milliseconds (&tv_sec_expired, 273 &tv_usec_expired, EXPIRE_AFTER); 274 275 276 tv_sec_past = tv_sec - 1; 277 tv_usec_past = tv_usec; 278 279 item = dbus_new0 (TestExpireItem, 1); 280 281 if (item == NULL) 282 goto oom; 283 284 item->item.added_tv_sec = tv_sec; 285 item->item.added_tv_usec = tv_usec; 286 if (!_dbus_list_append (&list->items, item)) 287 _dbus_assert_not_reached ("out of memory"); 288 289 next_interval = 290 do_expiration_with_current_time (list, tv_sec_not_expired, 291 tv_usec_not_expired); 292 _dbus_assert (item->expire_count == 0); 293 _dbus_verbose ("next_interval = %d\n", next_interval); 294 _dbus_assert (next_interval == 1); 295 296 next_interval = 297 do_expiration_with_current_time (list, tv_sec_expired, 298 tv_usec_expired); 299 _dbus_assert (item->expire_count == 1); 300 _dbus_verbose ("next_interval = %d\n", next_interval); 301 _dbus_assert (next_interval == -1); 302 303 next_interval = 304 do_expiration_with_current_time (list, tv_sec_past, 305 tv_usec_past); 306 _dbus_assert (item->expire_count == 1); 307 _dbus_verbose ("next_interval = %d\n", next_interval); 308 _dbus_assert (next_interval == 1000 + EXPIRE_AFTER); 309 310 _dbus_list_clear (&list->items); 311 dbus_free (item); 312 313 bus_expire_list_free (list); 314 _dbus_loop_unref (loop); 315 316 result = TRUE; 317 318 oom: 319 return result; 320 } 321 322 #endif /* DBUS_BUILD_TESTS */ 323