* Makefile, NEWS: Mention this.
* localtime.c (THREAD_PREFER_SINGLE): Default to 0.
(HAVE___ISTHREADED, HAVE_SYS_SINGLE_THREADED_H): Provide defaults.
(dolock, dounlock): New functions,
whose bodies are those of the old lock and unlock functions.
(lock): Return -1 if known to be single threaded so no lock is
needed. All callers changed.
(unlock, rd2wrlock, tzset_unlocked):
New arg THREADED. All callers changed.
---
Makefile | 9 +++-
NEWS | 7 +++
localtime.c | 125 ++++++++++++++++++++++++++++++++++++++--------------
3 files changed, 106 insertions(+), 35 deletions(-)
diff --git a/Makefile b/Makefile
index c2c82c3a..8939869a 100644
--- a/Makefile
+++ b/Makefile
@@ -304,7 +304,14 @@ LDLIBS=
# -DTHREAD_SAFE to make localtime.c thread-safe, as POSIX requires;
# not needed by the main-program tz code, which is single-threaded.
# Append other compiler flags as needed, e.g., -pthread on GNU/Linux.
-# The following option can also be used:
+# The following options can also be used:
+# -DTHREAD_PREFER_SINGLE to prefer speed in single-threaded apps,
+# at some cost in CPU time and energy in multi-threaded apps.
+# The following options can also be used:
+# -DHAVE___ISTHREADED=1 if there is an extern int __isthreaded
+# variable, 0 otherwise (default is guessed)
+# -DHAVE_SYS_SINGLE_THREADED_H=0 if <sys/single_threaded.h> works,
+# 0 otherwise (default is guessed)
# -DTHREAD_RWLOCK to use read-write locks intead of mutexes.
# This can improve paralellism and thus save real time
# if many threads call tzcode functions simultaneously.
diff --git a/NEWS b/NEWS
index 8ba97ab4..5bfb7bb4 100644
--- a/NEWS
+++ b/NEWS
@@ -68,6 +68,13 @@ Unreleased, experimental changes
transition begun in release 96k, which removed spaces in tzdata
because the spaces break time string parsers.
+ The new CFLAGS option -DTHREAD_PREFER_SINGLE causes tzcode
+ in single-threaded processes to avoid locks, as FreeBSD does.
+ This can save time in single-threaded apps. The threadedness
+ testing costs CPU time and energy in multi-threaded apps.
+ New options -DHAVE___ISTHREADED and -DHAVE_SYS_SINGLE_THREADED_H
+ can help configure how to test for single-threadedness.
+
The new CFLAGS option -DTHREAD_RWLOCK uses read-write locks, as
macOS does, instead of mutexes. This saves real time when TZ is
rarely changing and many threads call tzcode simultaneously.
diff --git a/localtime.c b/localtime.c
index 3a38ef04..01db7e70 100644
--- a/localtime.c
+++ b/localtime.c
@@ -50,31 +50,87 @@ struct stat { char st_ctime, st_dev, st_ino; }
#if defined THREAD_SAFE && THREAD_SAFE
# include <pthread.h>
+
+# ifndef THREAD_PREFER_SINGLE
+# define THREAD_PREFER_SINGLE 0
+# endif
+# if THREAD_PREFER_SINGLE
+# ifndef HAVE___ISTHREADED
+# if defined __FreeBSD__ || defined __OpenBSD__
+# define HAVE___ISTHREADED 1
+# else
+# define HAVE___ISTHREADED 0
+# endif
+# endif
+# if HAVE___ISTHREADED
+extern int __isthreaded;
+# else
+# if !defined HAVE_SYS_SINGLE_THREADED_H && defined __has_include
+# if __has_include(<sys/single_threaded.h>)
+# define HAVE_SYS_SINGLE_THREADED_H 1
+# else
+# define HAVE_SYS_SINGLE_THREADED_H 0
+# endif
+# endif
+# ifndef HAVE_SYS_SINGLE_THREADED_H
+# if defined __GLIBC__ && 2 < __GLIBC__ + (32 <= __GLIBC_MINOR__)
+# define HAVE_SYS_SINGLE_THREADED_H 1
+# else
+# define HAVE_SYS_SINGLE_THREADED_H 0
+# endif
+# endif
+# if HAVE_SYS_SINGLE_THREADED_H
+# include <sys/single_threaded.h>
+# endif
+# endif
+# endif
+
# if THREAD_RWLOCK
static pthread_rwlock_t locallock = PTHREAD_RWLOCK_INITIALIZER;
-static int lock(void) { return pthread_rwlock_rdlock(&locallock); }
-static void unlock(void) { pthread_rwlock_unlock(&locallock); }
+static int dolock(void) { return pthread_rwlock_rdlock(&locallock); }
+static void dounlock(void) { pthread_rwlock_unlock(&locallock); }
# else
static pthread_mutex_t locallock = PTHREAD_MUTEX_INITIALIZER;
-static int lock(void) { return pthread_mutex_lock(&locallock); }
-static void unlock(void) { pthread_mutex_unlock(&locallock); }
+static int dolock(void) { return pthread_mutex_lock(&locallock); }
+static void dounlock(void) { pthread_mutex_unlock(&locallock); }
# endif
+/* Get a lock. Return 0 on success, a positive errno value on failure,
+ negative if known to be single-threaded so no lock is needed. */
+static int
+lock(void)
+{
+# if THREAD_PREFER_SINGLE && HAVE___ISTHREADED
+ if (!__isthreaded)
+ return -1;
+# elif THREAD_PREFER_SINGLE && HAVE_SYS_SINGLE_THREADED_H
+ if (__libc_single_threaded)
+ return -1;
+# endif
+ return dolock();
+}
+static void
+unlock(bool threaded)
+{
+ if (threaded)
+ dounlock();
+}
#else
-static int lock(void) { return 0; }
-static void unlock(void) { }
+static int lock(void) { return -1; }
+static void unlock(ATTRIBUTE_MAYBE_UNUSED bool threaded) { }
#endif
-/* Upgrade a read lock to a write lock.
- Return 0 on success, an errno value otherwise. */
+/* If THREADED, upgrade a read lock to a write lock.
+ Return 0 on success, a positive errno value otherwise. */
static int
-rd2wrlock(void)
+rd2wrlock(ATTRIBUTE_MAYBE_UNUSED bool threaded)
{
# if THREAD_RWLOCK
- unlock();
- return pthread_rwlock_wrlock(&locallock);
-# else
- return 0;
+ if (threaded) {
+ dounlock();
+ return pthread_rwlock_wrlock(&locallock);
+ }
# endif
+ return 0;
}
/* Unless intptr_t is missing, pacify gcc -Wcast-qual on char const * exprs.
@@ -1756,11 +1812,11 @@ zoneinit(struct state *sp, char const *name, char
tzloadflags)
}
/* Like tzset(), but in a critical section.
- If THREAD_RWLOCK the caller has a read lock,
+ If THREADED && THREAD_RWLOCK the caller has a read lock,
and this function might ugrade it to a write lock.
If tz_change_interval is positive the time is NOW; otherwise ignore NOW. */
static void
-tzset_unlocked(monotime_t now)
+tzset_unlocked(bool threaded, monotime_t now)
{
char const *name;
struct state *sp;
@@ -1800,7 +1856,7 @@ tzset_unlocked(monotime_t now)
if (!THREAD_RWLOCK || writing)
break;
- if (rd2wrlock() != 0)
+ if (rd2wrlock(threaded) != 0)
return;
writing = true;
}
@@ -1853,12 +1909,12 @@ tzset(void)
{
monotime_t now = get_monotonic_time();
int err = lock();
- if (err != 0) {
+ if (0 < err) {
errno = err;
return;
}
- tzset_unlocked(now);
- unlock();
+ tzset_unlocked(!err, now);
+ unlock(!err);
}
#endif
@@ -1866,10 +1922,11 @@ static void
gmtcheck(void)
{
static bool gmt_is_set;
- if (lock() != 0)
+ int err = lock();
+ if (0 < err)
return;
if (! gmt_is_set) {
- if (rd2wrlock() != 0)
+ if (rd2wrlock(!err) != 0)
return;
if (!THREAD_RWLOCK || !gmt_is_set) {
#if ALL_STATE
@@ -1880,7 +1937,7 @@ gmtcheck(void)
gmt_is_set = true;
}
}
- unlock();
+ unlock(!err);
}
#if NETBSD_INSPIRED && !USE_TIMEX_T
@@ -2047,14 +2104,14 @@ localtime_tzset(time_t const *timep, struct tm *tmp,
bool setname)
{
monotime_t now = get_monotonic_time();
int err = lock();
- if (err) {
+ if (0 < err) {
errno = err;
return NULL;
}
if (0 <= tz_change_interval || setname || !lcl_is_set)
- tzset_unlocked(now);
+ tzset_unlocked(!err, now);
tmp = localsub(lclptr, timep, setname, tmp);
- unlock();
+ unlock(!err);
return tmp;
}
@@ -2715,13 +2772,13 @@ mktime(struct tm *tmp)
monotime_t now = get_monotonic_time();
time_t t;
int err = lock();
- if (err) {
+ if (0 < err) {
errno = err;
return -1;
}
- tzset_unlocked(now);
+ tzset_unlocked(!err, now);
t = mktime_tzname(lclptr, tmp, true);
- unlock();
+ unlock(!err);
return t;
}
@@ -2832,15 +2889,15 @@ time2posix(time_t t)
{
monotime_t now = get_monotonic_time();
int err = lock();
- if (err) {
+ if (0 < err) {
errno = err;
return -1;
}
if (0 <= tz_change_interval || !lcl_is_set)
- tzset_unlocked(now);
+ tzset_unlocked(!err, now);
if (lclptr)
t = time2posix_z(lclptr, t);
- unlock();
+ unlock(!err);
return t;
}
@@ -2878,15 +2935,15 @@ posix2time(time_t t)
{
monotime_t now = get_monotonic_time();
int err = lock();
- if (err) {
+ if (0 < err) {
errno = err;
return -1;
}
if (0 <= tz_change_interval || !lcl_is_set)
- tzset_unlocked(now);
+ tzset_unlocked(!err, now);
if (lclptr)
t = posix2time_z(lclptr, t);
- unlock();
+ unlock(!err);
return t;
}
--
2.51.0