/**********************************************************************
 *
 * Project:  CPL - Common Portability Library
 * Purpose:  CPL Multi-Threading, and process handling portability functions.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 **********************************************************************
 * Copyright (c) 2002, Frank Warmerdam
 * Copyright (c) 2009-2013, Even Rouault <even dot rouault at spatialys.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

// Include cpl_config.h BEFORE cpl_multiproc.h, as the later may undefine
// CPL_MULTIPROC_PTHREAD for mingw case.

#include "cpl_config.h"
#include "cpl_multiproc.h"

#ifdef CHECK_THREAD_CAN_ALLOCATE_TLS
#include <cassert>
#endif
#include <cerrno>
#include <cmath>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <algorithm>

#include "cpl_atomic_ops.h"
#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_string.h"
#include "cpl_vsi.h"

#if defined(CPL_MULTIPROC_STUB) && !defined(DEBUG)
#define MUTEX_NONE
#endif

// #define DEBUG_MUTEX

#if defined(DEBUG) &&                                                          \
    (defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)))
#ifndef DEBUG_CONTENTION
#define DEBUG_CONTENTION
#endif
#endif

typedef struct _CPLSpinLock CPLSpinLock;

struct _CPLLock
{
    CPLLockType eType;
    union
    {
        CPLMutex *hMutex;
        CPLSpinLock *hSpinLock;
    } u;

#ifdef DEBUG_CONTENTION
    bool bDebugPerfAsked;
    bool bDebugPerf;
    volatile int nCurrentHolders;
    GUIntBig nStartTime;
    GIntBig nMaxDiff;
    double dfAvgDiff;
    GUIntBig nIters;
#endif
};

#ifdef DEBUG_CONTENTION

#if defined(__x86_64)
#define GCC_CPUID(level, a, b, c, d)                                           \
    __asm__ volatile("xchgq %%rbx, %q1\n"                                      \
                     "cpuid\n"                                                 \
                     "xchgq %%rbx, %q1"                                        \
                     : "=a"(a), "=r"(b), "=c"(c), "=d"(d)                      \
                     : "0"(level))
#else
#define GCC_CPUID(level, a, b, c, d)                                           \
    __asm__ volatile("xchgl %%ebx, %1\n"                                       \
                     "cpuid\n"                                                 \
                     "xchgl %%ebx, %1"                                         \
                     : "=a"(a), "=r"(b), "=c"(c), "=d"(d)                      \
                     : "0"(level))
#endif

static GUIntBig CPLrdtsc()
{
    unsigned int a;
    unsigned int d;
    unsigned int unused1;
    unsigned int unused2;
    unsigned int unused3;
    unsigned int unused4;
    GCC_CPUID(0, unused1, unused2, unused3, unused4);
    __asm__ volatile("rdtsc" : "=a"(a), "=d"(d));
    return static_cast<GUIntBig>(a) | (static_cast<GUIntBig>(d) << 32);
}

static GUIntBig CPLrdtscp()
{
    unsigned int a;
    unsigned int d;
    unsigned int unused1;
    unsigned int unused2;
    unsigned int unused3;
    unsigned int unused4;
    __asm__ volatile("rdtscp" : "=a"(a), "=d"(d));
    GCC_CPUID(0, unused1, unused2, unused3, unused4);
    return static_cast<GUIntBig>(a) | (static_cast<GUIntBig>(d) << 32);
}
#endif

static CPLSpinLock *CPLCreateSpinLock();  // Returned NON acquired.
static int CPLCreateOrAcquireSpinLockInternal(CPLLock **);
static int CPLAcquireSpinLock(CPLSpinLock *);
static void CPLReleaseSpinLock(CPLSpinLock *);
static void CPLDestroySpinLock(CPLSpinLock *);

#ifndef CPL_MULTIPROC_PTHREAD
#ifndef MUTEX_NONE
static CPLMutex *CPLCreateOrAcquireMasterMutex(double);
static CPLMutex *&CPLCreateOrAcquireMasterMutexInternal(double);
static CPLMutex *CPLCreateUnacquiredMutex();
#endif
#endif

// We don't want it to be publicly used since it solves rather tricky issues
// that are better to remain hidden.
void CPLFinalizeTLS();

/************************************************************************/
/*                           CPLMutexHolder()                           */
/************************************************************************/

#ifdef MUTEX_NONE
CPLMutexHolder::CPLMutexHolder(CPLMutex ** /* phMutex */,
                               double /* dfWaitInSeconds */,
                               const char * /* pszFileIn */, int /* nLineIn */,
                               int /* nOptions */)
{
}

#else
CPLMutexHolder::CPLMutexHolder(CPLMutex **phMutex, double dfWaitInSeconds,
                               const char *pszFileIn, int nLineIn, int nOptions)
    : hMutex(nullptr), pszFile(pszFileIn), nLine(nLineIn)
{
    if (phMutex == nullptr)
    {
        fprintf(stderr, "CPLMutexHolder: phMutex )) NULL !\n");
        hMutex = nullptr;
        return;
    }

#ifdef DEBUG_MUTEX
    // There is no way to use CPLDebug() here because it works with
    // mutexes itself so we will fall in infinite recursion.
    // fprintf() will do the job right.
    fprintf(stderr, "CPLMutexHolder: Request %p for pid %ld at %d/%s.\n",
            *phMutex, static_cast<long>(CPLGetPID()), nLine, pszFile);
#else
    // TODO(schwehr): Find a better way to do handle this.
    (void)pszFile;
    (void)nLine;
#endif

    if (!CPLCreateOrAcquireMutexEx(phMutex, dfWaitInSeconds, nOptions))
    {
        fprintf(stderr, "CPLMutexHolder: Failed to acquire mutex!\n");
        hMutex = nullptr;
    }
    else
    {
#ifdef DEBUG_MUTEX
        fprintf(stderr, "CPLMutexHolder: Acquired %p for pid %ld at %d/%s.\n",
                *phMutex, static_cast<long>(CPLGetPID()), nLine, pszFile);
#endif

        hMutex = *phMutex;
    }
}
#endif  // ndef MUTEX_NONE

/************************************************************************/
/*                           CPLMutexHolder()                           */
/************************************************************************/

#ifdef MUTEX_NONE
CPLMutexHolder::CPLMutexHolder(CPLMutex * /* hMutexIn */,
                               double /* dfWaitInSeconds */,
                               const char * /* pszFileIn */, int /* nLineIn */)
{
}
#else
CPLMutexHolder::CPLMutexHolder(CPLMutex *hMutexIn, double dfWaitInSeconds,
                               const char *pszFileIn, int nLineIn)
    : hMutex(hMutexIn), pszFile(pszFileIn), nLine(nLineIn)
{
    if (hMutex != nullptr && !CPLAcquireMutex(hMutex, dfWaitInSeconds))
    {
        fprintf(stderr, "CPLMutexHolder: Failed to acquire mutex!\n");
        hMutex = nullptr;
    }
}
#endif  // ndef MUTEX_NONE

/************************************************************************/
/*                          ~CPLMutexHolder()                           */
/************************************************************************/

#ifdef MUTEX_NONE
CPLMutexHolder::~CPLMutexHolder()
{
}
#else
CPLMutexHolder::~CPLMutexHolder()
{
    if (hMutex != nullptr)
    {
#ifdef DEBUG_MUTEX
        fprintf(stderr, "~CPLMutexHolder: Release %p for pid %ld at %d/%s.\n",
                hMutex, static_cast<long>(CPLGetPID()), nLine, pszFile);
#endif
        CPLReleaseMutex(hMutex);
    }
}
#endif  // ndef MUTEX_NONE

int CPLCreateOrAcquireMutex(CPLMutex **phMutex, double dfWaitInSeconds)
{
    return CPLCreateOrAcquireMutexEx(phMutex, dfWaitInSeconds,
                                     CPL_MUTEX_RECURSIVE);
}

/************************************************************************/
/*                      CPLCreateOrAcquireMutex()                       */
/************************************************************************/

#ifndef CPL_MULTIPROC_PTHREAD

#ifndef MUTEX_NONE
CPLMutex *CPLCreateUnacquiredMutex()
{
    CPLMutex *hMutex = CPLCreateMutex();
    if (hMutex)
    {
        CPLReleaseMutex(hMutex);
    }
    return hMutex;
}

CPLMutex *&
CPLCreateOrAcquireMasterMutexInternal(double dfWaitInSeconds = 1000.0)
{
    // The dynamic initialization of the block scope hCOAMutex
    // with static storage duration is thread-safe in C++11
    static CPLMutex *hCOAMutex = CPLCreateUnacquiredMutex();

    // WARNING: although adding an CPLAssert(hCOAMutex); might seem logical
    // here, do not enable it (see comment below). It calls CPLError that
    // uses the hCOAMutex itself leading to recursive mutex acquisition
    // and likely a stack overflow.

    if (!hCOAMutex)
    {
        // Fall back to this, ironically, NOT thread-safe re-initialisation of
        // hCOAMutex in case of a memory error or call to CPLCleanupMasterMutex
        // sequenced in an unusual, unexpected or erroneous way.
        // For example, an unusual sequence could be:
        //   GDALDriverManager has been instantiated,
        //   then OGRCleanupAll is called which calls CPLCleanupMasterMutex,
        //   then CPLFreeConfig is called which acquires the hCOAMutex
        //   that has already been released and destroyed.

        hCOAMutex = CPLCreateUnacquiredMutex();
    }

    if (hCOAMutex)
    {
        CPLAcquireMutex(hCOAMutex, dfWaitInSeconds);
    }

    return hCOAMutex;
}

CPLMutex *CPLCreateOrAcquireMasterMutex(double dfWaitInSeconds = 1000.0)
{
    CPLMutex *hCOAMutex =
        CPLCreateOrAcquireMasterMutexInternal(dfWaitInSeconds);
    return hCOAMutex;
}
#endif

#ifdef MUTEX_NONE

int CPLCreateOrAcquireMutexEx(CPLMutex **phMutex, double dfWaitInSeconds,
                              int nOptions)
{
    return false;
}
#else
int CPLCreateOrAcquireMutexEx(CPLMutex **phMutex, double dfWaitInSeconds,
                              int nOptions)
{
    bool bSuccess = false;

    CPLMutex *hCOAMutex = CPLCreateOrAcquireMasterMutex(dfWaitInSeconds);
    if (hCOAMutex == nullptr)
    {
        *phMutex = nullptr;
        return FALSE;
    }

    if (*phMutex == nullptr)
    {
        *phMutex = CPLCreateMutexEx(nOptions);
        bSuccess = *phMutex != nullptr;
        CPLReleaseMutex(hCOAMutex);
    }
    else
    {
        CPLReleaseMutex(hCOAMutex);

        bSuccess = CPL_TO_BOOL(CPLAcquireMutex(*phMutex, dfWaitInSeconds));
    }

    return bSuccess;
}
#endif  // ndef MUTEX_NONE

