Alexandre Lision | 8af73cb | 2013-12-10 14:11:20 -0500 | [diff] [blame] | 1 | /* $Id$ */ |
| 2 | /* |
| 3 | * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) |
| 4 | * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or modify |
| 7 | * it under the terms of the GNU General Public License as published by |
| 8 | * the Free Software Foundation; either version 2 of the License, or |
| 9 | * (at your option) any later version. |
| 10 | * |
| 11 | * This program is distributed in the hope that it will be useful, |
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | * GNU General Public License for more details. |
| 15 | * |
| 16 | * You should have received a copy of the GNU General Public License |
| 17 | * along with this program; if not, write to the Free Software |
| 18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 19 | */ |
| 20 | #include "test.h" |
| 21 | |
| 22 | #if INCLUDE_CONCUR_TEST |
| 23 | |
| 24 | #define THIS_FILE "concur_test.c" |
| 25 | |
| 26 | /****************************************************************************/ |
| 27 | #define WORKER_THREAD_CNT 4 |
| 28 | #define SERVER_THREAD_CNT 4 |
| 29 | #define MAX_SOCK_CLIENTS 80 |
| 30 | |
| 31 | struct stun_test_session |
| 32 | { |
| 33 | pj_stun_config stun_cfg; |
| 34 | |
| 35 | pj_lock_t *lock; |
| 36 | |
| 37 | pj_thread_t *worker_threads[WORKER_THREAD_CNT]; |
| 38 | |
| 39 | pj_sock_t server_sock; |
| 40 | int server_port; |
| 41 | pj_thread_t *server_threads[SERVER_THREAD_CNT]; |
| 42 | pj_event_t *server_event; |
| 43 | |
| 44 | pj_bool_t thread_quit_flag; |
| 45 | |
| 46 | /* Test parameters: */ |
| 47 | struct { |
| 48 | int client_got_response; |
| 49 | |
| 50 | pj_bool_t server_wait_for_event; |
| 51 | pj_bool_t server_drop_request; |
| 52 | int client_sleep_after_start; |
| 53 | int client_sleep_before_destroy; |
| 54 | } param; |
| 55 | }; |
| 56 | |
| 57 | static int server_thread_proc(void *p) |
| 58 | { |
| 59 | struct stun_test_session *test_sess = (struct stun_test_session*)p; |
| 60 | pj_pool_t *pool; |
| 61 | pj_status_t status; |
| 62 | |
| 63 | PJ_LOG(4,(THIS_FILE, "Server thread running")); |
| 64 | |
| 65 | pool = pj_pool_create(test_sess->stun_cfg.pf, "server", 512, 512, NULL); |
| 66 | |
| 67 | while (!test_sess->thread_quit_flag) { |
| 68 | pj_time_val timeout = {0, 10}; |
| 69 | pj_fd_set_t rdset; |
| 70 | int n; |
| 71 | |
| 72 | /* Serve client */ |
| 73 | PJ_FD_ZERO(&rdset); |
| 74 | PJ_FD_SET(test_sess->server_sock, &rdset); |
| 75 | n = pj_sock_select((int)test_sess->server_sock+1, &rdset, |
| 76 | NULL, NULL, &timeout); |
| 77 | if (n==1 && PJ_FD_ISSET(test_sess->server_sock, &rdset)) { |
| 78 | pj_uint8_t pkt[512]; |
| 79 | pj_ssize_t pkt_len; |
| 80 | pj_size_t res_len; |
| 81 | pj_sockaddr client_addr; |
| 82 | int addr_len; |
| 83 | |
| 84 | pj_stun_msg *stun_req, *stun_res; |
| 85 | |
| 86 | pj_pool_reset(pool); |
| 87 | |
| 88 | /* Got query */ |
| 89 | pkt_len = sizeof(pkt); |
| 90 | addr_len = sizeof(client_addr); |
| 91 | status = pj_sock_recvfrom(test_sess->server_sock, pkt, &pkt_len, |
| 92 | 0, &client_addr, &addr_len); |
| 93 | if (status != PJ_SUCCESS) { |
| 94 | continue; |
| 95 | } |
| 96 | |
| 97 | status = pj_stun_msg_decode(pool, pkt, pkt_len, |
| 98 | PJ_STUN_IS_DATAGRAM, |
| 99 | &stun_req, NULL, NULL); |
| 100 | if (status != PJ_SUCCESS) { |
| 101 | PJ_PERROR(1,(THIS_FILE, status, "STUN request decode error")); |
| 102 | continue; |
| 103 | } |
| 104 | |
| 105 | status = pj_stun_msg_create_response(pool, stun_req, |
| 106 | PJ_STUN_SC_BAD_REQUEST, NULL, |
| 107 | &stun_res); |
| 108 | if (status != PJ_SUCCESS) { |
| 109 | PJ_PERROR(1,(THIS_FILE, status, "STUN create response error")); |
| 110 | continue; |
| 111 | } |
| 112 | |
| 113 | status = pj_stun_msg_encode(stun_res, pkt, sizeof(pkt), 0, |
| 114 | NULL, &res_len); |
| 115 | if (status != PJ_SUCCESS) { |
| 116 | PJ_PERROR(1,(THIS_FILE, status, "STUN encode error")); |
| 117 | continue; |
| 118 | } |
| 119 | |
| 120 | /* Ignore request */ |
| 121 | if (test_sess->param.server_drop_request) |
| 122 | continue; |
| 123 | |
| 124 | /* Wait for signal to continue */ |
| 125 | if (test_sess->param.server_wait_for_event) |
| 126 | pj_event_wait(test_sess->server_event); |
| 127 | |
| 128 | pkt_len = res_len; |
| 129 | pj_sock_sendto(test_sess->server_sock, pkt, &pkt_len, 0, |
| 130 | &client_addr, pj_sockaddr_get_len(&client_addr)); |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | pj_pool_release(pool); |
| 135 | |
| 136 | PJ_LOG(4,(THIS_FILE, "Server thread quitting")); |
| 137 | return 0; |
| 138 | } |
| 139 | |
| 140 | static int worker_thread_proc(void *p) |
| 141 | { |
| 142 | struct stun_test_session *test_sess = (struct stun_test_session*)p; |
| 143 | |
| 144 | PJ_LOG(4,(THIS_FILE, "Worker thread running")); |
| 145 | |
| 146 | while (!test_sess->thread_quit_flag) { |
| 147 | pj_time_val timeout = {0, 10}; |
| 148 | pj_timer_heap_poll(test_sess->stun_cfg.timer_heap, NULL); |
| 149 | pj_ioqueue_poll(test_sess->stun_cfg.ioqueue, &timeout); |
| 150 | } |
| 151 | |
| 152 | PJ_LOG(4,(THIS_FILE, "Worker thread quitting")); |
| 153 | return 0; |
| 154 | } |
| 155 | |
| 156 | static pj_bool_t stun_sock_on_status(pj_stun_sock *stun_sock, |
| 157 | pj_stun_sock_op op, |
| 158 | pj_status_t status) |
| 159 | { |
| 160 | struct stun_test_session *test_sess = (struct stun_test_session*)pj_stun_sock_get_user_data(stun_sock); |
| 161 | |
| 162 | PJ_UNUSED_ARG(op); |
| 163 | PJ_UNUSED_ARG(status); |
| 164 | |
| 165 | test_sess->param.client_got_response++; |
| 166 | return PJ_TRUE; |
| 167 | } |
| 168 | |
| 169 | static int stun_destroy_test_session(struct stun_test_session *test_sess) |
| 170 | { |
| 171 | |
| 172 | unsigned i; |
| 173 | pj_stun_sock_cb stun_cb; |
| 174 | pj_status_t status; |
| 175 | pj_stun_sock *stun_sock[MAX_SOCK_CLIENTS]; |
| 176 | |
| 177 | pj_bzero(&stun_cb, sizeof(stun_cb)); |
| 178 | stun_cb.on_status = &stun_sock_on_status; |
| 179 | |
| 180 | pj_event_reset(test_sess->server_event); |
| 181 | |
| 182 | /* Create all clients first */ |
| 183 | for (i=0; i<MAX_SOCK_CLIENTS; ++i) { |
| 184 | char name[10]; |
| 185 | sprintf(name, "stun%02d", i); |
| 186 | status = pj_stun_sock_create(&test_sess->stun_cfg, name, pj_AF_INET(), |
| 187 | &stun_cb, NULL, test_sess, |
| 188 | &stun_sock[i]); |
| 189 | if (status != PJ_SUCCESS) { |
| 190 | PJ_PERROR(1,(THIS_FILE, status, "Error creating stun socket")); |
| 191 | return -10; |
| 192 | } |
| 193 | } |
| 194 | |
| 195 | /* Start resolution */ |
| 196 | for (i=0; i<MAX_SOCK_CLIENTS; ++i) { |
| 197 | pj_str_t server_ip = pj_str("127.0.0.1"); |
| 198 | status = pj_stun_sock_start(stun_sock[i], &server_ip, |
| 199 | (pj_uint16_t)test_sess->server_port, NULL); |
| 200 | if (status != PJ_SUCCESS) { |
| 201 | PJ_PERROR(1,(THIS_FILE, status, "Error starting stun socket")); |
| 202 | return -20; |
| 203 | } |
| 204 | } |
| 205 | |
| 206 | /* settle down */ |
| 207 | pj_thread_sleep(test_sess->param.client_sleep_after_start); |
| 208 | |
| 209 | /* Resume server threads */ |
| 210 | pj_event_set(test_sess->server_event); |
| 211 | |
| 212 | pj_thread_sleep(test_sess->param.client_sleep_before_destroy); |
| 213 | |
| 214 | /* Destroy clients */ |
| 215 | for (i=0; i<MAX_SOCK_CLIENTS; ++i) { |
| 216 | status = pj_stun_sock_destroy(stun_sock[i]); |
| 217 | if (status != PJ_SUCCESS) { |
| 218 | PJ_PERROR(1,(THIS_FILE, status, "Error destroying stun socket")); |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | return 0; |
| 223 | } |
| 224 | |
| 225 | static int stun_destroy_test(void) |
| 226 | { |
| 227 | enum { LOOP = 500 }; |
| 228 | struct stun_test_session test_sess; |
| 229 | pj_sockaddr bind_addr; |
| 230 | int addr_len; |
| 231 | pj_caching_pool cp; |
| 232 | pj_pool_t *pool; |
| 233 | unsigned i; |
| 234 | pj_status_t status; |
| 235 | int rc = 0; |
| 236 | |
| 237 | PJ_LOG(3,(THIS_FILE, " STUN destroy concurrency test")); |
| 238 | |
| 239 | pj_bzero(&test_sess, sizeof(test_sess)); |
| 240 | |
| 241 | pj_caching_pool_init(&cp, NULL, 0); |
| 242 | pool = pj_pool_create(&cp.factory, "testsess", 512, 512, NULL); |
| 243 | |
| 244 | pj_stun_config_init(&test_sess.stun_cfg, &cp.factory, 0, NULL, NULL); |
| 245 | |
| 246 | status = pj_timer_heap_create(pool, 1023, &test_sess.stun_cfg.timer_heap); |
| 247 | pj_assert(status == PJ_SUCCESS); |
| 248 | |
| 249 | status = pj_lock_create_recursive_mutex(pool, NULL, &test_sess.lock); |
| 250 | pj_assert(status == PJ_SUCCESS); |
| 251 | |
| 252 | pj_timer_heap_set_lock(test_sess.stun_cfg.timer_heap, test_sess.lock, PJ_TRUE); |
| 253 | pj_assert(status == PJ_SUCCESS); |
| 254 | |
| 255 | status = pj_ioqueue_create(pool, 512, &test_sess.stun_cfg.ioqueue); |
| 256 | pj_assert(status == PJ_SUCCESS); |
| 257 | |
| 258 | pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &test_sess.server_sock); |
| 259 | pj_sockaddr_init(pj_AF_INET(), &bind_addr, NULL, 0); |
| 260 | status = pj_sock_bind(test_sess.server_sock, &bind_addr, pj_sockaddr_get_len(&bind_addr)); |
| 261 | pj_assert(status == PJ_SUCCESS); |
| 262 | |
| 263 | addr_len = sizeof(bind_addr); |
| 264 | status = pj_sock_getsockname(test_sess.server_sock, &bind_addr, &addr_len); |
| 265 | pj_assert(status == PJ_SUCCESS); |
| 266 | |
| 267 | test_sess.server_port = pj_sockaddr_get_port(&bind_addr); |
| 268 | |
| 269 | status = pj_event_create(pool, NULL, PJ_TRUE, PJ_FALSE, &test_sess.server_event); |
| 270 | pj_assert(status == PJ_SUCCESS); |
| 271 | |
| 272 | for (i=0; i<SERVER_THREAD_CNT; ++i) { |
| 273 | status = pj_thread_create(pool, NULL, |
| 274 | &server_thread_proc, &test_sess, |
| 275 | 0, 0, &test_sess.server_threads[i]); |
| 276 | pj_assert(status == PJ_SUCCESS); |
| 277 | } |
| 278 | |
| 279 | for (i=0; i<WORKER_THREAD_CNT; ++i) { |
| 280 | status = pj_thread_create(pool, NULL, |
| 281 | &worker_thread_proc, &test_sess, |
| 282 | 0, 0, &test_sess.worker_threads[i]); |
| 283 | pj_assert(status == PJ_SUCCESS); |
| 284 | } |
| 285 | |
| 286 | /* Test 1: Main thread calls destroy while callback is processing response */ |
| 287 | PJ_LOG(3,(THIS_FILE, " Destroy in main thread while callback is running")); |
| 288 | for (i=0; i<LOOP; ++i) { |
| 289 | int sleep = pj_rand() % 5; |
| 290 | |
| 291 | PJ_LOG(3,(THIS_FILE, " Try %-3d of %d", i+1, LOOP)); |
| 292 | |
| 293 | /* Test 1: destroy at the same time when receiving response */ |
| 294 | pj_bzero(&test_sess.param, sizeof(test_sess.param)); |
| 295 | test_sess.param.client_sleep_after_start = 20; |
| 296 | test_sess.param.client_sleep_before_destroy = sleep; |
| 297 | test_sess.param.server_wait_for_event = PJ_TRUE; |
| 298 | stun_destroy_test_session(&test_sess); |
| 299 | PJ_LOG(3,(THIS_FILE, |
| 300 | " stun test a: sleep delay:%d: clients with response: %d", |
| 301 | sleep, test_sess.param.client_got_response)); |
| 302 | |
| 303 | /* Test 2: destroy at the same time with STUN retransmit timer */ |
| 304 | test_sess.param.server_drop_request = PJ_TRUE; |
| 305 | test_sess.param.client_sleep_after_start = 0; |
| 306 | test_sess.param.client_sleep_before_destroy = PJ_STUN_RTO_VALUE; |
| 307 | test_sess.param.server_wait_for_event = PJ_FALSE; |
| 308 | stun_destroy_test_session(&test_sess); |
| 309 | PJ_LOG(3,(THIS_FILE, " stun test b: retransmit concurrency")); |
| 310 | |
| 311 | /* Test 3: destroy at the same time with receiving response |
| 312 | * AND STUN retransmit timer */ |
| 313 | test_sess.param.client_got_response = 0; |
| 314 | test_sess.param.server_drop_request = PJ_FALSE; |
| 315 | test_sess.param.client_sleep_after_start = PJ_STUN_RTO_VALUE; |
| 316 | test_sess.param.client_sleep_before_destroy = 0; |
| 317 | test_sess.param.server_wait_for_event = PJ_TRUE; |
| 318 | stun_destroy_test_session(&test_sess); |
| 319 | PJ_LOG(3,(THIS_FILE, |
| 320 | " stun test c: clients with response: %d", |
| 321 | test_sess.param.client_got_response)); |
| 322 | |
| 323 | pj_thread_sleep(10); |
| 324 | |
| 325 | ice_one_conc_test(&test_sess.stun_cfg, PJ_FALSE); |
| 326 | |
| 327 | pj_thread_sleep(10); |
| 328 | } |
| 329 | |
| 330 | /* Avoid compiler warning */ |
| 331 | goto on_return; |
| 332 | |
| 333 | |
| 334 | on_return: |
| 335 | test_sess.thread_quit_flag = PJ_TRUE; |
| 336 | |
| 337 | for (i=0; i<SERVER_THREAD_CNT; ++i) { |
| 338 | pj_thread_join(test_sess.server_threads[i]); |
| 339 | } |
| 340 | |
| 341 | for (i=0; i<WORKER_THREAD_CNT; ++i) { |
| 342 | pj_thread_join(test_sess.worker_threads[i]); |
| 343 | } |
| 344 | |
| 345 | pj_event_destroy(test_sess.server_event); |
| 346 | pj_sock_close(test_sess.server_sock); |
| 347 | pj_ioqueue_destroy(test_sess.stun_cfg.ioqueue); |
| 348 | pj_timer_heap_destroy(test_sess.stun_cfg.timer_heap); |
| 349 | |
| 350 | pj_pool_release(pool); |
| 351 | pj_caching_pool_destroy(&cp); |
| 352 | |
| 353 | PJ_LOG(3,(THIS_FILE, " Done. rc=%d", rc)); |
| 354 | return rc; |
| 355 | } |
| 356 | |
| 357 | |
| 358 | int concur_test(void) |
| 359 | { |
| 360 | int rc = 0; |
| 361 | |
| 362 | rc += stun_destroy_test(); |
| 363 | |
| 364 | return 0; |
| 365 | } |
| 366 | |
| 367 | #endif /* INCLUDE_CONCUR_TEST */ |