/*! \brief Unit runnable. */ int runnable(struct dthread_t *thread) { for (int i = 0; i < _runnable_cycles; ++i) { // Increase counter pthread_mutex_lock(&_runnable_mx); ++_runnable_i; pthread_mutex_unlock(&_runnable_mx); // Cancellation point if (dt_is_cancelled(thread)) { break; } // Yield sched_yield(); } return 0; }
/*! API: run tests. */ int main(int argc, char *argv[]) { plan(8); // Register service and signal handler struct sigaction sa; sa.sa_handler = interrupt_handle; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGALRM, &sa, NULL); // Interrupt /* Initialize */ srand(time(NULL)); pthread_mutex_init(&_runnable_mx, NULL); pthread_mutex_init(&_destructor_mx, NULL); /* Test 1: Create unit */ dt_unit_t *unit = dt_create(2, &runnable, NULL, NULL); ok(unit != NULL, "dthreads: create unit (size %d)", unit->size); if (unit == NULL) { skip_block(7, "No dthreads unit"); goto skip_all; } /* Test 2: Start tasks. */ _runnable_i = 0; ok(dt_start(unit) == 0, "dthreads: start single task"); /* Test 3: Wait for tasks. */ ok(dt_join(unit) == 0, "dthreads: join threads"); /* Test 4: Compare counter. */ int expected = _runnable_cycles * 2; is_int(expected, _runnable_i, "dthreads: result ok"); /* Test 5: Deinitialize */ dt_delete(&unit); ok(unit == NULL, "dthreads: delete unit"); /* Test 6: Wrong values. */ unit = dt_create(-1, NULL, NULL, NULL); ok(unit == NULL, "dthreads: create with negative count"); /* Test 7: NULL operations crashing. */ int ret = 0; ret += dt_activate(0); ret += dt_cancel(0); ret += dt_compact(0); dt_delete(0); ret += dt_is_cancelled(0); ret += dt_join(0); ret += dt_signalize(0, SIGALRM); ret += dt_start(0); ret += dt_stop(0); ret += dt_unit_lock(0); ret += dt_unit_unlock(0); is_int(-1098, ret, "dthreads: correct values when passed NULL context"); /* Test 8: Thread destructor. */ _destructor_data = 0; unit = dt_create(2, 0, destruct, 0); dt_start(unit); dt_stop(unit); dt_join(unit); is_int(2, _destructor_data, "dthreads: destructor with dt_create_coherent()"); dt_delete(&unit); skip_all: pthread_mutex_destroy(&_runnable_mx); pthread_mutex_destroy(&_destructor_mx); return 0; }
/*! API: run tests. */ static int dt_tests_run(int argc, char *argv[]) { // Register service and signal handler struct sigaction sa; sa.sa_handler = interrupt_handle; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGALRM, &sa, NULL); // Interrupt /* Initialize */ srand(time(NULL)); struct timeval tv; pthread_mutex_init(&_runnable_mx, NULL); /* Test 1: Create unit */ dt_unit_t *unit = dt_test_create(2); ok(unit != 0, "dthreads: create unit (optimal size %d)", unit->size); skip(unit == 0, DT_TEST_COUNT - 1); /* Test 2: Assign a single task. */ ok(dt_test_single(unit), "dthreads: assign single task"); /* Test 3: Start tasks. */ _runnable_i = 0; ok(dt_test_start(unit), "dthreads: start single task"); /* Test 4: Wait for tasks. */ ok(dt_test_join(unit), "dthreads: join threads"); /* Test 5: Compare counter. */ int expected = _runnable_cycles * 1; cmp_ok(_runnable_i, "==", expected, "dthreads: result ok"); /* Test 6: Repurpose threads. */ _runnable_i = 0; ok(dt_test_coherent(unit), "dthreads: repurpose to coherent"); /* Test 7: Restart threads. */ ok(dt_test_start(unit), "dthreads: start coherent unit"); /* Test 8: Repurpose single thread. */ tv.tv_sec = 0; tv.tv_usec = 4000 + rand() % 1000; // 4-5ms note("waiting for %dus to let thread do some work ...", tv.tv_usec); select(0, 0, 0, 0, &tv); ok(dt_test_repurpose(unit, 0), "dthreads: repurpose on-the-fly"); /* Test 9: Cancel blocking thread. */ tv.tv_sec = 0; tv.tv_usec = (250 + rand() % 500) * 1000; // 250-750ms note("waiting for %dms to let thread pretend blocking I/O ...", tv.tv_usec / 1000); select(0, 0, 0, 0, &tv); ok(dt_test_cancel(unit, 0), "dthreads: cancel blocking thread"); /* Test 10: Wait for tasks. */ ok(dt_test_join(unit), "dthreads: join threads"); /* Test 11: Compare counter. */ int expected_lo = _runnable_cycles * (unit->size - 1); cmp_ok(_runnable_i, ">=", expected_lo, "dthreads: result %d is => %d", _runnable_i, expected_lo); /* Test 12: Compare counter #2. */ /*! \note repurpose could trigger next run of the unit if both finished */ int expected_hi = _runnable_cycles * (unit->size + unit->size - 1); cmp_ok(_runnable_i, "<=", expected_hi, "dthreads: result %d is <= %d", _runnable_i, expected_hi); /* Test 13: Reanimate dead threads. */ ok(dt_test_reanimate(unit), "dthreads: reanimate dead threads"); /* Test 14: Deinitialize */ dt_delete(&unit); ok(unit == 0, "dthreads: delete unit"); endskip; /* Test 15: Wrong values. */ unit = dt_create(-1); ok(unit == 0, "dthreads: create with negative count"); unit = dt_create_coherent(dt_optimal_size(), 0, 0); /* Test 16: NULL runnable. */ cmp_ok(dt_start(unit), "==", 0, "dthreads: start with NULL runnable"); /* Test 17: NULL operations crashing. */ int op_count = 14; int expected_min = op_count * -1; // All functions must return -1 at least int ret = 0; lives_ok( { ret += dt_activate(0); // -1 ret += dt_cancel(0); // -1 ret += dt_compact(0); // -1 dt_delete(0); // ret += dt_is_cancelled(0); // 0 ret += dt_join(0); // -1 ret += dt_repurpose(0, 0, 0); // -1 ret += dt_signalize(0, SIGALRM); // -1 ret += dt_start(0); // -1 ret += dt_start_id(0); // -1 ret += dt_stop(0); // -1 ret += dt_stop_id(0); // -1 ret += dt_unit_lock(0); // -1 ret += dt_unit_unlock(0); // -1 }, "dthreads: not crashed while executing functions on NULL context");
int tcp_master(dthread_t *thread) { if (!thread || !thread->data) { return KNOT_EINVAL; } iohandler_t *handler = (iohandler_t *)thread->data; unsigned *iostate = &handler->thread_state[dt_get_id(thread)]; int ret = KNOT_EOK; ref_t *ref = NULL; tcp_context_t tcp; memset(&tcp, 0, sizeof(tcp_context_t)); /* Create big enough memory cushion. */ knot_mm_t mm; mm_ctx_mempool(&mm, 16 * MM_DEFAULT_BLKSIZE); /* Create TCP answering context. */ tcp.server = handler->server; tcp.thread_id = handler->thread_id[dt_get_id(thread)]; tcp.overlay.mm = &mm; /* Prepare structures for bound sockets. */ conf_val_t val = conf_get(conf(), C_SRV, C_LISTEN); fdset_init(&tcp.set, conf_val_count(&val) + CONF_XFERS); /* Create iovec abstraction. */ for (unsigned i = 0; i < 2; ++i) { tcp.iov[i].iov_len = KNOT_WIRE_MAX_PKTSIZE; tcp.iov[i].iov_base = malloc(tcp.iov[i].iov_len); if (tcp.iov[i].iov_base == NULL) { ret = KNOT_ENOMEM; goto finish; } } /* Initialize sweep interval. */ timev_t next_sweep = {0}; time_now(&next_sweep); next_sweep.tv_sec += TCP_SWEEP_INTERVAL; for(;;) { /* Check handler state. */ if (unlikely(*iostate & ServerReload)) { *iostate &= ~ServerReload; /* Cancel client connections. */ for (unsigned i = tcp.client_threshold; i < tcp.set.n; ++i) { close(tcp.set.pfd[i].fd); } ref_release(ref); ref = server_set_ifaces(handler->server, &tcp.set, IO_TCP, tcp.thread_id); if (tcp.set.n == 0) { break; /* Terminate on zero interfaces. */ } tcp.client_threshold = tcp.set.n; } /* Check for cancellation. */ if (dt_is_cancelled(thread)) { break; } /* Serve client requests. */ tcp_wait_for_events(&tcp); /* Sweep inactive clients. */ if (tcp.last_poll_time.tv_sec >= next_sweep.tv_sec) { fdset_sweep(&tcp.set, &tcp_sweep, NULL); time_now(&next_sweep); next_sweep.tv_sec += TCP_SWEEP_INTERVAL; } } finish: free(tcp.iov[0].iov_base); free(tcp.iov[1].iov_base); mp_delete(mm.ctx); fdset_clear(&tcp.set); ref_release(ref); return ret; }