/************************************************************************/
/*                   CPLCreateOrAcquireMutexInternal()                  */
/************************************************************************/

#ifdef MUTEX_NONE
static int CPLCreateOrAcquireMutexInternal(CPLLock **phLock,
                                           double dfWaitInSeconds,
                                           CPLLockType eType)
{
    return false;
}
#else
static int CPLCreateOrAcquireMutexInternal(CPLLock **phLock,
                                           double dfWaitInSeconds,
                                           CPLLockType eType)

{
    bool bSuccess = false;

    CPLMutex *hCOAMutex = CPLCreateOrAcquireMasterMutex(dfWaitInSeconds);
    if (hCOAMutex == nullptr)
    {
        *phLock = nullptr;
        return FALSE;
    }

    if (*phLock == nullptr)
    {
        *phLock = static_cast<CPLLock *>(calloc(1, sizeof(CPLLock)));
        if (*phLock)
        {
            (*phLock)->eType = eType;
            (*phLock)->u.hMutex = CPLCreateMutexEx(
                (eType == LOCK_RECURSIVE_MUTEX) ? CPL_MUTEX_RECURSIVE
                                                : CPL_MUTEX_ADAPTIVE);
            if ((*phLock)->u.hMutex == nullptr)
            {
                free(*phLock);
                *phLock = nullptr;
            }
        }
        bSuccess = *phLock != nullptr;
        CPLReleaseMutex(hCOAMutex);
    }
    else
    {
        CPLReleaseMutex(hCOAMutex);

        bSuccess =
            CPL_TO_BOOL(CPLAcquireMutex((*phLock)->u.hMutex, dfWaitInSeconds));
    }

    return bSuccess;
}
#endif  // ndef MUTEX_NONE

#endif  // CPL_MULTIPROC_PTHREAD

/************************************************************************/
/*                      CPLCleanupMasterMutex()                         */
/************************************************************************/

void CPLCleanupMasterMutex()
{
#ifndef CPL_MULTIPROC_PTHREAD
#ifndef MUTEX_NONE
    CPLMutex *&hCOAMutex = CPLCreateOrAcquireMasterMutexInternal();
    if (hCOAMutex != nullptr)
    {
        CPLReleaseMutex(hCOAMutex);
        CPLDestroyMutex(hCOAMutex);
        hCOAMutex = nullptr;
    }
#endif
#endif
}

/************************************************************************/
/*                        CPLCleanupTLSList()                           */
/*                                                                      */
/*      Free resources associated with a TLS vector (implementation     */
/*      independent).                                                   */
/************************************************************************/

static void CPLCleanupTLSList(void **papTLSList)

{
#ifdef DEBUG_VERBOSE
    printf("CPLCleanupTLSList(%p)\n", papTLSList); /*ok*/
#endif

    if (papTLSList == nullptr)
        return;

    for (int i = 0; i < CTLS_MAX; i++)
    {
        if (papTLSList[i] != nullptr && papTLSList[i + CTLS_MAX] != nullptr)
        {
            CPLTLSFreeFunc pfnFree =
                reinterpret_cast<CPLTLSFreeFunc>(papTLSList[i + CTLS_MAX]);
            pfnFree(papTLSList[i]);
            papTLSList[i] = nullptr;
        }
    }

    CPLFree(papTLSList);
}

#if defined(CPL_MULTIPROC_STUB)
/************************************************************************/
/* ==================================================================== */
/*                        CPL_MULTIPROC_STUB                            */
/*                                                                      */
/*      Stub implementation.  Mutexes don't provide exclusion, file     */
/*      locking is achieved with extra "lock files", and thread         */
/*      creation doesn't work.  The PID is always just one.             */
/* ==================================================================== */
/************************************************************************/

/************************************************************************/
/*                             CPLGetNumCPUs()                          */
/************************************************************************/

int CPLGetNumCPUs()
{
    return 1;
}

/************************************************************************/
/*                        CPLGetThreadingModel()                        */
/************************************************************************/

const char *CPLGetThreadingModel()

{
    return "stub";
}

/************************************************************************/
/*                           CPLCreateMutex()                           */
/************************************************************************/

#ifdef MUTEX_NONE
CPLMutex *CPLCreateMutex()
{
    return (CPLMutex *)0xdeadbeef;
}
#else
CPLMutex *CPLCreateMutex()
{
    unsigned char *pabyMutex = static_cast<unsigned char *>(malloc(4));
    if (pabyMutex == nullptr)
        return nullptr;

    pabyMutex[0] = 1;
    pabyMutex[1] = 'r';
    pabyMutex[2] = 'e';
    pabyMutex[3] = 'd';

    return (CPLMutex *)pabyMutex;
}
#endif

CPLMutex *CPLCreateMutexEx(int /*nOptions*/)

{
    return CPLCreateMutex();
}

/************************************************************************/
/*                          CPLAcquireMutex()                           */
/************************************************************************/

#ifdef MUTEX_NONE
int CPLAcquireMutex(CPLMutex *hMutex, double /* dfWaitInSeconds */)
{
    return TRUE;
}
#else
int CPLAcquireMutex(CPLMutex *hMutex, double /*dfWaitInSeconds*/)
{
    unsigned char *pabyMutex = reinterpret_cast<unsigned char *>(hMutex);

    CPLAssert(pabyMutex[1] == 'r' && pabyMutex[2] == 'e' &&
              pabyMutex[3] == 'd');

    pabyMutex[0] += 1;

    return TRUE;
}
#endif  // ! MUTEX_NONE

/************************************************************************/
/*                          CPLReleaseMutex()                           */
/************************************************************************/

#ifdef MUTEX_NONE
void CPLReleaseMutex(CPLMutex * /* hMutex */)
{
}
#else
void CPLReleaseMutex(CPLMutex *hMutex)
{
    unsigned char *pabyMutex = reinterpret_cast<unsigned char *>(hMutex);

    CPLAssert(pabyMutex[1] == 'r' && pabyMutex[2] == 'e' &&
              pabyMutex[3] == 'd');

    if (pabyMutex[0] < 1)
        CPLDebug("CPLMultiProc",
                 "CPLReleaseMutex() called on mutex with %d as ref count!",
                 pabyMutex[0]);

    pabyMutex[0] -= 1;
}
#endif

/************************************************************************/
/*                          CPLDestroyMutex()                           */
/************************************************************************/

#ifdef MUTEX_NONE
void CPLDestroyMutex(CPLMutex * /* hMutex */)
{
}
#else
void CPLDestroyMutex(CPLMutex *hMutex)
{
    unsigned char *pabyMutex = reinterpret_cast<unsigned char *>(hMutex);

    CPLAssert(pabyMutex[1] == 'r' && pabyMutex[2] == 'e' &&
              pabyMutex[3] == 'd');

    free(pabyMutex);
}
#endif

/************************************************************************/
/*                            CPLCreateCond()                           */
/************************************************************************/

CPLCond *CPLCreateCond()
{
    return nullptr;
}

/************************************************************************/
/*                            CPLCondWait()                             */
/************************************************************************/

void CPLCondWait(CPLCond * /* hCond */, CPLMutex * /* hMutex */)
{
}

/************************************************************************/
/*                         CPLCondTimedWait()                           */
/************************************************************************/

CPLCondTimedWaitReason CPLCondTimedWait(CPLCond * /* hCond */,
                                        CPLMutex * /* hMutex */, double)
{
    return COND_TIMED_WAIT_OTHER;
}

/************************************************************************/
/*                            CPLCondSignal()                           */
/************************************************************************/

void CPLCondSignal(CPLCond * /* hCond */)
{
}

/************************************************************************/
/*                           CPLCondBroadcast()                         */
/************************************************************************/

void CPLCondBroadcast(CPLCond * /* hCond */)
{
}

/************************************************************************/
/*                            CPLDestroyCond()                          */
/************************************************************************/

void CPLDestroyCond(CPLCond * /* hCond */)
{
}

/************************************************************************/
/*                            CPLLockFile()                             */
/*                                                                      */
/*      Lock a file.  This implementation has a terrible race           */
/*      condition.  If we don't succeed in opening the lock file, we    */
/*      assume we can create one and own the target file, but other     */
/*      processes might easily try creating the target file at the      */
/*      same time, overlapping us.  Death!  Mayhem!  The traditional    */
/*      solution is to use open() with _O_CREAT|_O_EXCL but this        */
/*      function and these arguments aren't trivially portable.         */
/*      Also, this still leaves a race condition on NFS drivers         */
/*      (apparently).                                                   */
/************************************************************************/

void *CPLLockFile(const char *pszPath, double dfWaitInSeconds)

{
    /* -------------------------------------------------------------------- */
    /*      We use a lock file with a name derived from the file we want    */
    /*      to lock to represent the file being locked.  Note that for      */
    /*      the stub implementation the target file does not even need      */
    /*      to exist to be locked.                                          */
    /* -------------------------------------------------------------------- */
    char *pszLockFilename =
        static_cast<char *>(CPLMalloc(strlen(pszPath) + 30));
    snprintf(pszLockFilename, strlen(pszPath) + 30, "%s.lock", pszPath);

    FILE *fpLock = fopen(pszLockFilename, "r");
    while (fpLock != nullptr && dfWaitInSeconds > 0.0)
    {
        fclose(fpLock);
        CPLSleep(std::min(dfWaitInSeconds, 0.5));
        dfWaitInSeconds -= 0.5;

        fpLock = fopen(pszLockFilename, "r");
    }

    if (fpLock != nullptr)
    {
        fclose(fpLock);
        CPLFree(pszLockFilename);
        return nullptr;
    }

    fpLock = fopen(pszLockFilename, "w");

    if (fpLock == nullptr)
    {
        CPLFree(pszLockFilename);
        return nullptr;
    }

    fwrite("held\n", 1, 5, fpLock);
    fclose(fpLock);

    return pszLockFilename;
}

/************************************************************************/
/*                           CPLUnlockFile()                            */
/************************************************************************/

void CPLUnlockFile(void *hLock)

{
    char *pszLockFilename = static_cast<char *>(hLock);

    if (hLock == nullptr)
        return;

    VSIUnlink(pszLockFilename);

    CPLFree(pszLockFilename);
}

