This avoids bottlenecks when many threads access local time but no
thread is changing TZ. It costs CPU time and energy.
* Makefile, NEWS: Mention this.
* localtime.c (THREAD_RWLOCK): New macro, defaulting to 0.
(locallock) [THREAD_RWLOCK]:
Now of type pthread_rwlock_t, not pthread_mutex_t.
All uses changed.
(rd2wrlock): New function.
(tzset_unlocked): If THREAD_RWLOCK, upgrade the read lock
to a write lock if needed.
(tzset_unlocked, gmtcheck): Upgrade the read to a write lock in
the unlikely case where writing is needed.
---
Makefile | 5 +++
NEWS | 5 +++
localtime.c | 100 +++++++++++++++++++++++++++++++++++++---------------
3 files changed, 82 insertions(+), 28 deletions(-)
diff --git a/Makefile b/Makefile
index 85c2c50b..c2c82c3a 100644
--- a/Makefile
+++ b/Makefile
@@ -304,6 +304,11 @@ 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:
+# -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.
+# It also costs CPU time and thus energy.
# -Dtime_tz=\"T\" to use T as the time_t type, rather than the system time_t
# This is intended for internal use only; it mangles external names.
# -DTZ_CHANGE_INTERVAL=N if functions depending on TZ should check
diff --git a/NEWS b/NEWS
index 64d9336d..8ba97ab4 100644
--- a/NEWS
+++ b/NEWS
@@ -68,6 +68,11 @@ 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_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.
+ It costs more CPU time and energy.
+
tzcode now uses mempcpy if available, guessing its availability.
Compile with -DHAVE_MEMPCPY=1 or 0 to override the guess.
diff --git a/localtime.c b/localtime.c
index ec13ed71..3a38ef04 100644
--- a/localtime.c
+++ b/localtime.c
@@ -44,16 +44,39 @@ struct stat { char st_ctime, st_dev, st_ino; }
# define st_ctim st_ctimespec
#endif
+#ifndef THREAD_RWLOCK
+# define THREAD_RWLOCK 0
+#endif
+
#if defined THREAD_SAFE && THREAD_SAFE
# include <pthread.h>
+# 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); }
+# 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); }
+# endif
#else
static int lock(void) { return 0; }
static void unlock(void) { }
#endif
+/* Upgrade a read lock to a write lock.
+ Return 0 on success, an errno value otherwise. */
+static int
+rd2wrlock(void)
+{
+# if THREAD_RWLOCK
+ unlock();
+ return pthread_rwlock_wrlock(&locallock);
+# else
+ return 0;
+# endif
+}
+
/* Unless intptr_t is missing, pacify gcc -Wcast-qual on char const * exprs.
Use this carefully, as the casts disable type checking.
This is a macro so that it can be used in static initializers. */
@@ -1733,38 +1756,55 @@ 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,
+ 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)
{
- char const *name = getenv("TZ");
- struct state *sp = lclptr;
- char tzloadflags = TZLOAD_FROMENV | TZLOAD_TZSTRING;
- size_t namelen = sizeof lcl_TZname + 1; /* placeholder for no name */
-
- if (name) {
- namelen = strnlen(name, sizeof lcl_TZname);
-
- /* Abbreviate a string like "/usr/share/zoneinfo/America/Los_Angeles"
- to its shorter equivalent "America/Los_Angeles". */
- if (!SUPPRESS_TZDIR && tzdirslashlen < namelen
- && memcmp(name, tzdirslash, tzdirslashlen) == 0) {
- char const *p = name + tzdirslashlen;
- while (*p == '/')
- p++;
- if (*p && *p != ':') {
- name = p;
- namelen = strnlen(name, sizeof lcl_TZname);
- tzloadflags |= TZLOAD_TZDIR_SUB;
+ char const *name;
+ struct state *sp;
+ char tzloadflags;
+ size_t namelen;
+ bool writing = false;
+
+ for (;;) {
+ name = getenv("TZ");
+ sp = lclptr;
+ tzloadflags = TZLOAD_FROMENV | TZLOAD_TZSTRING;
+ namelen = sizeof lcl_TZname + 1; /* placeholder for no name */
+
+ if (name) {
+ namelen = strnlen(name, sizeof lcl_TZname);
+
+ /* Abbreviate a string like "/usr/share/zoneinfo/America/Los_Angeles"
+ to its shorter equivalent "America/Los_Angeles". */
+ if (!SUPPRESS_TZDIR && tzdirslashlen < namelen
+ && memcmp(name, tzdirslash, tzdirslashlen) == 0) {
+ char const *p = name + tzdirslashlen;
+ while (*p == '/')
+ p++;
+ if (*p && *p != ':') {
+ name = p;
+ namelen = strnlen(name, sizeof lcl_TZname);
+ tzloadflags |= TZLOAD_TZDIR_SUB;
+ }
}
}
+
+ if ((tz_change_interval <= 0 ? tz_change_interval < 0 : fresh_tzdata(now))
+ && (name
+ ? 0 < lcl_is_set && strcmp(lcl_TZname, name) == 0
+ : lcl_is_set < 0))
+ return;
+
+ if (!THREAD_RWLOCK || writing)
+ break;
+ if (rd2wrlock() != 0)
+ return;
+ writing = true;
}
- if ((tz_change_interval <= 0 ? tz_change_interval < 0 : fresh_tzdata(now))
- && (name
- ? 0 < lcl_is_set && strcmp(lcl_TZname, name) == 0
- : lcl_is_set < 0))
- return;
# if ALL_STATE
if (! sp)
lclptr = sp = malloc(sizeof *lclptr);
@@ -1829,12 +1869,16 @@ gmtcheck(void)
if (lock() != 0)
return;
if (! gmt_is_set) {
+ if (rd2wrlock() != 0)
+ return;
+ if (!THREAD_RWLOCK || !gmt_is_set) {
#if ALL_STATE
- gmtptr = malloc(sizeof *gmtptr);
+ gmtptr = malloc(sizeof *gmtptr);
#endif
- if (gmtptr)
- gmtload(gmtptr);
- gmt_is_set = true;
+ if (gmtptr)
+ gmtload(gmtptr);
+ gmt_is_set = true;
+ }
}
unlock();
}
--
2.51.0