From 23af112f5556d6a785c17e09f2422ac931405f61 Mon Sep 17 00:00:00 2001
From: Thomas Guillem <thomas@gllm.fr>
Date: Tue, 5 Apr 2022 15:46:03 +0200
Subject: [PATCH 1/1] lib: make curl_global_init() threadsafe when possible

Use a posix pthread or a Windows SRWLOCK to lock curl_global_init*() and
curl_global_cleanup().

Closes #8680

Conflict:update easy_lock.h to latest
Reference:https://github.com/curl/curl/commit/23af112f5556d6a785c17e09f2422ac931405f61
---
 configure.ac         |  2 ++
 lib/Makefile.inc     |  1 +
 lib/easy.c           | 52 +++++++++++++++++++++++++++++----
 lib/easy_lock.h      | 105 ++++++++++++++++++++++++++++++++++++++++++++
 m4/curl-functions.m4 | 23 +++++++++++++++
 5 files changed, 178 insertions(+), 5 deletions(-)
 create mode 100644 lib/easy_lock.h

diff --git a/configure.ac b/configure.ac
index 3910ec631..009f32c32 100644
--- a/configure.ac
+++ b/configure.ac
@@ -118,6 +118,7 @@ AC_SUBST(libext)
 dnl figure out the libcurl version
 CURLVERSION=`$SED -ne 's/^#define LIBCURL_VERSION "\(.*\)".*/\1/p' ${srcdir}/include/curl/curlver.h`
 XC_CHECK_PROG_CC
+CURL_ATOMIC
 
 dnl for --enable-code-coverage
 CURL_COVERAGE