/************************************************************************/
/*                             CPLGetPID()                              */
/************************************************************************/

GIntBig CPLGetPID()

{
    return 1;
}

/************************************************************************/
/*                          CPLCreateThread();                          */
/************************************************************************/

int CPLCreateThread(CPLThreadFunc /* pfnMain */, void * /* pArg */)
{
    CPLDebug("CPLCreateThread", "Fails to dummy implementation");

    return -1;
}

/************************************************************************/
/*                      CPLCreateJoinableThread()                       */
/************************************************************************/

CPLJoinableThread *CPLCreateJoinableThread(CPLThreadFunc /* pfnMain */,
                                           void * /* pThreadArg */)
{
    CPLDebug("CPLCreateJoinableThread", "Fails to dummy implementation");

    return nullptr;
}

/************************************************************************/
/*                          CPLJoinThread()                             */
/************************************************************************/

void CPLJoinThread(CPLJoinableThread * /* hJoinableThread */)
{
}

/************************************************************************/
/*                              CPLSleep()                              */
/************************************************************************/

void CPLSleep(double dfWaitInSeconds)
{
    time_t ltime;

    time(&ltime);
    const time_t ttime = ltime + static_cast<int>(dfWaitInSeconds + 0.5);

    for (; ltime < ttime; time(&ltime))
    {
        // Currently we just busy wait.  Perhaps we could at least block on io?
    }
}

/************************************************************************/
/*                           CPLGetTLSList()                            */
/************************************************************************/

static void **papTLSList = nullptr;

static void **CPLGetTLSList(int *pbMemoryErrorOccurred)

{
    if (pbMemoryErrorOccurred)
        *pbMemoryErrorOccurred = FALSE;
    if (papTLSList == nullptr)
    {
        papTLSList =
            static_cast<void **>(VSICalloc(sizeof(void *), CTLS_MAX * 2));
        if (papTLSList == nullptr)
        {
            if (pbMemoryErrorOccurred)
            {
                *pbMemoryErrorOccurred = TRUE;
                fprintf(stderr,
                        "CPLGetTLSList() failed to allocate TLS list!\n");
                return nullptr;
            }
            CPLEmergencyError("CPLGetTLSList() failed to allocate TLS list!");
        }
    }

    return papTLSList;
}

/************************************************************************/
/*                             CPLFinalizeTLS()                         */
/************************************************************************/

void CPLFinalizeTLS()
{
    CPLCleanupTLS();
}

/************************************************************************/
/*                           CPLCleanupTLS()                            */
/************************************************************************/

void CPLCleanupTLS()

{
    CPLCleanupTLSList(papTLSList);
    papTLSList = nullptr;
}

// endif CPL_MULTIPROC_STUB

#elif defined(CPL_MULTIPROC_WIN32)

/************************************************************************/
/* ==================================================================== */
/*                        CPL_MULTIPROC_WIN32                           */
/*                                                                      */
/*    WIN32 Implementation of multiprocessing functions.                */
/* ==================================================================== */
/************************************************************************/

/* InitializeCriticalSectionAndSpinCount requires _WIN32_WINNT >= 0x403 */
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0500

#include <windows.h>

/************************************************************************/
/*                             CPLGetNumCPUs()                          */
/************************************************************************/

int CPLGetNumCPUs()
{
    SYSTEM_INFO info;
    GetSystemInfo(&info);
    const DWORD dwNum = info.dwNumberOfProcessors;
    if (dwNum < 1)
        return 1;
    return static_cast<int>(dwNum);
}

/************************************************************************/
/*                        CPLGetThreadingModel()                        */
/************************************************************************/

const char *CPLGetThreadingModel()

{
    return "win32";
}

/************************************************************************/
/*                           CPLCreateMutex()                           */
/************************************************************************/

CPLMutex *CPLCreateMutex()

{
#ifdef USE_WIN32_MUTEX
    HANDLE hMutex = CreateMutex(nullptr, TRUE, nullptr);

    return (CPLMutex *)hMutex;
#else

    // Do not use CPLMalloc() since its debugging infrastructure
    // can call the CPL*Mutex functions.
    CRITICAL_SECTION *pcs =
        static_cast<CRITICAL_SECTION *>(malloc(sizeof(*pcs)));
    if (pcs)
    {
        InitializeCriticalSectionAndSpinCount(pcs, 4000);
        EnterCriticalSection(pcs);
    }

    return reinterpret_cast<CPLMutex *>(pcs);
#endif
}

CPLMutex *CPLCreateMutexEx(int /* nOptions */)

{
    return CPLCreateMutex();
}

/************************************************************************/
/*                          CPLAcquireMutex()                           */
/************************************************************************/

int CPLAcquireMutex(CPLMutex *hMutexIn, double dfWaitInSeconds)

{
#ifdef USE_WIN32_MUTEX
    HANDLE hMutex = (HANDLE)hMutexIn;
    const DWORD hr =
        WaitForSingleObject(hMutex, static_cast<int>(dfWaitInSeconds * 1000));

    return hr != WAIT_TIMEOUT;
#else
    CRITICAL_SECTION *pcs = reinterpret_cast<CRITICAL_SECTION *>(hMutexIn);
    BOOL ret;

    if (dfWaitInSeconds >= 1000.0)
    {
        // We assume this is the synonymous for infinite, so it is more
        // efficient to use EnterCriticalSection() directly
        EnterCriticalSection(pcs);
        ret = TRUE;
    }
    else
    {
        while ((ret = TryEnterCriticalSection(pcs)) == 0 &&
               dfWaitInSeconds > 0.0)
        {
            CPLSleep(std::min(dfWaitInSeconds, 0.01));
            dfWaitInSeconds -= 0.01;
        }
    }

    return ret;
#endif
}

/************************************************************************/
/*                          CPLReleaseMutex()                           */
/************************************************************************/

void CPLReleaseMutex(CPLMutex *hMutexIn)

{
#ifdef USE_WIN32_MUTEX
    HANDLE hMutex = (HANDLE)hMutexIn;

    ReleaseMutex(hMutex);
#else
    CRITICAL_SECTION *pcs = reinterpret_cast<CRITICAL_SECTION *>(hMutexIn);

    LeaveCriticalSection(pcs);
#endif
}

/************************************************************************/
/*                          CPLDestroyMutex()                           */
/************************************************************************/

void CPLDestroyMutex(CPLMutex *hMutexIn)

{
#ifdef USE_WIN32_MUTEX
    HANDLE hMutex = (HANDLE)hMutexIn;

    CloseHandle(hMutex);
#else
    CRITICAL_SECTION *pcs = reinterpret_cast<CRITICAL_SECTION *>(hMutexIn);

    DeleteCriticalSection(pcs);
    free(pcs);
#endif
}

/************************************************************************/
/*                            CPLCreateCond()                           */
/************************************************************************/

struct _WaiterItem
{
    HANDLE hEvent;
    struct _WaiterItem *psNext;
};
typedef struct _WaiterItem WaiterItem;

typedef struct
{
    CPLMutex *hInternalMutex;
    WaiterItem *psWaiterList;
} Win32Cond;

CPLCond *CPLCreateCond()
{
    Win32Cond *psCond = static_cast<Win32Cond *>(malloc(sizeof(Win32Cond)));
    if (psCond == nullptr)
        return nullptr;
    psCond->hInternalMutex = CPLCreateMutex();
    if (psCond->hInternalMutex == nullptr)
    {
        free(psCond);
        return nullptr;
    }
    CPLReleaseMutex(psCond->hInternalMutex);
    psCond->psWaiterList = nullptr;
    return reinterpret_cast<CPLCond *>(psCond);
}

/************************************************************************/
/*                            CPLCondWait()                             */
/************************************************************************/

static void CPLTLSFreeEvent(void *pData)
{
    CloseHandle(static_cast<HANDLE>(pData));
}

void CPLCondWait(CPLCond *hCond, CPLMutex *hClientMutex)
{
    CPLCondTimedWait(hCond, hClientMutex, -1);
}

/************************************************************************/
/*                         CPLCondTimedWait()                           */
/************************************************************************/

CPLCondTimedWaitReason CPLCondTimedWait(CPLCond *hCond, CPLMutex *hClientMutex,
                                        double dfWaitInSeconds)
{
    Win32Cond *psCond = reinterpret_cast<Win32Cond *>(hCond);

    HANDLE hEvent = static_cast<HANDLE>(CPLGetTLS(CTLS_WIN32_COND));
    if (hEvent == nullptr)
    {
        hEvent = CreateEvent(nullptr, /* security attributes */
                             0,       /* manual reset = no */
                             0,       /* initial state = unsignaled */
                             nullptr /* no name */);
        CPLAssert(hEvent != nullptr);

        CPLSetTLSWithFreeFunc(CTLS_WIN32_COND, hEvent, CPLTLSFreeEvent);
    }

    /* Insert the waiter into the waiter list of the condition */
    CPLAcquireMutex(psCond->hInternalMutex, 1000.0);

    WaiterItem *psItem = static_cast<WaiterItem *>(malloc(sizeof(WaiterItem)));
    CPLAssert(psItem != nullptr);

    psItem->hEvent = hEvent;
    psItem->psNext = psCond->psWaiterList;

    psCond->psWaiterList = psItem;

    CPLReleaseMutex(psCond->hInternalMutex);

    // Release the client mutex before waiting for the event being signaled.
    CPLReleaseMutex(hClientMutex);

    // Ideally we would check that we do not get WAIT_FAILED but it is hard
    // to report a failure.
    auto ret = WaitForSingleObject(
        hEvent, dfWaitInSeconds < 0 ? INFINITE
                                    : static_cast<int>(dfWaitInSeconds * 1000));

    // Reacquire the client mutex.
    CPLAcquireMutex(hClientMutex, 1000.0);

    if (ret == WAIT_OBJECT_0)
        return COND_TIMED_WAIT_COND;
    if (ret == WAIT_TIMEOUT)
        return COND_TIMED_WAIT_TIME_OUT;
    return COND_TIMED_WAIT_OTHER;
}

/************************************************************************/
/*                            CPLCondSignal()                           */
/************************************************************************/

void CPLCondSignal(CPLCond *hCond)
{
    Win32Cond *psCond = reinterpret_cast<Win32Cond *>(hCond);

    // Signal the first registered event, and remove it from the list.
    CPLAcquireMutex(psCond->hInternalMutex, 1000.0);

    WaiterItem *psIter = psCond->psWaiterList;
    if (psIter != nullptr)
    {
        SetEvent(psIter->hEvent);
        psCond->psWaiterList = psIter->psNext;
        free(psIter);
    }

    CPLReleaseMutex(psCond->hInternalMutex);
}

