Add debugging information to yarn error messages.
diff --git a/pigz.c b/pigz.c
index 0667360..b5f6ea8 100644
--- a/pigz.c
+++ b/pigz.c
@@ -4280,7 +4280,7 @@
#ifndef NOTHREAD
// handle error received from yarn function
local void cut_yarn(int err) {
- throw(err, err == ENOMEM ? "not enough memory" : "internal threads error");
+ throw(err, "internal threads error");
}
#endif
diff --git a/yarn.c b/yarn.c
index d1debac..9a4f975 100644
--- a/yarn.c
+++ b/yarn.c
@@ -1,6 +1,6 @@
/* yarn.c -- generic thread operations implemented using pthread functions
- * Copyright (C) 2008, 2011, 2012, 2015, 2018 Mark Adler
- * Version 1.5 8 May 2018 Mark Adler
+ * Copyright (C) 2008, 2011, 2012, 2015, 2018, 2019 Mark Adler
+ * Version 1.6 3 Apr 2019 Mark Adler
* For conditions of distribution and use, see copyright notice in yarn.h
*/
@@ -21,6 +21,7 @@
Accept and do nothing for NULL argument to free_lock()
1.5 8 May 2018 Remove destruct() to avoid use of pthread_cancel()
Normalize the code style
+ 1.6 3 Apr 2019 Add debugging information to fail() error messages.
*/
// For thread portability.
@@ -42,7 +43,7 @@
// pthread_mutex_lock(), pthread_mutex_unlock(), pthread_mutex_destroy(),
// pthread_cond_t, PTHREAD_COND_INITIALIZER, pthread_cond_init(),
// pthread_cond_broadcast(), pthread_cond_wait(), pthread_cond_destroy()
-#include <errno.h> // ENOMEM, EAGAIN, EINVAL
+#include <errno.h> // EPERM, ESRCH, EDEADLK, ENOMEM, EBUSY, EINVAL, EAGAIN
// Interface definition.
#include "yarn.h"
@@ -56,12 +57,37 @@
// Immediately exit -- use for errors that shouldn't ever happen.
-local void fail(int err) {
+local void fail(int err, char const *file, long line, char const *func) {
+ fprintf(stderr, "%s: ", yarn_prefix);
+ switch (err) {
+ case EPERM:
+ fputs("already unlocked", stderr);
+ break;
+ case ESRCH:
+ fputs("no such thread", stderr);
+ break;
+ case EDEADLK:
+ fputs("resource deadlock", stderr);
+ break;
+ case ENOMEM:
+ fputs("out of memory", stderr);
+ break;
+ case EBUSY:
+ fputs("can't destroy locked resource", stderr);
+ break;
+ case EINVAL:
+ fputs("invalid request", stderr);
+ break;
+ case EAGAIN:
+ fputs("resource unavailable", stderr);
+ break;
+ default:
+ fprintf(stderr, "internal error %d", err);
+ }
+ fprintf(stderr, " (%s:%ld:%s).\n", file, line, func);
if (yarn_abort != NULL)
yarn_abort(err);
- fprintf(stderr, "%s: %s (%d) -- aborting\n", yarn_prefix,
- err == ENOMEM ? "out of memory" : "internal pthread error", err);
- exit(err == ENOMEM || err == EAGAIN ? err : EINVAL);
+ exit(err);
}
// Memory handling routines provided by user. If none are provided, malloc()
@@ -78,11 +104,11 @@
}
// Memory allocation that cannot fail (from the point of view of the caller).
-local void *my_malloc(size_t size) {
+local void *my_malloc(size_t size, char const *file, long line) {
void *block;
if ((block = my_malloc_f(size)) == NULL)
- fail(ENOMEM);
+ fail(ENOMEM, file, line, "malloc");
return block;
}
@@ -94,69 +120,76 @@
long value;
};
-lock *new_lock(long initial) {
- int ret;
- lock *bolt;
-
- bolt = my_malloc(sizeof(struct lock_s));
- if ((ret = pthread_mutex_init(&(bolt->mutex), NULL)) ||
- (ret = pthread_cond_init(&(bolt->cond), NULL)))
- fail(ret);
+lock *new_lock_(long initial, char const *file, long line) {
+ lock *bolt = my_malloc(sizeof(struct lock_s), file, line);
+ int ret = pthread_mutex_init(&(bolt->mutex), NULL);
+ if (ret)
+ fail(ret, file, line, "mutex_init");
+ ret = pthread_cond_init(&(bolt->cond), NULL);
+ if (ret)
+ fail(ret, file, line, "cond_init");
bolt->value = initial;
return bolt;
}
-void possess(lock *bolt) {
- int ret;
-
- if ((ret = pthread_mutex_lock(&(bolt->mutex))) != 0)
- fail(ret);
+void possess_(lock *bolt, char const *file, long line) {
+ int ret = pthread_mutex_lock(&(bolt->mutex));
+ if (ret)
+ fail(ret, file, line, "mutex_lock");
}
-void release(lock *bolt) {
- int ret;
-
- if ((ret = pthread_mutex_unlock(&(bolt->mutex))) != 0)
- fail(ret);
+void release_(lock *bolt, char const *file, long line) {
+ int ret = pthread_mutex_unlock(&(bolt->mutex));
+ if (ret)
+ fail(ret, file, line, "mutex_unlock");
}
-void twist(lock *bolt, enum twist_op op, long val) {
- int ret;
-
+void twist_(lock *bolt, enum twist_op op, long val,
+ char const *file, long line) {
if (op == TO)
bolt->value = val;
else if (op == BY)
bolt->value += val;
- if ((ret = pthread_cond_broadcast(&(bolt->cond))) ||
- (ret = pthread_mutex_unlock(&(bolt->mutex))))
- fail(ret);
+ int ret = pthread_cond_broadcast(&(bolt->cond));
+ if (ret)
+ fail(ret, file, line, "cond_broadcast");
+ ret = pthread_mutex_unlock(&(bolt->mutex));
+ if (ret)
+ fail(ret, file, line, "mutex_unlock");
}
#define until(a) while(!(a))
-void wait_for(lock *bolt, enum wait_op op, long val) {
- int ret;
-
+void wait_for_(lock *bolt, enum wait_op op, long val,
+ char const *file, long line) {
switch (op) {
- case TO_BE:
- until (bolt->value == val)
- if ((ret = pthread_cond_wait(&(bolt->cond), &(bolt->mutex))) != 0)
- fail(ret);
- break;
- case NOT_TO_BE:
- until (bolt->value != val)
- if ((ret = pthread_cond_wait(&(bolt->cond), &(bolt->mutex))) != 0)
- fail(ret);
- break;
- case TO_BE_MORE_THAN:
- until (bolt->value > val)
- if ((ret = pthread_cond_wait(&(bolt->cond), &(bolt->mutex))) != 0)
- fail(ret);
- break;
- case TO_BE_LESS_THAN:
- until (bolt->value < val)
- if ((ret = pthread_cond_wait(&(bolt->cond), &(bolt->mutex))) != 0)
- fail(ret);
+ case TO_BE:
+ until (bolt->value == val) {
+ int ret = pthread_cond_wait(&(bolt->cond), &(bolt->mutex));
+ if (ret)
+ fail(ret, file, line, "cond_wait");
+ }
+ break;
+ case NOT_TO_BE:
+ until (bolt->value != val) {
+ int ret = pthread_cond_wait(&(bolt->cond), &(bolt->mutex));
+ if (ret)
+ fail(ret, file, line, "cond_wait");
+ }
+ break;
+ case TO_BE_MORE_THAN:
+ until (bolt->value > val) {
+ int ret = pthread_cond_wait(&(bolt->cond), &(bolt->mutex));
+ if (ret)
+ fail(ret, file, line, "cond_wait");
+ }
+ break;
+ case TO_BE_LESS_THAN:
+ until (bolt->value < val) {
+ int ret = pthread_cond_wait(&(bolt->cond), &(bolt->mutex));
+ if (ret)
+ fail(ret, file, line, "cond_wait");
+ }
}
}
@@ -164,14 +197,15 @@
return bolt->value;
}
-void free_lock(lock *bolt) {
- int ret;
-
+void free_lock_(lock *bolt, char const *file, long line) {
if (bolt == NULL)
return;
- if ((ret = pthread_cond_destroy(&(bolt->cond))) ||
- (ret = pthread_mutex_destroy(&(bolt->mutex))))
- fail(ret);
+ int ret = pthread_cond_destroy(&(bolt->cond));
+ if (ret)
+ fail(ret, file, line, "cond_destroy");
+ ret = pthread_mutex_destroy(&(bolt->mutex));
+ if (ret)
+ fail(ret, file, line, "mutex_destroy");
my_free(bolt);
}
@@ -196,26 +230,26 @@
struct capsule {
void (*probe)(void *);
void *payload;
+ char const *file;
+ long line;
};
// Mark the calling thread as done and alert join_all().
-local void reenter(void *dummy) {
- thread *match, **prior;
- pthread_t me;
-
- (void)dummy;
+local void reenter(void *arg) {
+ struct capsule *capsule = arg;
// find this thread in the threads list by matching the thread id
- me = pthread_self();
- possess(&(threads_lock));
- prior = &(threads);
+ pthread_t me = pthread_self();
+ possess_(&(threads_lock), capsule->file, capsule->line);
+ thread **prior = &(threads);
+ thread *match;
while ((match = *prior) != NULL) {
if (pthread_equal(match->id, me))
break;
prior = &(match->next);
}
if (match == NULL)
- fail(EINVAL);
+ fail(ESRCH, capsule->file, capsule->line, "reenter lost");
// mark this thread as done and move it to the head of the list
match->done = 1;
@@ -226,7 +260,7 @@
}
// update the count of threads to be joined and alert join_all()
- twist(&(threads_lock), BY, +1);
+ twist_(&(threads_lock), BY, +1, capsule->file, capsule->line);
}
// All threads go through this routine. Just before a thread exits, it marks
@@ -237,7 +271,7 @@
struct capsule *capsule = arg;
// run reenter() before leaving
- pthread_cleanup_push(reenter, NULL);
+ pthread_cleanup_push(reenter, arg);
// execute the requested function with argument
capsule->probe(capsule->payload);
@@ -252,64 +286,69 @@
// Not all POSIX implementations create threads as joinable by default, so that
// is made explicit here.
-thread *launch(void (*probe)(void *), void *payload) {
- int ret;
- thread *th;
- struct capsule *capsule;
- pthread_attr_t attr;
-
+thread *launch_(void (*probe)(void *), void *payload,
+ char const *file, long line) {
// construct the requested call and argument for the ignition() routine
// (allocated instead of automatic so that we're sure this will still be
// there when ignition() actually starts up -- ignition() will free this
// allocation)
- capsule = my_malloc(sizeof(struct capsule));
+ struct capsule *capsule = my_malloc(sizeof(struct capsule), file, line);
capsule->probe = probe;
capsule->payload = payload;
+ capsule->file = file;
+ capsule->line = line;
// assure this thread is in the list before join_all() or ignition() looks
// for it
- possess(&(threads_lock));
+ possess_(&(threads_lock), file, line);
// create the thread and call ignition() from that thread
- th = my_malloc(sizeof(struct thread_s));
- if ((ret = pthread_attr_init(&attr)) ||
- (ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE)) ||
- (ret = pthread_create(&(th->id), &attr, ignition, capsule)) ||
- (ret = pthread_attr_destroy(&attr)))
- fail(ret);
+ thread *th = my_malloc(sizeof(struct thread_s), file, line);
+ pthread_attr_t attr;
+ int ret = pthread_attr_init(&attr);
+ if (ret)
+ fail(ret, file, line, "attr_init");
+ ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+ if (ret)
+ fail(ret, file, line, "attr_setdetachstate");
+ ret = pthread_create(&(th->id), &attr, ignition, capsule);
+ if (ret)
+ fail(ret, file, line, "create");
+ ret = pthread_attr_destroy(&attr);
+ if (ret)
+ fail(ret, file, line, "attr_destroy");
// put the thread in the threads list for join_all()
th->done = 0;
th->next = threads;
threads = th;
- release(&(threads_lock));
+ release_(&(threads_lock), file, line);
return th;
}
-void join(thread *ally) {
- int ret;
- thread *match, **prior;
-
+void join_(thread *ally, char const *file, long line) {
// wait for thread to exit and return its resources
- if ((ret = pthread_join(ally->id, NULL)) != 0)
- fail(ret);
+ int ret = pthread_join(ally->id, NULL);
+ if (ret)
+ fail(ret, file, line, "join");
// find the thread in the threads list
- possess(&(threads_lock));
- prior = &(threads);
+ possess_(&(threads_lock), file, line);
+ thread **prior = &(threads);
+ thread *match;
while ((match = *prior) != NULL) {
if (match == ally)
break;
prior = &(match->next);
}
if (match == NULL)
- fail(EINVAL);
+ fail(ESRCH, file, line, "join lost");
// remove thread from list and update exited count, free thread
if (match->done)
threads_lock.value--;
*prior = match->next;
- release(&(threads_lock));
+ release_(&(threads_lock), file, line);
my_free(ally);
}
@@ -317,33 +356,32 @@
// announced that they have exited (see ignition()). When there are many
// threads, this is faster than waiting for some random thread to exit while a
// bunch of other threads have already exited.
-int join_all(void) {
- int ret, count;
- thread *match, **prior;
-
+int join_all_(char const *file, long line) {
// grab the threads list and initialize the joined count
- count = 0;
- possess(&(threads_lock));
+ int count = 0;
+ possess_(&(threads_lock), file, line);
// do until threads list is empty
while (threads != NULL) {
// wait until at least one thread has reentered
- wait_for(&(threads_lock), NOT_TO_BE, 0);
+ wait_for_(&(threads_lock), NOT_TO_BE, 0, file, line);
// find the first thread marked done (should be at or near the top)
- prior = &(threads);
+ thread **prior = &(threads);
+ thread *match;
while ((match = *prior) != NULL) {
if (match->done)
break;
prior = &(match->next);
}
if (match == NULL)
- fail(EINVAL);
+ fail(ESRCH, file, line, "join_all lost");
// join the thread (will be almost immediate), remove from the threads
// list, update the reenter count, and free the thread
- if ((ret = pthread_join(match->id, NULL)) != 0)
- fail(ret);
+ int ret = pthread_join(match->id, NULL);
+ if (ret)
+ fail(ret, file, line, "join");
threads_lock.value--;
*prior = match->next;
my_free(match);
@@ -351,6 +389,6 @@
}
// let go of the threads list and return the number of threads joined
- release(&(threads_lock));
+ release_(&(threads_lock), file, line);
return count;
}
diff --git a/yarn.h b/yarn.h
index b19a59f..1076d73 100644
--- a/yarn.h
+++ b/yarn.h
@@ -111,19 +111,28 @@
void yarn_mem(void *(*)(size_t), void (*)(void *));
typedef struct thread_s thread;
-thread *launch(void (*)(void *), void *);
-void join(thread *);
-int join_all(void);
+thread *launch_(void (*)(void *), void *, char const *, long);
+#define launch(a, b) launch_(a, b, __FILE__, __LINE__)
+void join_(thread *, char const *, long);
+#define join(a) join_(a, __FILE__, __LINE__)
+int join_all_(char const *, long);
+#define join_all() join_all_(__FILE__, __LINE__)
typedef struct lock_s lock;
-lock *new_lock(long);
-void possess(lock *);
-void release(lock *);
+lock *new_lock_(long, char const *, long);
+#define new_lock(a) new_lock_(a, __FILE__, __LINE__)
+void possess_(lock *, char const *, long);
+#define possess(a) possess_(a, __FILE__, __LINE__)
+void release_(lock *, char const *, long);
+#define release(a) release_(a, __FILE__, __LINE__)
enum twist_op { TO, BY };
-void twist(lock *, enum twist_op, long);
+void twist_(lock *, enum twist_op, long, char const *, long);
+#define twist(a, b, c) twist_(a, b, c, __FILE__, __LINE__)
enum wait_op {
TO_BE, /* or */ NOT_TO_BE, /* that is the question */
TO_BE_MORE_THAN, TO_BE_LESS_THAN };
-void wait_for(lock *, enum wait_op, long);
+void wait_for_(lock *, enum wait_op, long, char const *, long);
+#define wait_for(a, b, c) wait_for_(a, b, c, __FILE__, __LINE__)
long peek_lock(lock *);
-void free_lock(lock *);
+void free_lock_(lock *, char const *, long);
+#define free_lock(a) free_lock_(a, __FILE__, __LINE__)