@@ -3444,6 +3445,7 @@ AC_CHECK_FUNCS([fnmatch \
   if_nametoindex \
   mach_absolute_time \
   pipe \
+  sched_yield \
   setlocale \
   setmode \
   setrlimit \
diff --git a/lib/Makefile.inc b/lib/Makefile.inc
index 1ab007896..f756515d6 100644
--- a/lib/Makefile.inc
+++ b/lib/Makefile.inc
@@ -262,6 +262,7 @@ LIB_HFILES =         \
   doh.h              \
   dotdot.h           \
   dynbuf.h           \
+  easy_lock.h        \
   easyif.h           \
   easyoptions.h      \
   escape.h           \
diff --git a/lib/easy.c b/lib/easy.c
index 336cada87..7781c8e16 100644
--- a/lib/easy.c
+++ b/lib/easy.c
@@ -84,11 +84,25 @@
 #include "curl_printf.h"
 #include "curl_memory.h"
 #include "memdebug.h"
+#include "easy_lock.h"
 
 /* true globals -- for curl_global_init() and curl_global_cleanup() */
 static unsigned int  initialized;
 static long          init_flags;
 
+#ifdef GLOBAL_INIT_IS_THREADSAFE
+
+static curl_simple_lock s_lock = CURL_SIMPLE_LOCK_INIT;
+#define global_init_lock() curl_simple_lock_lock(&s_lock)
+#define global_init_unlock() curl_simple_lock_unlock(&s_lock)
+
+#else
+
+#define global_init_lock()
+#define global_init_unlock()
+
+#endif
+
 /*
  * strdup (and other memory functions) is redefined in complicated
  * ways, but at this point it must be defined as the system-supplied strdup
@@ -207,7 +221,14 @@ static CURLcode global_init(long flags, bool memoryfuncs)
  */
 CURLcode curl_global_init(long flags)
 {
-  return global_init(flags, TRUE);
+  CURLcode result;
+  global_init_lock();
+
+  result = global_init(flags, TRUE);
+
+  global_init_unlock();
+
+  return result;
 }
 
 /*
@@ -218,15 +239,20 @@ CURLcode curl_global_init_mem(long flags, curl_malloc_callback m,
                               curl_free_callback f, curl_realloc_callback r,
                               curl_strdup_callback s, curl_calloc_callback c)
 {
+  CURLcode result;
+
   /* Invalid input, return immediately */
   if(!m || !f || !r || !s || !c)
     return CURLE_FAILED_INIT;
 
+  global_init_lock();
+
   if(initialized) {
     /* Already initialized, don't do it again, but bump the variable anyway to
        work like curl_global_init() and require the same amount of cleanup
        calls. */
     initialized++;
+    global_init_unlock();
     return CURLE_OK;
   }
 
@@ -239,7 +265,11 @@ CURLcode curl_global_init_mem(long flags, curl_malloc_callback m,
   Curl_ccalloc = c;
 
   /* Call the actual init function, but without setting */
-  return global_init(flags, FALSE);
+  result = global_init(flags, FALSE);
+
+  global_init_unlock();
+
+  return result;
 }
 
 /**
@@ -248,11 +278,17 @@ CURLcode curl_global_init_mem(long flags, curl_malloc_callback m,
  */
 void curl_global_cleanup(void)
 {
-  if(!initialized)
+  global_init_lock();
+
+  if(!initialized) {
+    global_init_unlock();
     return;
+  }
 
-  if(--initialized)
+  if(--initialized) {
+    global_init_unlock();
     return;
+  }
 
   Curl_ssl_cleanup();
   Curl_resolver_global_cleanup();
@@ -273,6 +309,8 @@ void curl_global_cleanup(void)
 #endif
 
   init_flags  = 0;
+
+  global_init_unlock();
 }
 
 /*
@@ -285,14 +323,18 @@ struct Curl_easy *curl_easy_init(void)
   struct Curl_easy *data;
 
   /* Make sure we inited the global SSL stuff */
+  global_init_lock();
+
   if(!initialized) {
-    result = curl_global_init(CURL_GLOBAL_DEFAULT);
+    result = global_init(CURL_GLOBAL_DEFAULT, TRUE);
     if(result) {
       /* something in the global init failed, return nothing */
       DEBUGF(fprintf(stderr, "Error: curl_global_init failed\n"));
+      global_init_unlock();
       return NULL;
     }
   }
+  global_init_unlock();
 
   /* We use curl_open() with undefined URL so far */
   result = Curl_open(&data);
diff --git a/lib/easy_lock.h b/lib/easy_lock.h
new file mode 100644
index 000000000..41627c4d4
--- /dev/null
+++ b/lib/easy_lock.h
@@ -0,0 +1,105 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#define GLOBAL_INIT_IS_THREADSAFE
+
+#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x600
+
+#ifdef __MINGW32__
+#ifndef __MINGW64_VERSION_MAJOR
+#if (__MINGW32_MAJOR_VERSION < 5) || \
+    (__MINGW32_MAJOR_VERSION == 5 && __MINGW32_MINOR_VERSION == 0)
+/* mingw >= 5.0.1 defines SRWLOCK, and slightly different from MS define */
+typedef PVOID SRWLOCK, *PSRWLOCK;
+#endif
+#endif
+#ifndef SRWLOCK_INIT
+#define SRWLOCK_INIT NULL
+#endif
+#endif /* __MINGW32__ */
+
+#define curl_simple_lock SRWLOCK
+#define CURL_SIMPLE_LOCK_INIT SRWLOCK_INIT
+
+#define curl_simple_lock_lock(m) AcquireSRWLockExclusive(m)
+#define curl_simple_lock_unlock(m) ReleaseSRWLockExclusive(m)
+
+#elif defined(HAVE_ATOMIC) && defined(HAVE_STDATOMIC_H)
+#include <stdatomic.h>
+#if defined(HAVE_SCHED_YIELD)
+#include <sched.h>
+#endif
+
+#define curl_simple_lock atomic_int
+#define CURL_SIMPLE_LOCK_INIT 0
+
+/* a clang-thing */
+#ifndef __has_builtin
+#define __has_builtin(x) 0
+#endif
+
+#ifndef __INTEL_COMPILER
+/* The Intel compiler tries to look like GCC *and* clang *and* lies in its
+   __has_builtin() function, so override it. */
+
+/* if GCC on i386/x86_64 or if the built-in is present */
+#if ( (defined(__GNUC__) && !defined(__clang__)) &&     \
+      (defined(__i386__) || defined(__x86_64__))) ||    \
+  __has_builtin(__builtin_ia32_pause)
+#define HAVE_BUILTIN_IA32_PAUSE
+#endif
+
+#endif
+
+static inline void curl_simple_lock_lock(curl_simple_lock *lock)
+{
+  for(;;) {
+    if(!atomic_exchange_explicit(lock, true, memory_order_acquire))
+      break;
+    /* Reduce cache coherency traffic */
+    while(atomic_load_explicit(lock, memory_order_relaxed)) {
+      /* Reduce load (not mandatory) */
+#ifdef HAVE_BUILTIN_IA32_PAUSE
+      __builtin_ia32_pause();
+#elif defined(__aarch64__)
+      __asm__ volatile("yield" ::: "memory");
+#elif defined(HAVE_SCHED_YIELD)
+      sched_yield();
+#endif
+    }
+  }
+}
+
+static inline void curl_simple_lock_unlock(curl_simple_lock *lock)
+{
+  atomic_store_explicit(lock, false, memory_order_release);
+}
+
+#else
+
+#undef  GLOBAL_INIT_IS_THREADSAFE
+
+#endif
diff --git a/m4/curl-functions.m4 b/m4/curl-functions.m4
index b40d42798..eb85dce4a 100644
--- a/m4/curl-functions.m4
+++ b/m4/curl-functions.m4
@@ -6566,3 +6566,26 @@ AC_DEFUN([CURL_COVERAGE],[
     LIBS="$LIBS -lgcov"
   fi
 ])
+
+dnl CURL_ATOMIC
+dnl --------------------------------------------------
+dnl Check if _Atomic works
+dnl
+AC_DEFUN([CURL_ATOMIC],[
+  AC_MSG_CHECKING([if _Atomic is available])
+  AC_COMPILE_IFELSE([
+    AC_LANG_PROGRAM([[
+      $curl_includes_unistd
+    ]],[[
+      _Atomic int i = 0;
+    ]])
+  ],[
+    AC_MSG_RESULT([yes])
+    AC_DEFINE_UNQUOTED(HAVE_ATOMIC, 1,
+      [Define to 1 if you have _Atomic support.])
+    tst_atomic="yes"
+  ],[
+    AC_MSG_RESULT([no])
+    tst_atomic="no"
+  ])
+])
-- 
2.33.0