/************************************************************************/
/*                           CPLCondBroadcast()                         */
/************************************************************************/

void CPLCondBroadcast(CPLCond *hCond)
{
    Win32Cond *psCond = reinterpret_cast<Win32Cond *>(hCond);

    // Signal all the registered events, and remove them from the list.
    CPLAcquireMutex(psCond->hInternalMutex, 1000.0);

    WaiterItem *psIter = psCond->psWaiterList;
    while (psIter != nullptr)
    {
        WaiterItem *psNext = psIter->psNext;
        SetEvent(psIter->hEvent);
        free(psIter);
        psIter = psNext;
    }
    psCond->psWaiterList = nullptr;

    CPLReleaseMutex(psCond->hInternalMutex);
}

/************************************************************************/
/*                            CPLDestroyCond()                          */
/************************************************************************/

void CPLDestroyCond(CPLCond *hCond)
{
    Win32Cond *psCond = reinterpret_cast<Win32Cond *>(hCond);
    CPLDestroyMutex(psCond->hInternalMutex);
    psCond->hInternalMutex = nullptr;
    CPLAssert(psCond->psWaiterList == nullptr);
    free(psCond);
}

/************************************************************************/
/*                            CPLLockFile()                             */
/************************************************************************/

void *CPLLockFile(const char *pszPath, double dfWaitInSeconds)

{
    char *pszLockFilename =
        static_cast<char *>(CPLMalloc(strlen(pszPath) + 30));
    snprintf(pszLockFilename, strlen(pszPath) + 30, "%s.lock", pszPath);

    // FIXME: use CreateFileW()
    HANDLE hLockFile =
        CreateFileA(pszLockFilename, GENERIC_WRITE, 0, nullptr, CREATE_NEW,
                    FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, nullptr);

    while (GetLastError() == ERROR_ALREADY_EXISTS && dfWaitInSeconds > 0.0)
    {
        CloseHandle(hLockFile);
        CPLSleep(std::min(dfWaitInSeconds, 0.125));
        dfWaitInSeconds -= 0.125;

        hLockFile = CreateFileA(
            pszLockFilename, GENERIC_WRITE, 0, nullptr, CREATE_NEW,
            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
    }

    CPLFree(pszLockFilename);

    if (hLockFile == INVALID_HANDLE_VALUE)
        return nullptr;

    if (GetLastError() == ERROR_ALREADY_EXISTS)
    {
        CloseHandle(hLockFile);
        return nullptr;
    }

    return static_cast<void *>(hLockFile);
}

/************************************************************************/
/*                           CPLUnlockFile()                            */
/************************************************************************/

void CPLUnlockFile(void *hLock)

{
    HANDLE hLockFile = static_cast<HANDLE>(hLock);

    CloseHandle(hLockFile);
}

/************************************************************************/
/*                             CPLGetPID()                              */
/************************************************************************/

GIntBig CPLGetPID()

{
    return static_cast<GIntBig>(GetCurrentThreadId());
}

/************************************************************************/
/*                       CPLStdCallThreadJacket()                       */
/************************************************************************/

typedef struct
{
    void *pAppData;
    CPLThreadFunc pfnMain;
    HANDLE hThread;
} CPLStdCallThreadInfo;

static DWORD WINAPI CPLStdCallThreadJacket(void *pData)

{
    CPLStdCallThreadInfo *psInfo = static_cast<CPLStdCallThreadInfo *>(pData);

    psInfo->pfnMain(psInfo->pAppData);

    if (psInfo->hThread == nullptr)
        CPLFree(psInfo);  // Only for detached threads.

    CPLCleanupTLS();

    return 0;
}

/************************************************************************/
/*                          CPLCreateThread()                           */
/*                                                                      */
/*      The WIN32 CreateThread() call requires an entry point that      */
/*      has __stdcall conventions, so we provide a jacket function      */
/*      to supply that.                                                 */
/************************************************************************/

int CPLCreateThread(CPLThreadFunc pfnMain, void *pThreadArg)

{
    CPLStdCallThreadInfo *psInfo = static_cast<CPLStdCallThreadInfo *>(
        CPLCalloc(sizeof(CPLStdCallThreadInfo), 1));
    psInfo->pAppData = pThreadArg;
    psInfo->pfnMain = pfnMain;
    psInfo->hThread = nullptr;

    DWORD nThreadId = 0;
    HANDLE hThread =
        CreateThread(nullptr, 0, CPLStdCallThreadJacket, psInfo, 0, &nThreadId);

    if (hThread == nullptr)
        return -1;

    CloseHandle(hThread);

    return nThreadId;
}

/************************************************************************/
/*                      CPLCreateJoinableThread()                       */
/************************************************************************/

CPLJoinableThread *CPLCreateJoinableThread(CPLThreadFunc pfnMain,
                                           void *pThreadArg)

{
    CPLStdCallThreadInfo *psInfo = static_cast<CPLStdCallThreadInfo *>(
        CPLCalloc(sizeof(CPLStdCallThreadInfo), 1));
    psInfo->pAppData = pThreadArg;
    psInfo->pfnMain = pfnMain;

    DWORD nThreadId = 0;
    HANDLE hThread =
        CreateThread(nullptr, 0, CPLStdCallThreadJacket, psInfo, 0, &nThreadId);

    if (hThread == nullptr)
        return nullptr;

    psInfo->hThread = hThread;
    return reinterpret_cast<CPLJoinableThread *>(psInfo);
}

/************************************************************************/
/*                          CPLJoinThread()                             */
/************************************************************************/

void CPLJoinThread(CPLJoinableThread *hJoinableThread)
{
    CPLStdCallThreadInfo *psInfo =
        reinterpret_cast<CPLStdCallThreadInfo *>(hJoinableThread);

    WaitForSingleObject(psInfo->hThread, INFINITE);
    CloseHandle(psInfo->hThread);
    CPLFree(psInfo);
}

/************************************************************************/
/*                              CPLSleep()                              */
/************************************************************************/

void CPLSleep(double dfWaitInSeconds)

{
    Sleep(static_cast<DWORD>(dfWaitInSeconds * 1000.0));
}

static bool bTLSKeySetup = false;
static DWORD nTLSKey = 0;

/************************************************************************/
/*                           CPLGetTLSList()                            */
/************************************************************************/

static void **CPLGetTLSList(int *pbMemoryErrorOccurred)

{
    void **papTLSList = nullptr;

    if (pbMemoryErrorOccurred)
        *pbMemoryErrorOccurred = FALSE;
    if (!bTLSKeySetup)
    {
        nTLSKey = TlsAlloc();
        if (nTLSKey == TLS_OUT_OF_INDEXES)
        {
            if (pbMemoryErrorOccurred)
            {
                *pbMemoryErrorOccurred = TRUE;
                fprintf(stderr, "CPLGetTLSList(): TlsAlloc() failed!\n");
                return nullptr;
            }
            CPLEmergencyError("CPLGetTLSList(): TlsAlloc() failed!");
        }
        bTLSKeySetup = true;
    }

    papTLSList = static_cast<void **>(TlsGetValue(nTLSKey));
    if (papTLSList == nullptr)
    {
        papTLSList =
            static_cast<void **>(VSICalloc(sizeof(void *), CTLS_MAX * 2));
        if (papTLSList == nullptr)
        {
            if (pbMemoryErrorOccurred)
            {
                *pbMemoryErrorOccurred = TRUE;
                fprintf(stderr,
                        "CPLGetTLSList() failed to allocate TLS list!\n");
                return nullptr;
            }
            CPLEmergencyError("CPLGetTLSList() failed to allocate TLS list!");
        }
        if (TlsSetValue(nTLSKey, papTLSList) == 0)
        {
            if (pbMemoryErrorOccurred)
            {
                *pbMemoryErrorOccurred = TRUE;
                fprintf(stderr, "CPLGetTLSList(): TlsSetValue() failed!\n");
                return nullptr;
            }
            CPLEmergencyError("CPLGetTLSList(): TlsSetValue() failed!");
        }
    }

    return papTLSList;
}

/************************************************************************/
/*                             CPLFinalizeTLS()                         */
/************************************************************************/

void CPLFinalizeTLS()
{
    CPLCleanupTLS();
}

/************************************************************************/
/*                           CPLCleanupTLS()                            */
/************************************************************************/

void CPLCleanupTLS()

{
    if (!bTLSKeySetup)
        return;

    void **papTLSList = static_cast<void **>(TlsGetValue(nTLSKey));
    if (papTLSList == nullptr)
        return;

    TlsSetValue(nTLSKey, nullptr);

    CPLCleanupTLSList(papTLSList);
}

// endif CPL_MULTIPROC_WIN32

#elif defined(CPL_MULTIPROC_PTHREAD)

#include <pthread.h>
#include <time.h>
#include <unistd.h>
#include <sys/time.h>

#ifdef HAVE_SCHED_GETAFFINITY
#include <sched.h>
#endif

/************************************************************************/
/* ==================================================================== */
/*                        CPL_MULTIPROC_PTHREAD                         */
/*                                                                      */
/*    PTHREAD Implementation of multiprocessing functions.              */
/* ==================================================================== */
/************************************************************************/

/************************************************************************/
/*                             CPLGetNumCPUs()                          */
/************************************************************************/

int CPLGetNumCPUs()
{
    int nCPUs;
#ifdef _SC_NPROCESSORS_ONLN
    nCPUs = static_cast<int>(sysconf(_SC_NPROCESSORS_ONLN));
#else
    nCPUs = 1;
#endif

#ifdef HAVE_SCHED_GETAFFINITY
    if (nCPUs > 1)
    {
        cpu_set_t *set = CPU_ALLOC(nCPUs);
        if (set)
        {
            size_t sizeof_set = CPU_ALLOC_SIZE(nCPUs);
            CPU_ZERO_S(sizeof_set, set);
            if (sched_getaffinity(getpid(), sizeof_set, set) == 0)
                nCPUs = CPU_COUNT_S(sizeof_set, set);
            else
                CPLDebug("CPL", "sched_getaffinity() failed");
            CPU_FREE(set);
        }
    }
#endif

    return nCPUs;
}

/************************************************************************/
/*                      CPLCreateOrAcquireMutex()                       */
/************************************************************************/
#ifdef HAVE_GCC_WARNING_ZERO_AS_NULL_POINTER_CONSTANT
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
#endif
static pthread_mutex_t global_mutex = PTHREAD_MUTEX_INITIALIZER;
#ifdef HAVE_GCC_WARNING_ZERO_AS_NULL_POINTER_CONSTANT
#pragma GCC diagnostic pop
#endif

static CPLMutex *CPLCreateMutexInternal(bool bAlreadyInGlobalLock,
                                        int nOptions);

int CPLCreateOrAcquireMutexEx(CPLMutex **phMutex, double dfWaitInSeconds,
                              int nOptions)

{
    bool bSuccess = false;

    pthread_mutex_lock(&global_mutex);
    if (*phMutex == nullptr)
    {
        *phMutex = CPLCreateMutexInternal(true, nOptions);
        bSuccess = *phMutex != nullptr;
        pthread_mutex_unlock(&global_mutex);
    }
    else
    {
        pthread_mutex_unlock(&global_mutex);

        bSuccess = CPL_TO_BOOL(CPLAcquireMutex(*phMutex, dfWaitInSeconds));
    }

    return bSuccess;
}

/************************************************************************/
/*                   CPLCreateOrAcquireMutexInternal()                  */
/************************************************************************/

static int CPLCreateOrAcquireMutexInternal(CPLLock **phLock,
                                           double dfWaitInSeconds,
                                           CPLLockType eType)
{
    bool bSuccess = false;

    pthread_mutex_lock(&global_mutex);
    if (*phLock == nullptr)
    {
        *phLock = static_cast<CPLLock *>(calloc(1, sizeof(CPLLock)));
        if (*phLock)
        {
            (*phLock)->eType = eType;
            (*phLock)->u.hMutex = CPLCreateMutexInternal(
                true, eType == LOCK_RECURSIVE_MUTEX ? CPL_MUTEX_RECURSIVE
                                                    : CPL_MUTEX_ADAPTIVE);
            if ((*phLock)->u.hMutex == nullptr)
            {
                free(*phLock);
                *phLock = nullptr;
            }
        }
        bSuccess = *phLock != nullptr;
        pthread_mutex_unlock(&global_mutex);
    }
    else
    {
        pthread_mutex_unlock(&global_mutex);

        bSuccess =
            CPL_TO_BOOL(CPLAcquireMutex((*phLock)->u.hMutex, dfWaitInSeconds));
    }

    return bSuccess;
}

/************************************************************************/
/*                        CPLGetThreadingModel()                        */
/************************************************************************/

const char *CPLGetThreadingModel()

{
    return "pthread";
}

/************************************************************************/
/*                           CPLCreateMutex()                           */
/************************************************************************/

typedef struct _MutexLinkedElt MutexLinkedElt;
struct _MutexLinkedElt
{
    pthread_mutex_t sMutex;
    int nOptions;
    _MutexLinkedElt *psPrev;
    _MutexLinkedElt *psNext;
};
static MutexLinkedElt *psMutexList = nullptr;

static void CPLInitMutex(MutexLinkedElt *psItem)
{
    if (psItem->nOptions == CPL_MUTEX_REGULAR)
    {
#ifdef HAVE_GCC_WARNING_ZERO_AS_NULL_POINTER_CONSTANT
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
#endif
        pthread_mutex_t tmp_mutex = PTHREAD_MUTEX_INITIALIZER;
#ifdef HAVE_GCC_WARNING_ZERO_AS_NULL_POINTER_CONSTANT
#pragma GCC diagnostic pop
#endif
        psItem->sMutex = tmp_mutex;
        return;
    }

    // When an adaptive mutex is required, we can safely fallback to regular
    // mutex if we don't have HAVE_PTHREAD_MUTEX_ADAPTIVE_NP.
    if (psItem->nOptions == CPL_MUTEX_ADAPTIVE)
    {
#if defined(HAVE_PTHREAD_MUTEX_ADAPTIVE_NP)
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ADAPTIVE_NP);
        pthread_mutex_init(&(psItem->sMutex), &attr);
#else
        pthread_mutex_t tmp_mutex = PTHREAD_MUTEX_INITIALIZER;
        psItem->sMutex = tmp_mutex;
#endif
        return;
    }

#if defined(PTHREAD_MUTEX_RECURSIVE) || defined(HAVE_PTHREAD_MUTEX_RECURSIVE)
    {
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
        pthread_mutex_init(&(psItem->sMutex), &attr);
    }
// BSDs have PTHREAD_MUTEX_RECURSIVE as an enum, not a define.
// But they have #define MUTEX_TYPE_COUNTING_FAST PTHREAD_MUTEX_RECURSIVE
#elif defined(MUTEX_TYPE_COUNTING_FAST)
    {
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, MUTEX_TYPE_COUNTING_FAST);
        pthread_mutex_init(&(psItem->sMutex), &attr);
    }
