summaryrefslogtreecommitdiffstats
path: root/lib/pthreadpool/tests.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pthreadpool/tests.c')
-rw-r--r--lib/pthreadpool/tests.c517
1 files changed, 517 insertions, 0 deletions
diff --git a/lib/pthreadpool/tests.c b/lib/pthreadpool/tests.c
new file mode 100644
index 0000000..08cb59e
--- /dev/null
+++ b/lib/pthreadpool/tests.c
@@ -0,0 +1,517 @@
+#include <stdio.h>
+#include <string.h>
+#include <poll.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include "pthreadpool_pipe.h"
+#include "pthreadpool_tevent.h"
+
+static int test_init(void)
+{
+ struct pthreadpool_pipe *p;
+ int ret;
+
+ ret = pthreadpool_pipe_init(1, &p);
+ if (ret != 0) {
+ fprintf(stderr, "pthreadpool_pipe_init failed: %s\n",
+ strerror(ret));
+ return -1;
+ }
+ ret = pthreadpool_pipe_destroy(p);
+ if (ret != 0) {
+ fprintf(stderr, "pthreadpool_pipe_destroy failed: %s\n",
+ strerror(ret));
+ return -1;
+ }
+ return 0;
+}
+
+static void test_sleep(void *ptr)
+{
+ int *ptimeout = (int *)ptr;
+ int ret;
+ ret = poll(NULL, 0, *ptimeout);
+ if (ret != 0) {
+ fprintf(stderr, "poll returned %d (%s)\n",
+ ret, strerror(errno));
+ }
+}
+
+static int test_jobs(int num_threads, int num_jobs)
+{
+ char *finished;
+ struct pthreadpool_pipe *p;
+ int timeout = 1;
+ int i, ret;
+
+ finished = (char *)calloc(1, num_jobs);
+ if (finished == NULL) {
+ fprintf(stderr, "calloc failed\n");
+ return -1;
+ }
+
+ ret = pthreadpool_pipe_init(num_threads, &p);
+ if (ret != 0) {
+ fprintf(stderr, "pthreadpool_pipe_init failed: %s\n",
+ strerror(ret));
+ free(finished);
+ return -1;
+ }
+
+ for (i=0; i<num_jobs; i++) {
+ ret = pthreadpool_pipe_add_job(p, i, test_sleep, &timeout);
+ if (ret != 0) {
+ fprintf(stderr, "pthreadpool_pipe_add_job failed: "
+ "%s\n", strerror(ret));
+ free(finished);
+ return -1;
+ }
+ }
+
+ for (i=0; i<num_jobs; i++) {
+ int jobid = -1;
+ ret = pthreadpool_pipe_finished_jobs(p, &jobid, 1);
+ if (ret < 0) {
+ fprintf(stderr, "pthreadpool_pipe_finished_jobs "
+ "failed: %s\n", strerror(-ret));
+ free(finished);
+ return -1;
+ }
+ if ((ret != 1) || (jobid >= num_jobs)) {
+ fprintf(stderr, "invalid job number %d\n", jobid);
+ free(finished);
+ return -1;
+ }
+ finished[jobid] += 1;
+ }
+
+ for (i=0; i<num_jobs; i++) {
+ if (finished[i] != 1) {
+ fprintf(stderr, "finished[%d] = %d\n",
+ i, finished[i]);
+ free(finished);
+ return -1;
+ }
+ }
+
+ ret = pthreadpool_pipe_destroy(p);
+ if (ret != 0) {
+ fprintf(stderr, "pthreadpool_pipe_destroy failed: %s\n",
+ strerror(ret));
+ free(finished);
+ return -1;
+ }
+
+ free(finished);
+ return 0;
+}
+
+static int test_busydestroy(void)
+{
+ struct pthreadpool_pipe *p;
+ int timeout = 50;
+ struct pollfd pfd;
+ int ret, jobid;
+
+ ret = pthreadpool_pipe_init(1, &p);
+ if (ret != 0) {
+ fprintf(stderr, "pthreadpool_pipe_init failed: %s\n",
+ strerror(ret));
+ return -1;
+ }
+ ret = pthreadpool_pipe_add_job(p, 1, test_sleep, &timeout);
+ if (ret != 0) {
+ fprintf(stderr, "pthreadpool_pipe_add_job failed: %s\n",
+ strerror(ret));
+ return -1;
+ }
+ ret = pthreadpool_pipe_destroy(p);
+ if (ret != EBUSY) {
+ fprintf(stderr, "Could destroy a busy pool\n");
+ return -1;
+ }
+
+ pfd.fd = pthreadpool_pipe_signal_fd(p);
+ pfd.events = POLLIN|POLLERR;
+
+ do {
+ ret = poll(&pfd, 1, -1);
+ } while ((ret == -1) && (errno == EINTR));
+
+ ret = pthreadpool_pipe_finished_jobs(p, &jobid, 1);
+ if (ret < 0) {
+ fprintf(stderr, "pthreadpool_pipe_finished_jobs failed: %s\n",
+ strerror(-ret));
+ return -1;
+ }
+
+ ret = pthreadpool_pipe_destroy(p);
+ if (ret != 0) {
+ fprintf(stderr, "pthreadpool_pipe_destroy failed: %s\n",
+ strerror(ret));
+ return -1;
+ }
+ return 0;
+}
+
+static int test_fork(void)
+{
+ struct pthreadpool_pipe *p;
+ pid_t child, waited;
+ int status, ret;
+
+ ret = pthreadpool_pipe_init(1, &p);
+ if (ret != 0) {
+ fprintf(stderr, "pthreadpool_pipe_init failed: %s\n",
+ strerror(ret));
+ return -1;
+ }
+ ret = pthreadpool_pipe_destroy(p);
+ if (ret != 0) {
+ fprintf(stderr, "pthreadpool_pipe_destroy failed: %s\n",
+ strerror(ret));
+ return -1;
+ }
+
+ child = fork();
+ if (child < 0) {
+ perror("fork failed");
+ return -1;
+ }
+ if (child == 0) {
+ exit(0);
+ }
+ waited = wait(&status);
+ if (waited == -1) {
+ perror("wait failed");
+ return -1;
+ }
+ if (waited != child) {
+ fprintf(stderr, "expected child %d, got %d\n",
+ (int)child, (int)waited);
+ return -1;
+ }
+ return 0;
+}
+
+static void busyfork_job(void *private_data)
+{
+ return;
+}
+
+static int test_busyfork(void)
+{
+ struct pthreadpool_pipe *p;
+ int fds[2];
+ struct pollfd pfd;
+ pid_t child, waitret;
+ int ret, jobnum, wstatus;
+
+ ret = pipe(fds);
+ if (ret == -1) {
+ perror("pipe failed");
+ return -1;
+ }
+
+ ret = pthreadpool_pipe_init(1, &p);
+ if (ret != 0) {
+ fprintf(stderr, "pthreadpool_pipe_init failed: %s\n",
+ strerror(ret));
+ return -1;
+ }
+
+ ret = pthreadpool_pipe_add_job(p, 1, busyfork_job, NULL);
+ if (ret != 0) {
+ fprintf(stderr, "pthreadpool_add_job failed: %s\n",
+ strerror(ret));
+ return -1;
+ }
+
+ ret = pthreadpool_pipe_finished_jobs(p, &jobnum, 1);
+ if (ret != 1) {
+ fprintf(stderr, "pthreadpool_pipe_finished_jobs failed\n");
+ return -1;
+ }
+
+ ret = poll(NULL, 0, 200);
+ if (ret == -1) {
+ perror("poll failed");
+ return -1;
+ }
+
+ child = fork();
+ if (child < 0) {
+ perror("fork failed");
+ return -1;
+ }
+
+ if (child == 0) {
+ ret = pthreadpool_pipe_destroy(p);
+ if (ret != 0) {
+ fprintf(stderr, "pthreadpool_pipe_destroy failed: "
+ "%s\n", strerror(ret));
+ exit(1);
+ }
+ exit(0);
+ }
+
+ ret = close(fds[1]);
+ if (ret == -1) {
+ perror("close failed");
+ return -1;
+ }
+
+ pfd = (struct pollfd) { .fd = fds[0], .events = POLLIN };
+
+ ret = poll(&pfd, 1, 5000);
+ if (ret == -1) {
+ perror("poll failed");
+ return -1;
+ }
+ if (ret == 0) {
+ fprintf(stderr, "Child did not exit for 5 seconds\n");
+ /*
+ * The child might hang forever in
+ * pthread_cond_destroy for example. Be kind to the
+ * system and kill it.
+ */
+ kill(child, SIGTERM);
+ return -1;
+ }
+ if (ret != 1) {
+ fprintf(stderr, "poll returned %d -- huh??\n", ret);
+ return -1;
+ }
+
+ ret = poll(NULL, 0, 200);
+ if (ret == -1) {
+ perror("poll failed");
+ return -1;
+ }
+
+ waitret = waitpid(child, &wstatus, WNOHANG);
+ if (waitret != child) {
+ fprintf(stderr, "waitpid returned %d\n", (int)waitret);
+ return -1;
+ }
+
+ if (!WIFEXITED(wstatus)) {
+ fprintf(stderr, "child did not properly exit\n");
+ return -1;
+ }
+
+ ret = WEXITSTATUS(wstatus);
+ if (ret != 0) {
+ fprintf(stderr, "child returned %d\n", ret);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int test_busyfork2(void)
+{
+ struct pthreadpool_pipe *p;
+ pid_t child;
+ int ret, jobnum;
+ struct pollfd pfd;
+
+ ret = pthreadpool_pipe_init(1, &p);
+ if (ret != 0) {
+ fprintf(stderr, "pthreadpool_pipe_init failed: %s\n",
+ strerror(ret));
+ return -1;
+ }
+
+ ret = pthreadpool_pipe_add_job(p, 1, busyfork_job, NULL);
+ if (ret != 0) {
+ fprintf(stderr, "pthreadpool_add_job failed: %s\n",
+ strerror(ret));
+ return -1;
+ }
+
+ ret = pthreadpool_pipe_finished_jobs(p, &jobnum, 1);
+ if (ret != 1) {
+ fprintf(stderr, "pthreadpool_pipe_finished_jobs failed\n");
+ return -1;
+ }
+
+ ret = poll(NULL, 0, 10);
+ if (ret == -1) {
+ perror("poll failed");
+ return -1;
+ }
+
+ ret = pthreadpool_pipe_add_job(p, 1, busyfork_job, NULL);
+ if (ret != 0) {
+ fprintf(stderr, "pthreadpool_add_job failed: %s\n",
+ strerror(ret));
+ return -1;
+ }
+
+ /*
+ * Do the fork right after the add_job. This tests a race
+ * where the atfork prepare handler gets all idle threads off
+ * the condvar. If we are faster doing the fork than the
+ * existing idle thread could get out of idle and take the
+ * job, after the fork we end up with no threads to take care
+ * of the job.
+ */
+
+ child = fork();
+ if (child < 0) {
+ perror("fork failed");
+ return -1;
+ }
+
+ if (child == 0) {
+ exit(0);
+ }
+
+ pfd = (struct pollfd) {
+ .fd = pthreadpool_pipe_signal_fd(p),
+ .events = POLLIN|POLLERR
+ };
+
+ do {
+ ret = poll(&pfd, 1, 5000);
+ } while ((ret == -1) && (errno == EINTR));
+
+ if (ret == 0) {
+ fprintf(stderr, "job unfinished after 5 seconds\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void test_tevent_wait(void *private_data)
+{
+ int *timeout = private_data;
+ poll(NULL, 0, *timeout);
+}
+
+static int test_tevent_1(void)
+{
+ struct tevent_context *ev;
+ struct pthreadpool_tevent *pool;
+ struct tevent_req *req1, *req2;
+ int timeout10 = 10;
+ int timeout100 = 100;
+ int ret;
+ bool ok;
+
+ ev = tevent_context_init(NULL);
+ if (ev == NULL) {
+ ret = errno;
+ fprintf(stderr, "tevent_context_init failed: %s\n",
+ strerror(ret));
+ return ret;
+ }
+ ret = pthreadpool_tevent_init(ev, UINT_MAX, &pool);
+ if (ret != 0) {
+ fprintf(stderr, "pthreadpool_tevent_init failed: %s\n",
+ strerror(ret));
+ TALLOC_FREE(ev);
+ return ret;
+ }
+ req1 = pthreadpool_tevent_job_send(
+ ev, ev, pool, test_tevent_wait, &timeout10);
+ if (req1 == NULL) {
+ fprintf(stderr, "pthreadpool_tevent_job_send failed\n");
+ TALLOC_FREE(ev);
+ return ENOMEM;
+ }
+ req2 = pthreadpool_tevent_job_send(
+ ev, ev, pool, test_tevent_wait, &timeout100);
+ if (req2 == NULL) {
+ fprintf(stderr, "pthreadpool_tevent_job_send failed\n");
+ TALLOC_FREE(ev);
+ return ENOMEM;
+ }
+ ok = tevent_req_poll(req2, ev);
+ if (!ok) {
+ ret = errno;
+ fprintf(stderr, "tevent_req_poll failed: %s\n",
+ strerror(ret));
+ TALLOC_FREE(ev);
+ return ret;
+ }
+ ret = pthreadpool_tevent_job_recv(req1);
+ TALLOC_FREE(req1);
+ if (ret != 0) {
+ fprintf(stderr, "tevent_req_poll failed: %s\n",
+ strerror(ret));
+ TALLOC_FREE(ev);
+ return ret;
+ }
+
+ TALLOC_FREE(req2);
+
+ ret = tevent_loop_wait(ev);
+ if (ret != 0) {
+ fprintf(stderr, "tevent_loop_wait failed\n");
+ return ret;
+ }
+
+ TALLOC_FREE(pool);
+ TALLOC_FREE(ev);
+ return 0;
+}
+
+int main(void)
+{
+ int ret;
+
+ ret = test_tevent_1();
+ if (ret != 0) {
+ fprintf(stderr, "test_event_1 failed: %s\n",
+ strerror(ret));
+ return 1;
+ }
+
+ ret = test_init();
+ if (ret != 0) {
+ fprintf(stderr, "test_init failed\n");
+ return 1;
+ }
+
+ ret = test_fork();
+ if (ret != 0) {
+ fprintf(stderr, "test_fork failed\n");
+ return 1;
+ }
+
+ ret = test_jobs(10, 10000);
+ if (ret != 0) {
+ fprintf(stderr, "test_jobs failed\n");
+ return 1;
+ }
+
+ ret = test_busydestroy();
+ if (ret != 0) {
+ fprintf(stderr, "test_busydestroy failed\n");
+ return 1;
+ }
+
+ ret = test_busyfork();
+ if (ret != 0) {
+ fprintf(stderr, "test_busyfork failed\n");
+ return 1;
+ }
+
+ ret = test_busyfork2();
+ if (ret != 0) {
+ fprintf(stderr, "test_busyfork2 failed\n");
+ return 1;
+ }
+
+ printf("success\n");
+ return 0;
+}