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

Reply via email to