#elif defined(PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP)
    pthread_mutex_t tmp_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
    psItem->sMutex = tmp_mutex;
#else
#error "Recursive mutexes apparently unsupported, configure --without-threads"
#endif
}

static CPLMutex *CPLCreateMutexInternal(bool bAlreadyInGlobalLock, int nOptions)
{
    MutexLinkedElt *psItem =
        static_cast<MutexLinkedElt *>(malloc(sizeof(MutexLinkedElt)));
    if (psItem == nullptr)
    {
        fprintf(stderr, "CPLCreateMutexInternal() failed.\n");
        return nullptr;
    }

    if (!bAlreadyInGlobalLock)
        pthread_mutex_lock(&global_mutex);
    psItem->psPrev = nullptr;
    psItem->psNext = psMutexList;
    if (psMutexList)
        psMutexList->psPrev = psItem;
    psMutexList = psItem;
    if (!bAlreadyInGlobalLock)
        pthread_mutex_unlock(&global_mutex);

    psItem->nOptions = nOptions;
    CPLInitMutex(psItem);

    // Mutexes are implicitly acquired when created.
    CPLAcquireMutex(reinterpret_cast<CPLMutex *>(psItem), 0.0);

    return reinterpret_cast<CPLMutex *>(psItem);
}

CPLMutex *CPLCreateMutex()
{
    return CPLCreateMutexInternal(false, CPL_MUTEX_RECURSIVE);
}

CPLMutex *CPLCreateMutexEx(int nOptions)
{
    return CPLCreateMutexInternal(false, nOptions);
}

/************************************************************************/
/*                          CPLAcquireMutex()                           */
/************************************************************************/

int CPLAcquireMutex(CPLMutex *hMutexIn, double /* dfWaitInSeconds */)
{
    // TODO: Need to add timeout support.
    MutexLinkedElt *psItem = reinterpret_cast<MutexLinkedElt *>(hMutexIn);
    const int err = pthread_mutex_lock(&(psItem->sMutex));

    if (err != 0)
    {
        if (err == EDEADLK)
            fprintf(stderr, "CPLAcquireMutex: Error = %d/EDEADLK\n", err);
        else
            fprintf(stderr, "CPLAcquireMutex: Error = %d (%s)\n", err,
                    strerror(err));

        return FALSE;
    }

    return TRUE;
}

/************************************************************************/
/*                          CPLReleaseMutex()                           */
/************************************************************************/

void CPLReleaseMutex(CPLMutex *hMutexIn)

{
    MutexLinkedElt *psItem = reinterpret_cast<MutexLinkedElt *>(hMutexIn);
    const int err = pthread_mutex_unlock(&(psItem->sMutex));
    if (err != 0)
    {
        fprintf(stderr, "CPLReleaseMutex: Error = %d (%s)\n", err,
                strerror(err));
    }
}

/************************************************************************/
/*                          CPLDestroyMutex()                           */
/************************************************************************/

void CPLDestroyMutex(CPLMutex *hMutexIn)

{
    MutexLinkedElt *psItem = reinterpret_cast<MutexLinkedElt *>(hMutexIn);
    const int err = pthread_mutex_destroy(&(psItem->sMutex));
    if (err != 0)
    {
        fprintf(stderr, "CPLDestroyMutex: Error = %d (%s)\n", err,
                strerror(err));
    }
    pthread_mutex_lock(&global_mutex);
    if (psItem->psPrev)
        psItem->psPrev->psNext = psItem->psNext;
    if (psItem->psNext)
        psItem->psNext->psPrev = psItem->psPrev;
    if (psItem == psMutexList)
        psMutexList = psItem->psNext;
    pthread_mutex_unlock(&global_mutex);
    free(hMutexIn);
}

/************************************************************************/
/*                          CPLReinitAllMutex()                         */
/************************************************************************/

// Used by gdalclientserver.cpp just after forking, to avoid
// deadlocks while mixing threads with fork.
void CPLReinitAllMutex();  // TODO(schwehr): Put this in a header.
void CPLReinitAllMutex()
{
    MutexLinkedElt *psItem = psMutexList;
    while (psItem != nullptr)
    {
        CPLInitMutex(psItem);
        psItem = psItem->psNext;
    }
#ifdef HAVE_GCC_WARNING_ZERO_AS_NULL_POINTER_CONSTANT
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
#endif
    pthread_mutex_t tmp_global_mutex = PTHREAD_MUTEX_INITIALIZER;
#ifdef HAVE_GCC_WARNING_ZERO_AS_NULL_POINTER_CONSTANT
#pragma GCC diagnostic pop
#endif
    global_mutex = tmp_global_mutex;
}

/************************************************************************/
/*                            CPLCreateCond()                           */
/************************************************************************/

CPLCond *CPLCreateCond()
{
    pthread_cond_t *pCond =
        static_cast<pthread_cond_t *>(malloc(sizeof(pthread_cond_t)));
    if (pCond && pthread_cond_init(pCond, nullptr) == 0)
        return reinterpret_cast<CPLCond *>(pCond);
    fprintf(stderr, "CPLCreateCond() failed.\n");
    free(pCond);
    return nullptr;
}

/************************************************************************/
/*                            CPLCondWait()                             */
/************************************************************************/

void CPLCondWait(CPLCond *hCond, CPLMutex *hMutex)
{
    pthread_cond_t *pCond = reinterpret_cast<pthread_cond_t *>(hCond);
    MutexLinkedElt *psItem = reinterpret_cast<MutexLinkedElt *>(hMutex);
    pthread_mutex_t *pMutex = &(psItem->sMutex);
    pthread_cond_wait(pCond, pMutex);
}

/************************************************************************/
/*                         CPLCondTimedWait()                           */
/************************************************************************/

