# HG changeset patch # User Yasumasa Suenaga # Date 1497425431 -32400 # Node ID 1ac3803a35d1fbe3ac55c5c47cfafe750d12e6b7 # Parent b862390b22bce8b5693c58df2eda1c1ddf44a635 Bug 3403: HeapStats Agent might crash if application exits with System.exit() Reviewed-by: ykubota https://github.com/HeapStats/heapstats/pull/100 diff -r b862390b22bc -r 1ac3803a35d1 ChangeLog --- a/ChangeLog Thu Jun 08 23:06:50 2017 +0900 +++ b/ChangeLog Wed Jun 14 16:30:31 2017 +0900 @@ -1,3 +1,7 @@ +2017-06-14 Yasumasa Suenaga + + * Bug 3403: HeapStats Agent might crash if application exits with System.exit() + 2017-06-08 KUBOTA Yuji * Bug 3399: Fix potential error when conflict between class loading and GC diff -r b862390b22bc -r 1ac3803a35d1 agent/src/heapstats-engines/heapstatsMBean.cpp --- a/agent/src/heapstats-engines/heapstatsMBean.cpp Thu Jun 08 23:06:50 2017 +0900 +++ b/agent/src/heapstats-engines/heapstatsMBean.cpp Wed Jun 14 16:30:31 2017 +0900 @@ -1,7 +1,7 @@ /*! * \file heapstatsMBean.cpp * \brief JNI implementation for HeapStatsMBean. - * Copyright (C) 2014-2016 Yasumasa Suenaga + * Copyright (C) 2014-2017 Yasumasa Suenaga * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -22,12 +22,20 @@ #include #include +#include #include +#ifdef HAVE_ATOMIC +#include +#else +#include +#endif + #include "globals.hpp" #include "configuration.hpp" #include "heapstatsMBean.hpp" +#include "util.hpp" /* Variables */ static jclass mapCls = NULL; @@ -50,6 +58,11 @@ static jmethodID longValue = NULL; static jmethodID longValueOf = NULL; +static jclass linkedCls = NULL; +static volatile bool isLoaded = false; +static std::atomic_int processing(0); + + /*! * \brief Raise Java Exception. * @@ -301,7 +314,11 @@ return; } + TProcessMark mark(processing); + /* Initialize variables. */ + linkedCls = (jclass)env->NewGlobalRef(cls); + isLoaded = true; /* For Map object */ if (!prepareForMapObject(env)) { @@ -328,6 +345,52 @@ } } +static void *JNIDummy(void) { + return NULL; +} + +/*! + * \brief Unregister JNI functions in libheapstats. + * \param env Pointer of JNI environment. + */ +void UnregisterHeapStatsNatives(JNIEnv *env) { + if (!isLoaded) { + return; + } + + /* Regist JNI functions */ + JNINativeMethod methods[] = { + {(char *)"getHeapStatsVersion0", + (char *)"()Ljava/lang/String;", + (void *)JNIDummy}, + {(char *)"getConfiguration0", + (char *)"(Ljava/lang/String;)Ljava/lang/Object;", + (void *)JNIDummy}, + {(char *)"getConfigurationList0", + (char *)"()Ljava/util/Map;", + (void *)JNIDummy}, + {(char *)"changeConfiguration0", + (char *)"(Ljava/lang/String;Ljava/lang/Object;)Z", + (void *)JNIDummy}, + {(char *)"invokeLogCollection0", + (char *)"()Z", + (void *)JNIDummy}, + {(char *)"invokeAllLogCollection0", + (char *)"()Z", + (void *)JNIDummy}}; + + if (env->RegisterNatives(linkedCls, methods, 6) != 0) { + raiseException(env, "java/lang/UnsatisfiedLinkError", + "Could not unregister HeapStats native functions."); + } + + env->DeleteGlobalRef(linkedCls); + + while (processing > 0) { + sched_yield(); + } +} + /*! * \brief Get HeapStats version string from libheapstats. * @@ -336,6 +399,7 @@ * \return Version string which is attached. */ JNIEXPORT jstring JNICALL GetHeapStatsVersion(JNIEnv *env, jobject obj) { + TProcessMark mark(processing); jstring versionStr = env->NewStringUTF(PACKAGE_STRING " (" #ifdef SSE2 @@ -432,6 +496,7 @@ */ JNIEXPORT jobject JNICALL GetConfiguration(JNIEnv *env, jobject obj, jstring key) { + TProcessMark mark(processing); const char *opt = env->GetStringUTFChars(key, NULL); if (opt == NULL) { raiseException(env, "java/lang/RuntimeException", @@ -467,6 +532,7 @@ * \return Current configuration list. */ JNIEXPORT jobject JNICALL GetConfigurationList(JNIEnv *env, jobject obj) { + TProcessMark mark(processing); jobject result = env->NewObject(mapCls, map_ctor); if (result == NULL) { raiseException(env, "java/lang/RuntimeException", @@ -509,6 +575,7 @@ */ JNIEXPORT jboolean JNICALL ChangeConfiguration(JNIEnv *env, jobject obj, jstring key, jobject value) { + TProcessMark mark(processing); jclass valueCls = env->GetObjectClass(value); char *opt = (char *)env->GetStringUTFChars(key, NULL); if (opt == NULL) { @@ -631,6 +698,7 @@ * \return Result of this call. */ JNIEXPORT jboolean JNICALL InvokeLogCollection(JNIEnv *env, jobject obj) { + TProcessMark mark(processing); int ret = logManager->collectLog(NULL, env, Signal, (TMSecTime)getNowTimeSec(), "JMX event"); return ret == 0 ? JNI_TRUE : JNI_FALSE; @@ -644,6 +712,7 @@ * \return Result of this call. */ JNIEXPORT jboolean JNICALL InvokeAllLogCollection(JNIEnv *env, jobject obj) { + TProcessMark mark(processing); int ret = logManager->collectLog(NULL, env, AnotherSignal, (TMSecTime)getNowTimeSec(), "JMX event"); return ret == 0 ? JNI_TRUE : JNI_FALSE; diff -r b862390b22bc -r 1ac3803a35d1 agent/src/heapstats-engines/heapstatsMBean.hpp --- a/agent/src/heapstats-engines/heapstatsMBean.hpp Thu Jun 08 23:06:50 2017 +0900 +++ b/agent/src/heapstats-engines/heapstatsMBean.hpp Wed Jun 14 16:30:31 2017 +0900 @@ -1,7 +1,7 @@ /*! * \file heapstatsMBean.hpp * \brief JNI implementation for HeapStatsMBean. - * Copyright (C) 2014 Yasumasa Suenaga + * Copyright (C) 2014-2017 Yasumasa Suenaga * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -42,4 +42,6 @@ } #endif +void UnregisterHeapStatsNatives(JNIEnv *env); + #endif // HEAPSTATSMBEAN_HPP diff -r b862390b22bc -r 1ac3803a35d1 agent/src/heapstats-engines/libmain.cpp --- a/agent/src/heapstats-engines/libmain.cpp Thu Jun 08 23:06:50 2017 +0900 +++ b/agent/src/heapstats-engines/libmain.cpp Wed Jun 14 16:30:31 2017 +0900 @@ -381,11 +381,6 @@ reloadSigMngr = NULL; } - /* Invoke JVM finalize event of snapshot function. */ - onVMDeathForSnapShot(jvmti, env); - /* Invoke JVM finalize event of log function. */ - onVMDeathForLog(jvmti, env); - /* If agent is attaching now. */ if (likely(conf->Attach()->get())) { /* Stop and disable each thread. */ @@ -396,6 +391,13 @@ conf->ThreadRecordFileName()->get()); } } + + /* Invoke JVM finalize event of snapshot function. */ + onVMDeathForSnapShot(jvmti, env); + /* Invoke JVM finalize event of log function. */ + onVMDeathForLog(jvmti, env); + /* Unregister native functions for HeapStats MBean */ + UnregisterHeapStatsNatives(env); } /*! diff -r b862390b22bc -r 1ac3803a35d1 agent/src/heapstats-engines/logMain.cpp --- a/agent/src/heapstats-engines/logMain.cpp Thu Jun 08 23:06:50 2017 +0900 +++ b/agent/src/heapstats-engines/logMain.cpp Wed Jun 14 16:30:31 2017 +0900 @@ -20,6 +20,13 @@ */ #include +#include + +#ifdef HAVE_ATOMIC +#include +#else +#include +#endif #include "globals.hpp" #include "elapsedTimer.hpp" @@ -69,6 +76,12 @@ bool abortionByDeadlock = false; /*! + * \brief processing flag + */ +static std::atomic_int processing(0); + + +/*! * \brief Take log information. * \param jvmti [in] JVMTI environment object. * \param env [in] JNI environment object. @@ -134,6 +147,8 @@ * \param env [in] JNI environment object. */ void intervalSigProcForLog(jvmtiEnv *jvmti, JNIEnv *env) { + TProcessMark mark(processing); + /* If catch normal log signal. */ if (unlikely(flagLogSignal != 0)) { TMSecTime nowTime = (TMSecTime)getNowTimeSec(); @@ -163,6 +178,8 @@ * This value is always OccurredDeadlock. */ void onOccurredDeadLock(jvmtiEnv *jvmti, JNIEnv *env, TInvokeCause cause) { + TProcessMark mark(processing); + /* Get now date and time. */ TMSecTime occurTime = TDeadlockFinder::getInstance()->getDeadlockTime(); @@ -190,6 +207,8 @@ void JNICALL OnResourceExhausted(jvmtiEnv *jvmti, JNIEnv *env, jint flags, const void *reserved, const char *description) { + TProcessMark mark(processing); + /* Raise alert. */ logger->printCritMsg("ALERT(RESOURCE): resource was exhausted. info:\"%s\"", description); @@ -409,6 +428,15 @@ delete logAllSignalMngr; logAllSignalMngr = NULL; } + + /* + * ResourceExhausted, MonitorContendedEnter for Deadlock JVMTI event, + * all callee of TakeLogInfo() will not be started at this point. + * So we wait to finish all existed tasks. + */ + while (processing > 0) { + sched_yield(); + } } /*! diff -r b862390b22bc -r 1ac3803a35d1 agent/src/heapstats-engines/snapShotMain.cpp --- a/agent/src/heapstats-engines/snapShotMain.cpp Thu Jun 08 23:06:50 2017 +0900 +++ b/agent/src/heapstats-engines/snapShotMain.cpp Wed Jun 14 16:30:31 2017 +0900 @@ -21,6 +21,12 @@ #include +#ifdef HAVE_ATOMIC +#include +#else +#include +#endif + #include "globals.hpp" #include "vmFunctions.hpp" #include "elapsedTimer.hpp" @@ -112,6 +118,12 @@ */ int classUnloadEventIdx = -1; +/*! + * \brief processing flag + */ +static std::atomic_int processing(0); + + /* Function defines. */ /*! @@ -604,6 +616,7 @@ * e.g. GC, DumpRequest or Interval. */ void TakeSnapShot(jvmtiEnv *jvmti, JNIEnv *env, TInvokeCause cause) { + TProcessMark mark(processing); TVMVariables *vmVal = TVMVariables::getInstance(); /* @@ -851,6 +864,18 @@ if (likely(classUnloadEventIdx >= 0)) { jvmti->SetExtensionEventCallback(classUnloadEventIdx, NULL); } + + /* Wait until all tasks are finished. */ + while (processing > 0) { + sched_yield(); + } + + /* Destroy object that is each snapshot trigger. */ + delete gcWatcher; + gcWatcher = NULL; + + delete timer; + timer = NULL; } /*! @@ -926,13 +951,6 @@ delete clsContainer; clsContainer = NULL; - /* Destroy object that is each snapshot trigger. */ - delete gcWatcher; - gcWatcher = NULL; - - delete timer; - timer = NULL; - /* Finalize oop util. */ oopUtilFinalize(); } diff -r b862390b22bc -r 1ac3803a35d1 agent/src/heapstats-engines/threadRecorder.cpp --- a/agent/src/heapstats-engines/threadRecorder.cpp Thu Jun 08 23:06:50 2017 +0900 +++ b/agent/src/heapstats-engines/threadRecorder.cpp Wed Jun 14 16:30:31 2017 +0900 @@ -31,6 +31,13 @@ #include #include #include +#include + +#ifdef HAVE_ATOMIC +#include +#else +#include +#endif #include "globals.hpp" #include "util.hpp" @@ -51,6 +58,7 @@ /* variables */ jclass threadClass; jmethodID currentThreadMethod; +static std::atomic_int processing(0); /* JVMTI event handler */ @@ -62,6 +70,7 @@ * \param thread [in] jthread object which is created. */ void JNICALL OnThreadStart(jvmtiEnv *jvmti, JNIEnv *env, jthread thread) { + TProcessMark mark(processing); TThreadRecorder *recorder = TThreadRecorder::getInstance(); recorder->registerNewThread(jvmti, thread); recorder->putEvent(thread, ThreadStart, 0); @@ -75,6 +84,7 @@ * \param thread [in] jthread object which is created. */ void JNICALL OnThreadEnd(jvmtiEnv *jvmti, JNIEnv *env, jthread thread) { + TProcessMark mark(processing); TThreadRecorder::getInstance()->putEvent(thread, ThreadEnd, 0); } @@ -89,6 +99,7 @@ void JNICALL OnMonitorContendedEnterForThreadRecording(jvmtiEnv *jvmti, JNIEnv *env, jthread thread, jobject object) { + TProcessMark mark(processing); TThreadRecorder::getInstance()->putEvent(thread, MonitorContendedEnter, 0); } @@ -104,6 +115,7 @@ JNIEnv *env, jthread thread, jobject object) { + TProcessMark mark(processing); TThreadRecorder::getInstance()->putEvent(thread, MonitorContendedEntered, 0); } @@ -118,6 +130,7 @@ */ void JNICALL OnMonitorWait(jvmtiEnv *jvmti, JNIEnv *jni_env, jthread thread, jobject object, jlong timeout) { + TProcessMark mark(processing); TThreadRecorder::getInstance()->putEvent(thread, MonitorWait, timeout); } @@ -132,6 +145,7 @@ */ void JNICALL OnMonitorWaited(jvmtiEnv *jvmti, JNIEnv *jni_env, jthread thread, jobject object, jboolean timeout) { + TProcessMark mark(processing); TThreadRecorder::getInstance()->putEvent(thread, MonitorWaited, timeout); } @@ -141,6 +155,7 @@ * \param jvmti [in] JVMTI environment. */ void JNICALL OnDataDumpRequestForDumpThreadRecordData(jvmtiEnv *jvmti) { + TProcessMark mark(processing); TThreadRecorder::inst->dump(conf->ThreadRecordFileName()->get()); } @@ -168,6 +183,7 @@ * \param millis [in] Time of sleeping. */ void JNICALL JVM_SleepPrologue(JNIEnv *env, jclass threadClass, jlong millis) { + TProcessMark mark(processing); void *javaThread = GetCurrentThread(env); TThreadRecorder::getInstance()->putEvent((jthread)&javaThread, ThreadSleepStart, millis); @@ -181,6 +197,7 @@ * \param millis [in] Time of sleeping. */ void JNICALL JVM_SleepEpilogue(JNIEnv *env, jclass threadClass, jlong millis) { + TProcessMark mark(processing); void *javaThread = GetCurrentThread(env); TThreadRecorder::getInstance()->putEvent((jthread)&javaThread, ThreadSleepEnd, millis); @@ -196,6 +213,7 @@ */ void JNICALL UnsafeParkPrologue(JNIEnv *env, jobject unsafe, jboolean isAbsolute, jlong time) { + TProcessMark mark(processing); void *javaThread = GetCurrentThread(env); TThreadRecorder::getInstance()->putEvent((jthread)&javaThread, Park, time); } @@ -210,17 +228,24 @@ */ void JNICALL UnsafeParkEpilogue(JNIEnv *env, jobject unsafe, jboolean isAbsolute, jlong time) { + TProcessMark mark(processing); void *javaThread = GetCurrentThread(env); TThreadRecorder::getInstance()->putEvent((jthread)&javaThread, Unpark, time); } /* I/O tracer */ +/* Dummy method */ +void * JNICALL IoTrace_dummy(void) { + return NULL; +} + /* * Method: socketReadBegin * Signature: ()Ljava/lang/Object; */ JNIEXPORT jobject JNICALL IoTrace_socketReadBegin(JNIEnv *env, jclass cls) { + TProcessMark mark(processing); void *javaThread = GetCurrentThread(env); TThreadRecorder::getInstance()->putEvent((jthread)&javaThread, SocketReadStart, 0); @@ -235,6 +260,7 @@ jobject context, jobject address, jint port, jint timeout, jlong bytesRead) { + TProcessMark mark(processing); void *javaThread = GetCurrentThread(env); TThreadRecorder::getInstance()->putEvent((jthread)&javaThread, SocketReadEnd, bytesRead); @@ -245,6 +271,7 @@ * Signature: ()Ljava/lang/Object; */ JNIEXPORT jobject JNICALL IoTrace_socketWriteBegin(JNIEnv *env, jclass cls) { + TProcessMark mark(processing); void *javaThread = GetCurrentThread(env); TThreadRecorder::getInstance()->putEvent((jthread)&javaThread, SocketWriteStart, 0); @@ -258,6 +285,7 @@ JNIEXPORT void JNICALL IoTrace_socketWriteEnd(JNIEnv *env, jclass cls, jobject context, jobject address, jint port, jlong bytesWritten) { + TProcessMark mark(processing); void *javaThread = GetCurrentThread(env); TThreadRecorder::getInstance()->putEvent((jthread)&javaThread, SocketWriteEnd, bytesWritten); @@ -269,6 +297,7 @@ */ JNIEXPORT jobject JNICALL IoTrace_fileReadBegin(JNIEnv *env, jclass cls, jstring path) { + TProcessMark mark(processing); void *javaThread = GetCurrentThread(env); TThreadRecorder::getInstance()->putEvent((jthread)&javaThread, FileReadStart, 0); @@ -281,6 +310,7 @@ */ JNIEXPORT void JNICALL IoTrace_fileReadEnd(JNIEnv *env, jclass cls, jobject context, jlong bytesRead) { + TProcessMark mark(processing); void *javaThread = GetCurrentThread(env); TThreadRecorder::getInstance()->putEvent((jthread)&javaThread, FileReadEnd, bytesRead); @@ -292,6 +322,7 @@ */ JNIEXPORT jobject JNICALL IoTrace_fileWriteBegin(JNIEnv *env, jclass cls, jstring path) { + TProcessMark mark(processing); void *javaThread = GetCurrentThread(env); TThreadRecorder::getInstance()->putEvent((jthread)&javaThread, FileWriteStart, 0); @@ -305,6 +336,7 @@ JNIEXPORT void JNICALL IoTrace_fileWriteEnd(JNIEnv *env, jclass cls, jobject context, jlong bytesWritten) { + TProcessMark mark(processing); void *javaThread = GetCurrentThread(env); TThreadRecorder::getInstance()->putEvent((jthread)&javaThread, FileWriteEnd, bytesWritten); @@ -526,6 +558,55 @@ } /*! + * \brief Unegister I/O tracer. + * + * \param env [in] JNI environment. + */ +void TThreadRecorder::UnregisterIOTracer(JNIEnv *env) { + if (conf->ThreadRecordIOTracer()->get() == NULL) { + return; + } + + jclass iotraceClass = env->FindClass("sun/misc/IoTrace"); + + if (iotraceClass == NULL) { + env->ExceptionClear(); // It may occur NoClassDefFoumdError + return; + } + + /* Register hook native methods. */ + JNINativeMethod methods[] = { + {(char *)"socketReadBegin", + (char *)"()Ljava/lang/Object;", + (void *)&IoTrace_dummy}, + {(char *)"socketReadEnd", + (char *)"(Ljava/lang/Object;Ljava/net/InetAddress;IIJ)V", + (void *)&IoTrace_dummy}, + {(char *)"socketWriteBegin", + (char *)"()Ljava/lang/Object;", + (void *)&IoTrace_dummy}, + {(char *)"socketWriteEnd", + (char *)"(Ljava/lang/Object;Ljava/net/InetAddress;IJ)V", + (void *)&IoTrace_dummy}, + {(char *)"fileReadBegin", + (char *)"(Ljava/lang/String;)Ljava/lang/Object;", + (void *)&IoTrace_dummy}, + {(char *)"fileReadEnd", + (char *)"(Ljava/lang/Object;J)V", + (void *)&IoTrace_dummy}, + {(char *)"fileWriteBegin", + (char *)"(Ljava/lang/String;)Ljava/lang/Object;", + (void *)&IoTrace_dummy}, + {(char *)"fileWriteEnd", + (char *)"(Ljava/lang/Object;J)V", + (void *)&IoTrace_dummy}}; + + env->RegisterNatives(iotraceClass, methods, 8); + + return; +} + +/*! * \brief Initialize HeapStats Thread Recorder. * * \param jvmti [in] JVMTI environment. @@ -626,6 +707,14 @@ TJVMSleepCallback::switchCallback(env, false); TUnsafeParkCallback::switchCallback(env, false); + /* Stop to hook IoTrace */ + UnregisterIOTracer(env); + + /* Wait until all tasks are finished. */ + while (processing > 0) { + sched_yield(); + } + /* Stop HeapStats Thread Recorder */ inst->dump(fname); diff -r b862390b22bc -r 1ac3803a35d1 agent/src/heapstats-engines/threadRecorder.hpp --- a/agent/src/heapstats-engines/threadRecorder.hpp Thu Jun 08 23:06:50 2017 +0900 +++ b/agent/src/heapstats-engines/threadRecorder.hpp Wed Jun 14 16:30:31 2017 +0900 @@ -1,7 +1,7 @@ /*! * \file threadRecorder.hpp * \brief Recording thread status. - * Copyright (C) 2015 Yasumasa Suenaga + * Copyright (C) 2015-2017 Yasumasa Suenaga * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -177,6 +177,13 @@ */ static bool registerIOTracer(jvmtiEnv *jvmti, JNIEnv *env); + /*! + * \brief Unegister I/O tracer. + * + * \param env [in] JNI environment. + */ + static void UnregisterIOTracer(JNIEnv *env); + public: /*! * \brief JVMTI callback for ThreadStart event. diff -r b862390b22bc -r 1ac3803a35d1 agent/src/heapstats-engines/util.hpp --- a/agent/src/heapstats-engines/util.hpp Thu Jun 08 23:06:50 2017 +0900 +++ b/agent/src/heapstats-engines/util.hpp Wed Jun 14 16:30:31 2017 +0900 @@ -25,6 +25,12 @@ #include #include +#ifdef HAVE_ATOMIC +#include +#else +#include +#endif + #include #include #include @@ -367,6 +373,20 @@ size_t operator()(const T &val) const { return (size_t)val; }; }; +/*! + * \brief Utility class for flag of processing. + */ +class TProcessMark { + + private: + std::atomic_int &flag; + + public: + TProcessMark(std::atomic_int &flg) : flag(flg) { flag++; } + ~TProcessMark() { flag--; } + +}; + /* CPU Specific utilities. */ #ifdef AVX #include "arch/x86/avx/util.hpp" diff -r b862390b22bc -r 1ac3803a35d1 configure --- a/configure Thu Jun 08 23:06:50 2017 +0900 +++ b/configure Wed Jun 14 16:30:31 2017 +0900 @@ -6471,6 +6471,38 @@ USE_PCRE_FALSE= fi + +# Check for atomic operation +for ac_header in atomic +do : + ac_fn_cxx_check_header_mongrel "$LINENO" "atomic" "ac_cv_header_atomic" "$ac_includes_default" +if test "x$ac_cv_header_atomic" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_ATOMIC 1 +_ACEOF + +else + + for ac_header in cstdatomic +do : + ac_fn_cxx_check_header_mongrel "$LINENO" "cstdatomic" "ac_cv_header_cstdatomic" "$ac_includes_default" +if test "x$ac_cv_header_cstdatomic" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_CSTDATOMIC 1 +_ACEOF + +else + as_fn_error $? "Compiler does not support C++ atomic." "$LINENO" 5 +fi + +done + + +fi + +done + + ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' diff -r b862390b22bc -r 1ac3803a35d1 configure.ac --- a/configure.ac Thu Jun 08 23:06:50 2017 +0900 +++ b/configure.ac Wed Jun 14 16:30:31 2017 +0900 @@ -99,6 +99,14 @@ ] ) AM_CONDITIONAL(USE_PCRE, test -n "${ac_cv_header_pcre_h}") + +# Check for atomic operation +AC_CHECK_HEADERS([atomic], [], +[ + AC_CHECK_HEADERS([cstdatomic], [], + [AC_MSG_ERROR([Compiler does not support C++ atomic.])]) +]) + AC_LANG_POP([C++]) # Checks for library header files.