CPLCondTimedWaitReason CPLCondTimedWait(CPLCond *hCond, CPLMutex *hMutex,
                                        double dfWaitInSeconds)
{
    pthread_cond_t *pCond = reinterpret_cast<pthread_cond_t *>(hCond);
    MutexLinkedElt *psItem = reinterpret_cast<MutexLinkedElt *>(hMutex);
    pthread_mutex_t *pMutex = &(psItem->sMutex);
    struct timeval tv;
    struct timespec ts;

    gettimeofday(&tv, nullptr);
    ts.tv_sec = time(nullptr) + static_cast<int>(dfWaitInSeconds);
    ts.tv_nsec = tv.tv_usec * 1000 + static_cast<int>(1000 * 1000 * 1000 *
                                                      fmod(dfWaitInSeconds, 1));
    ts.tv_sec += ts.tv_nsec / (1000 * 1000 * 1000);
    ts.tv_nsec %= (1000 * 1000 * 1000);
    int ret = pthread_cond_timedwait(pCond, pMutex, &ts);
    if (ret == 0)
        return COND_TIMED_WAIT_COND;
    else if (ret == ETIMEDOUT)
        return COND_TIMED_WAIT_TIME_OUT;
    else
        return COND_TIMED_WAIT_OTHER;
}

/************************************************************************/
/*                            CPLCondSignal()                           */
/************************************************************************/

void CPLCondSignal(CPLCond *hCond)
{
    pthread_cond_t *pCond = reinterpret_cast<pthread_cond_t *>(hCond);
    pthread_cond_signal(pCond);
}

/************************************************************************/
/*                           CPLCondBroadcast()                         */
/************************************************************************/

void CPLCondBroadcast(CPLCond *hCond)
{
    pthread_cond_t *pCond = reinterpret_cast<pthread_cond_t *>(hCond);
    pthread_cond_broadcast(pCond);
}

/************************************************************************/
/*                            CPLDestroyCond()                          */
/************************************************************************/

void CPLDestroyCond(CPLCond *hCond)
{
    pthread_cond_t *pCond = reinterpret_cast<pthread_cond_t *>(hCond);
    pthread_cond_destroy(pCond);
    free(hCond);
}

/************************************************************************/
/*                            CPLLockFile()                             */
/*                                                                      */
/*      This is really a stub implementation, see first                 */
/*      CPLLockFile() for caveats.                                      */
/************************************************************************/

void *CPLLockFile(const char *pszPath, double dfWaitInSeconds)

{
    /* -------------------------------------------------------------------- */
    /*      We use a lock file with a name derived from the file we want    */
    /*      to lock to represent the file being locked.  Note that for      */
    /*      the stub implementation the target file does not even need      */
    /*      to exist to be locked.                                          */
    /* -------------------------------------------------------------------- */
    const size_t nLen = strlen(pszPath) + 30;
    char *pszLockFilename = static_cast<char *>(CPLMalloc(nLen));
    snprintf(pszLockFilename, nLen, "%s.lock", pszPath);

    FILE *fpLock = fopen(pszLockFilename, "r");
    while (fpLock != nullptr && dfWaitInSeconds > 0.0)
    {
        fclose(fpLock);
        CPLSleep(std::min(dfWaitInSeconds, 0.5));
        dfWaitInSeconds -= 0.5;

        fpLock = fopen(pszLockFilename, "r");
    }

    if (fpLock != nullptr)
    {
        fclose(fpLock);
        CPLFree(pszLockFilename);
        return nullptr;
    }

    fpLock = fopen(pszLockFilename, "w");

    if (fpLock == nullptr)
    {
        CPLFree(pszLockFilename);
        return nullptr;
    }

    fwrite("held\n", 1, 5, fpLock);
    fclose(fpLock);

    return pszLockFilename;
}

/************************************************************************/
/*                           CPLUnlockFile()                            */
/************************************************************************/

void CPLUnlockFile(void *hLock)

{
    char *pszLockFilename = static_cast<char *>(hLock);

    if (hLock == nullptr)
        return;

    VSIUnlink(pszLockFilename);

    CPLFree(pszLockFilename);
}

/************************************************************************/
/*                             CPLGetPID()                              */
/************************************************************************/

GIntBig CPLGetPID()

{
    return reinterpret_cast<GIntBig>(reinterpret_cast<void *>(pthread_self()));
}

static pthread_key_t oTLSKey;
static pthread_once_t oTLSKeySetup = PTHREAD_ONCE_INIT;

/************************************************************************/
/*                             CPLMake_key()                            */
/************************************************************************/

static void CPLMake_key()

{
    if (pthread_key_create(&oTLSKey, reinterpret_cast<void (*)(void *)>(
                                         CPLCleanupTLSList)) != 0)
    {
        CPLError(CE_Fatal, CPLE_AppDefined, "pthread_key_create() failed!");
    }
}

/************************************************************************/
/*                           CPLGetTLSList()                            */
/************************************************************************/

static void **CPLGetTLSList(int *pbMemoryErrorOccurred)

{
    if (pbMemoryErrorOccurred)
        *pbMemoryErrorOccurred = FALSE;

    if (pthread_once(&oTLSKeySetup, CPLMake_key) != 0)
    {
        if (pbMemoryErrorOccurred)
        {
            fprintf(stderr, "CPLGetTLSList(): pthread_once() failed!\n");
            *pbMemoryErrorOccurred = TRUE;
            return nullptr;
        }
        CPLEmergencyError("CPLGetTLSList(): pthread_once() failed!");
    }

    void **papTLSList = static_cast<void **>(pthread_getspecific(oTLSKey));
    if (papTLSList == nullptr)
    {
        papTLSList =
            static_cast<void **>(VSICalloc(sizeof(void *), CTLS_MAX * 2));
        if (papTLSList == nullptr)
        {
            if (pbMemoryErrorOccurred)
            {
                fprintf(stderr,
                        "CPLGetTLSList() failed to allocate TLS list!\n");
                *pbMemoryErrorOccurred = TRUE;
                return nullptr;
            }
            CPLEmergencyError("CPLGetTLSList() failed to allocate TLS list!");
        }
        if (pthread_setspecific(oTLSKey, papTLSList) != 0)
        {
            if (pbMemoryErrorOccurred)
            {
                fprintf(stderr,
                        "CPLGetTLSList(): pthread_setspecific() failed!\n");
                *pbMemoryErrorOccurred = TRUE;
                return nullptr;
            }
            CPLEmergencyError("CPLGetTLSList(): pthread_setspecific() failed!");
        }
    }

    return papTLSList;
}

/************************************************************************/
/*                       CPLStdCallThreadJacket()                       */
/************************************************************************/

typedef struct
{
    void *pAppData;
    CPLThreadFunc pfnMain;
    pthread_t hThread;
    bool bJoinable;
#ifdef CHECK_THREAD_CAN_ALLOCATE_TLS
    bool bInitSucceeded;
    bool bInitDone;
    pthread_mutex_t sMutex;
    pthread_cond_t sCond;
#endif
} CPLStdCallThreadInfo;

static void *CPLStdCallThreadJacket(void *pData)

{
    CPLStdCallThreadInfo *psInfo = static_cast<CPLStdCallThreadInfo *>(pData);

#ifdef CHECK_THREAD_CAN_ALLOCATE_TLS
    int bMemoryError = FALSE;
    CPLGetTLSList(&bMemoryError);
    if (bMemoryError)
        goto error;

    assert(pthread_mutex_lock(&(psInfo->sMutex)) == 0);
    psInfo->bInitDone = true;
    assert(pthread_cond_signal(&(psInfo->sCond)) == 0);
    assert(pthread_mutex_unlock(&(psInfo->sMutex)) == 0);
#endif

    psInfo->pfnMain(psInfo->pAppData);

    if (!psInfo->bJoinable)
        CPLFree(psInfo);

    return nullptr;

#ifdef CHECK_THREAD_CAN_ALLOCATE_TLS
error:
    assert(pthread_mutex_lock(&(psInfo->sMutex)) == 0);
    psInfo->bInitSucceeded = false;
    psInfo->bInitDone = true;
    assert(pthread_cond_signal(&(psInfo->sCond)) == 0);
    assert(pthread_mutex_unlock(&(psInfo->sMutex)) == 0);
    return nullptr;
#endif
}

/************************************************************************/
/*                          CPLCreateThread()                           */
/*                                                                      */
/*      The WIN32 CreateThread() call requires an entry point that      */
/*      has __stdcall conventions, so we provide a jacket function      */
/*      to supply that.                                                 */
/************************************************************************/

int CPLCreateThread(CPLThreadFunc pfnMain, void *pThreadArg)

{
    CPLStdCallThreadInfo *psInfo = static_cast<CPLStdCallThreadInfo *>(
        VSI_CALLOC_VERBOSE(sizeof(CPLStdCallThreadInfo), 1));
    if (psInfo == nullptr)
        return -1;
    psInfo->pAppData = pThreadArg;
    psInfo->pfnMain = pfnMain;
    psInfo->bJoinable = false;
#ifdef CHECK_THREAD_CAN_ALLOCATE_TLS
    psInfo->bInitSucceeded = true;
    psInfo->bInitDone = false;
    pthread_mutex_t sMutex = PTHREAD_MUTEX_INITIALIZER;
    psInfo->sMutex = sMutex;
    if (pthread_cond_init(&(psInfo->sCond), nullptr) != 0)
    {
        CPLFree(psInfo);
        fprintf(stderr, "CPLCreateThread() failed.\n");
        return -1;
    }
#endif

    pthread_attr_t hThreadAttr;
    pthread_attr_init(&hThreadAttr);
    pthread_attr_setdetachstate(&hThreadAttr, PTHREAD_CREATE_DETACHED);
    if (pthread_create(&(psInfo->hThread), &hThreadAttr, CPLStdCallThreadJacket,
                       static_cast<void *>(psInfo)) != 0)
    {
#ifdef CHECK_THREAD_CAN_ALLOCATE_TLS
        pthread_cond_destroy(&(psInfo->sCond));
#endif
        CPLFree(psInfo);
        fprintf(stderr, "CPLCreateThread() failed.\n");
        return -1;
    }

#ifdef CHECK_THREAD_CAN_ALLOCATE_TLS
    bool bInitSucceeded;
    while (true)
    {
        assert(pthread_mutex_lock(&(psInfo->sMutex)) == 0);
        bool bInitDone = psInfo->bInitDone;
        if (!bInitDone)
            assert(pthread_cond_wait(&(psInfo->sCond), &(psInfo->sMutex)) == 0);
        bInitSucceeded = psInfo->bInitSucceeded;
        assert(pthread_mutex_unlock(&(psInfo->sMutex)) == 0);
        if (bInitDone)
            break;
    }

    pthread_cond_destroy(&(psInfo->sCond));

    if (!bInitSucceeded)
    {
        CPLFree(psInfo);
        fprintf(stderr, "CPLCreateThread() failed.\n");
        return -1;
    }
#endif

    return 1;  // Can we return the actual thread pid?
}

/************************************************************************/
/*                      CPLCreateJoinableThread()                       */
/************************************************************************/

CPLJoinableThread *CPLCreateJoinableThread(CPLThreadFunc pfnMain,
                                           void *pThreadArg)

{
    CPLStdCallThreadInfo *psInfo = static_cast<CPLStdCallThreadInfo *>(
        VSI_CALLOC_VERBOSE(sizeof(CPLStdCallThreadInfo), 1));
    if (psInfo == nullptr)
        return nullptr;
    psInfo->pAppData = pThreadArg;
    psInfo->pfnMain = pfnMain;
    psInfo->bJoinable = true;
#ifdef CHECK_THREAD_CAN_ALLOCATE_TLS
    psInfo->bInitSucceeded = true;
    psInfo->bInitDone = false;
    pthread_mutex_t sMutex = PTHREAD_MUTEX_INITIALIZER;
    psInfo->sMutex = sMutex;
    {
        int err = pthread_cond_init(&(psInfo->sCond), nullptr);
        if (err != 0)
        {
            CPLFree(psInfo);
            fprintf(stderr, "CPLCreateJoinableThread() failed: %s.\n",
                    strerror(err));
            return nullptr;
        }
    }
#endif

    pthread_attr_t hThreadAttr;
    pthread_attr_init(&hThreadAttr);
    pthread_attr_setdetachstate(&hThreadAttr, PTHREAD_CREATE_JOINABLE);
    int err =
        pthread_create(&(psInfo->hThread), &hThreadAttr, CPLStdCallThreadJacket,
                       static_cast<void *>(psInfo));
    if (err != 0)
    {
#ifdef CHECK_THREAD_CAN_ALLOCATE_TLS
        pthread_cond_destroy(&(psInfo->sCond));
#endif
        CPLFree(psInfo);
        fprintf(stderr, "CPLCreateJoinableThread() failed: %s.\n",
                strerror(err));
        return nullptr;
    }

#ifdef CHECK_THREAD_CAN_ALLOCATE_TLS
    bool bInitSucceeded;
    while (true)
    {
        assert(pthread_mutex_lock(&(psInfo->sMutex)) == 0);
        bool bInitDone = psInfo->bInitDone;
        if (!bInitDone)
            assert(pthread_cond_wait(&(psInfo->sCond), &(psInfo->sMutex)) == 0);
        bInitSucceeded = psInfo->bInitSucceeded;
        assert(pthread_mutex_unlock(&(psInfo->sMutex)) == 0);
        if (bInitDone)
            break;
    }

    pthread_cond_destroy(&(psInfo->sCond));

    if (!bInitSucceeded)
    {
        void *status;
        pthread_join(psInfo->hThread, &status);
        CPLFree(psInfo);
        fprintf(stderr, "CPLCreateJoinableThread() failed.\n");
        return nullptr;
    }
#endif

    return reinterpret_cast<CPLJoinableThread *>(psInfo);
}

/************************************************************************/
/*                          CPLJoinThread()                             */
/************************************************************************/

void CPLJoinThread(CPLJoinableThread *hJoinableThread)
{
    CPLStdCallThreadInfo *psInfo =
        reinterpret_cast<CPLStdCallThreadInfo *>(hJoinableThread);
    if (psInfo == nullptr)
        return;

    void *status;
    pthread_join(psInfo->hThread, &status);

    CPLFree(psInfo);
}

/************************************************************************/
/*                              CPLSleep()                              */
/************************************************************************/

void CPLSleep(double dfWaitInSeconds)

{
    struct timespec sRequest;
    struct timespec sRemain;

    sRequest.tv_sec = static_cast<int>(floor(dfWaitInSeconds));
    sRequest.tv_nsec =
        static_cast<int>((dfWaitInSeconds - sRequest.tv_sec) * 1000000000);
    nanosleep(&sRequest, &sRemain);
}

/************************************************************************/
/*                             CPLFinalizeTLS()                         */
/************************************************************************/

void CPLFinalizeTLS()
{
    CPLCleanupTLS();
    // See #5509 for the explanation why this may be needed.
    pthread_key_delete(oTLSKey);
}

/************************************************************************/
/*                             CPLCleanupTLS()                          */
/************************************************************************/

void CPLCleanupTLS()

{
    void **papTLSList = static_cast<void **>(pthread_getspecific(oTLSKey));
    if (papTLSList == nullptr)
        return;

    pthread_setspecific(oTLSKey, nullptr);

    CPLCleanupTLSList(papTLSList);
}

/************************************************************************/
/*                          CPLCreateSpinLock()                         */
/************************************************************************/

#if defined(HAVE_PTHREAD_SPIN_LOCK)
#define HAVE_SPINLOCK_IMPL

struct _CPLSpinLock
{
    pthread_spinlock_t spin;
};

CPLSpinLock *CPLCreateSpinLock()
{
    CPLSpinLock *psSpin =
        static_cast<CPLSpinLock *>(malloc(sizeof(CPLSpinLock)));
    if (psSpin != nullptr &&
        pthread_spin_init(&(psSpin->spin), PTHREAD_PROCESS_PRIVATE) == 0)
    {
        return psSpin;
    }
    else
    {
        fprintf(stderr, "CPLCreateSpinLock() failed.\n");
        free(psSpin);
        return nullptr;
    }
}

/************************************************************************/
/*                        CPLAcquireSpinLock()                          */
/************************************************************************/

int CPLAcquireSpinLock(CPLSpinLock *psSpin)
{
    return pthread_spin_lock(&(psSpin->spin)) == 0;
}

/************************************************************************/
/*                   CPLCreateOrAcquireSpinLockInternal()               */
/************************************************************************/

int CPLCreateOrAcquireSpinLockInternal(CPLLock **ppsLock)
{
    pthread_mutex_lock(&global_mutex);
    if (*ppsLock == nullptr)
    {
        *ppsLock = static_cast<CPLLock *>(calloc(1, sizeof(CPLLock)));
        if (*ppsLock != nullptr)
        {
            (*ppsLock)->eType = LOCK_SPIN;
            (*ppsLock)->u.hSpinLock = CPLCreateSpinLock();
            if ((*ppsLock)->u.hSpinLock == nullptr)
            {
                free(*ppsLock);
                *ppsLock = nullptr;
            }
        }
    }
    pthread_mutex_unlock(&global_mutex);
    // coverity[missing_unlock]
    return (*ppsLock != nullptr && CPLAcquireSpinLock((*ppsLock)->u.hSpinLock));
}

/************************************************************************/
/*                       CPLReleaseSpinLock()                           */
/************************************************************************/

void CPLReleaseSpinLock(CPLSpinLock *psSpin)
{
    pthread_spin_unlock(&(psSpin->spin));
}

/************************************************************************/
/*                        CPLDestroySpinLock()                          */
/************************************************************************/

void CPLDestroySpinLock(CPLSpinLock *psSpin)
{
    pthread_spin_destroy(&(psSpin->spin));
    free(psSpin);
}
#endif  // HAVE_PTHREAD_SPIN_LOCK

#endif  // def CPL_MULTIPROC_PTHREAD

/************************************************************************/
/*                             CPLGetTLS()                              */
/************************************************************************/

void *CPLGetTLS(int nIndex)

{
    void **l_papTLSList = CPLGetTLSList(nullptr);

    CPLAssert(nIndex >= 0 && nIndex < CTLS_MAX);

    return l_papTLSList[nIndex];
}

/************************************************************************/
/*                            CPLGetTLSEx()                             */
/************************************************************************/

void *CPLGetTLSEx(int nIndex, int *pbMemoryErrorOccurred)

{
    void **l_papTLSList = CPLGetTLSList(pbMemoryErrorOccurred);
    if (l_papTLSList == nullptr)
        return nullptr;

    CPLAssert(nIndex >= 0 && nIndex < CTLS_MAX);

    return l_papTLSList[nIndex];
}

/************************************************************************/
/*                             CPLSetTLS()                              */
/************************************************************************/

void CPLSetTLS(int nIndex, void *pData, int bFreeOnExit)

{
    CPLSetTLSWithFreeFunc(nIndex, pData, (bFreeOnExit) ? CPLFree : nullptr);
}

/************************************************************************/
/*                      CPLSetTLSWithFreeFunc()                         */
/************************************************************************/

// Warning: The CPLTLSFreeFunc must not in any case directly or indirectly
// use or fetch any TLS data, or a terminating thread will hang!
void CPLSetTLSWithFreeFunc(int nIndex, void *pData, CPLTLSFreeFunc pfnFree)

{
    void **l_papTLSList = CPLGetTLSList(nullptr);

    CPLAssert(nIndex >= 0 && nIndex < CTLS_MAX);

    l_papTLSList[nIndex] = pData;
    l_papTLSList[CTLS_MAX + nIndex] = reinterpret_cast<void *>(pfnFree);
}

/************************************************************************/
/*                      CPLSetTLSWithFreeFuncEx()                       */
/************************************************************************/

// Warning: the CPLTLSFreeFunc must not in any case directly or indirectly
// use or fetch any TLS data, or a terminating thread will hang!
void CPLSetTLSWithFreeFuncEx(int nIndex, void *pData, CPLTLSFreeFunc pfnFree,
                             int *pbMemoryErrorOccurred)

{
    void **l_papTLSList = CPLGetTLSList(pbMemoryErrorOccurred);

    CPLAssert(nIndex >= 0 && nIndex < CTLS_MAX);

    l_papTLSList[nIndex] = pData;
    l_papTLSList[CTLS_MAX + nIndex] = reinterpret_cast<void *>(pfnFree);
}
#ifndef HAVE_SPINLOCK_IMPL

// No spinlock specific API? Fallback to mutex.

/************************************************************************/
/*                          CPLCreateSpinLock()                         */
/************************************************************************/

CPLSpinLock *CPLCreateSpinLock(void)
{
    CPLSpinLock *psSpin = reinterpret_cast<CPLSpinLock *>(CPLCreateMutex());
    if (psSpin)
        CPLReleaseSpinLock(psSpin);
    return psSpin;
}

/************************************************************************/
/*                     CPLCreateOrAcquireSpinLock()                     */
/************************************************************************/

int CPLCreateOrAcquireSpinLockInternal(CPLLock **ppsLock)
{
    return CPLCreateOrAcquireMutexInternal(ppsLock, 1000, LOCK_ADAPTIVE_MUTEX);
}

/************************************************************************/
/*                        CPLAcquireSpinLock()                          */
/************************************************************************/

int CPLAcquireSpinLock(CPLSpinLock *psSpin)
{
    return CPLAcquireMutex(reinterpret_cast<CPLMutex *>(psSpin), 1000);
}

/************************************************************************/
/*                       CPLReleaseSpinLock()                           */
/************************************************************************/

void CPLReleaseSpinLock(CPLSpinLock *psSpin)
{
    CPLReleaseMutex(reinterpret_cast<CPLMutex *>(psSpin));
}

/************************************************************************/
/*                        CPLDestroySpinLock()                          */
/************************************************************************/

void CPLDestroySpinLock(CPLSpinLock *psSpin)
{
    CPLDestroyMutex(reinterpret_cast<CPLMutex *>(psSpin));
}

#endif  // HAVE_SPINLOCK_IMPL

/************************************************************************/
/*                            CPLCreateLock()                           */
/************************************************************************/

CPLLock *CPLCreateLock(CPLLockType eType)
{
    switch (eType)
    {
        case LOCK_RECURSIVE_MUTEX:
        case LOCK_ADAPTIVE_MUTEX:
        {
            CPLMutex *hMutex = CPLCreateMutexEx(eType == LOCK_RECURSIVE_MUTEX
                                                    ? CPL_MUTEX_RECURSIVE
                                                    : CPL_MUTEX_ADAPTIVE);
            if (!hMutex)
                return nullptr;
            CPLReleaseMutex(hMutex);
            CPLLock *psLock = static_cast<CPLLock *>(malloc(sizeof(CPLLock)));
            if (psLock == nullptr)
            {
                fprintf(stderr, "CPLCreateLock() failed.\n");
                CPLDestroyMutex(hMutex);
                return nullptr;
            }
            psLock->eType = eType;
            psLock->u.hMutex = hMutex;
#ifdef DEBUG_CONTENTION
            psLock->bDebugPerf = false;
            psLock->bDebugPerfAsked = false;
            psLock->nCurrentHolders = 0;
            psLock->nStartTime = 0;
#endif
            return psLock;
        }
        case LOCK_SPIN:
        {
            CPLSpinLock *hSpinLock = CPLCreateSpinLock();
            if (!hSpinLock)
                return nullptr;
            CPLLock *psLock = static_cast<CPLLock *>(malloc(sizeof(CPLLock)));
            if (psLock == nullptr)
            {
                fprintf(stderr, "CPLCreateLock() failed.\n");
                CPLDestroySpinLock(hSpinLock);
                return nullptr;
            }
            psLock->eType = eType;
            psLock->u.hSpinLock = hSpinLock;
#ifdef DEBUG_CONTENTION
            psLock->bDebugPerf = false;
            psLock->bDebugPerfAsked = false;
            psLock->nCurrentHolders = 0;
            psLock->nStartTime = 0;
#endif
            return psLock;
        }
        default:
            CPLAssert(false);
            return nullptr;
    }
}

/************************************************************************/
/*                       CPLCreateOrAcquireLock()                       */
/************************************************************************/

int CPLCreateOrAcquireLock(CPLLock **ppsLock, CPLLockType eType)
{
#ifdef DEBUG_CONTENTION
    GUIntBig nStartTime = 0;
    if ((*ppsLock) && (*ppsLock)->bDebugPerfAsked)
        nStartTime = CPLrdtsc();
#endif
    int ret = 0;

    switch (eType)
    {
        case LOCK_RECURSIVE_MUTEX:
        case LOCK_ADAPTIVE_MUTEX:
        {
            ret = CPLCreateOrAcquireMutexInternal(ppsLock, 1000, eType);
            break;
        }
        case LOCK_SPIN:
        {
            ret = CPLCreateOrAcquireSpinLockInternal(ppsLock);
            break;
        }
        default:
            CPLAssert(false);
            return FALSE;
    }
#ifdef DEBUG_CONTENTION
    if (ret && (*ppsLock)->bDebugPerfAsked &&
        CPLAtomicInc(&((*ppsLock)->nCurrentHolders)) == 1)
    {
        (*ppsLock)->bDebugPerf = true;
        (*ppsLock)->nStartTime = nStartTime;
    }
#endif
    return ret;
}

/************************************************************************/
/*                          CPLAcquireLock()                            */
/************************************************************************/

int CPLAcquireLock(CPLLock *psLock)
{
#ifdef DEBUG_CONTENTION
    GUIntBig nStartTime = 0;
    if (psLock->bDebugPerfAsked)
        nStartTime = CPLrdtsc();
#endif
    int ret;
    if (psLock->eType == LOCK_SPIN)
        ret = CPLAcquireSpinLock(psLock->u.hSpinLock);
    else
        ret = CPLAcquireMutex(psLock->u.hMutex, 1000);
#ifdef DEBUG_CONTENTION
    if (ret && psLock->bDebugPerfAsked &&
        CPLAtomicInc(&(psLock->nCurrentHolders)) == 1)
    {
        psLock->bDebugPerf = true;
        psLock->nStartTime = nStartTime;
    }
#endif
    return ret;
}

/************************************************************************/
/*                         CPLReleaseLock()                             */
/************************************************************************/

void CPLReleaseLock(CPLLock *psLock)
{
#ifdef DEBUG_CONTENTION
    bool bHitMaxDiff = false;
    GIntBig nMaxDiff = 0;
    double dfAvgDiff = 0;
    if (psLock->bDebugPerf && CPLAtomicDec(&(psLock->nCurrentHolders)) == 0)
    {
        const GUIntBig nStopTime = CPLrdtscp();
        const GIntBig nDiffTime =
            static_cast<GIntBig>(nStopTime - psLock->nStartTime);
        if (nDiffTime > psLock->nMaxDiff)
        {
            bHitMaxDiff = true;
            psLock->nMaxDiff = nDiffTime;
        }
        nMaxDiff = psLock->nMaxDiff;
        psLock->nIters++;
        psLock->dfAvgDiff += (nDiffTime - psLock->dfAvgDiff) / psLock->nIters;
        dfAvgDiff = psLock->dfAvgDiff;
    }
#endif
    if (psLock->eType == LOCK_SPIN)
        CPLReleaseSpinLock(psLock->u.hSpinLock);
    else
        CPLReleaseMutex(psLock->u.hMutex);
#ifdef DEBUG_CONTENTION
    if (psLock->bDebugPerf &&
        (bHitMaxDiff || (psLock->nIters % 1000000) == (1000000 - 1)))
    {
        CPLDebug("LOCK", "Lock contention : max = " CPL_FRMT_GIB ", avg = %.0f",
                 nMaxDiff, dfAvgDiff);
    }
#endif
}

/************************************************************************/
/*                          CPLDestroyLock()                            */
/************************************************************************/

void CPLDestroyLock(CPLLock *psLock)
{
    if (psLock->eType == LOCK_SPIN)
        CPLDestroySpinLock(psLock->u.hSpinLock);
    else
        CPLDestroyMutex(psLock->u.hMutex);
    free(psLock);
}

/************************************************************************/
/*                       CPLLockSetDebugPerf()                          */
/************************************************************************/

#ifdef DEBUG_CONTENTION
void CPLLockSetDebugPerf(CPLLock *psLock, int bEnableIn)
{
    psLock->bDebugPerfAsked = CPL_TO_BOOL(bEnableIn);
}
#else
void CPLLockSetDebugPerf(CPLLock * /* psLock */, int bEnableIn)
{
    if (!bEnableIn)
        return;

    static bool bOnce = false;
    if (!bOnce)
    {
        bOnce = true;
        CPLDebug("LOCK", "DEBUG_CONTENTION not available");
    }
}
#endif

/************************************************************************/
/*                           CPLLockHolder()                            */
/************************************************************************/

CPLLockHolder::CPLLockHolder(CPLLock **phLock, CPLLockType eType,
                             const char *pszFileIn, int nLineIn)

{
#ifndef MUTEX_NONE
    pszFile = pszFileIn;
    nLine = nLineIn;

#ifdef DEBUG_MUTEX
    // XXX: There is no way to use CPLDebug() here because it works with
    // mutexes itself so we will fall in infinite recursion. Good old
    // fprintf() will do the job right.
    fprintf(stderr, "CPLLockHolder: Request %p for pid %ld at %d/%s.\n",
            *phLock, static_cast<long>(CPLGetPID()), nLine, pszFile);
#endif

    if (!CPLCreateOrAcquireLock(phLock, eType))
    {
        fprintf(stderr, "CPLLockHolder: Failed to acquire lock!\n");
        hLock = nullptr;
    }
    else
    {
#ifdef DEBUG_MUTEX
        fprintf(stderr, "CPLLockHolder: Acquired %p for pid %ld at %d/%s.\n",
                *phLock, static_cast<long>(CPLGetPID()), nLine, pszFile);
#endif

        hLock = *phLock;
    }
#endif  // ndef MUTEX_NONE
}

/************************************************************************/
/*                           CPLLockHolder()                            */
/************************************************************************/

CPLLockHolder::CPLLockHolder(CPLLock *hLockIn, const char *pszFileIn,
                             int nLineIn)

{
#ifndef MUTEX_NONE
    pszFile = pszFileIn;
    nLine = nLineIn;
    hLock = hLockIn;

    if (hLock != nullptr)
    {
        if (!CPLAcquireLock(hLock))
        {
            fprintf(stderr, "CPLLockHolder: Failed to acquire lock!\n");
            hLock = nullptr;
        }
    }
#endif  // ndef MUTEX_NONE
}

/************************************************************************/
/*                          ~CPLLockHolder()                            */
/************************************************************************/

CPLLockHolder::~CPLLockHolder()

{
#ifndef MUTEX_NONE
    if (hLock != nullptr)
    {
#ifdef DEBUG_MUTEX
        fprintf(stderr, "~CPLLockHolder: Release %p for pid %ld at %d/%s.\n",
                hLock, static_cast<long>(CPLGetPID()), nLine, pszFile);
#endif
        CPLReleaseLock(hLock);
    }
#endif  // ndef MUTEX_NONE
}

/************************************************************************/
/*                       CPLGetCurrentProcessID()                       */
/************************************************************************/

#ifdef CPL_MULTIPROC_WIN32

int CPLGetCurrentProcessID()
{
    return GetCurrentProcessId();
}

#else

#include <sys/types.h>
#include <unistd.h>

int CPLGetCurrentProcessID()
{
    return getpid();
}

#endif
