feat: use standard threads (#1019)

This commit is contained in:
Sergiu Deitsch 2024-01-03 02:24:52 +01:00 committed by GitHub
parent 645d0a5d5f
commit 0032896711
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 103 additions and 232 deletions

View File

@ -68,7 +68,6 @@ set (CMAKE_POSITION_INDEPENDENT_CODE ON)
set (CMAKE_VISIBILITY_INLINES_HIDDEN ON)
set (CMAKE_DEBUG_POSTFIX d)
set (CMAKE_THREAD_PREFER_PTHREAD 1)
find_package (GTest NO_MODULE)
@ -159,24 +158,6 @@ check_cxx_symbol_exists (backtrace_symbols execinfo.h
HAVE_EXECINFO_BACKTRACE_SYMBOLS)
check_cxx_symbol_exists (_chsize_s io.h HAVE__CHSIZE_S)
cmake_push_check_state (RESET)
set (CMAKE_REQUIRED_DEFINITIONS -D_XOPEN_SOURCE=500)
if (Threads_FOUND)
set (CMAKE_REQUIRED_LIBRARIES Threads::Threads)
endif (Threads_FOUND)
check_cxx_symbol_exists (pthread_threadid_np pthread.h HAVE_PTHREAD_THREADID_NP)
cmake_pop_check_state ()
if (HAVE_RWLOCK_INIT AND HAVE_RWLOCK_RDLOCK AND HAVE_RWLOCK_WRLOCK AND
HAVE_RWLOCK_UNLOCK AND HAVE_RWLOCK_DESTROY)
set (HAVE_RWLOCK TRUE)
endif (HAVE_RWLOCK_INIT AND HAVE_RWLOCK_RDLOCK AND HAVE_RWLOCK_WRLOCK AND
HAVE_RWLOCK_UNLOCK AND HAVE_RWLOCK_DESTROY)
cmake_push_check_state (RESET)
set (CMAKE_REQUIRED_LIBRARIES dbghelp)
check_cxx_symbol_exists (UnDecorateSymbolName "windows.h;dbghelp.h" HAVE_DBGHELP)

View File

@ -59,8 +59,6 @@ def glog_library(with_gflags = 1, **kwargs):
"-Wno-unused-function",
"-Wno-unused-local-typedefs",
"-Wno-unused-variable",
# Allows to include pthread.h.
"-DHAVE_PTHREAD",
# Allows src/logging.cc to determine the host name.
"-DHAVE_SYS_UTSNAME_H",
# For src/utilities.cc.
@ -98,8 +96,6 @@ def glog_library(with_gflags = 1, **kwargs):
darwin_only_copts = [
# For stacktrace.
"-DHAVE_DLADDR",
# Avoid deprecated syscall().
"-DHAVE_PTHREAD_THREADID_NP",
]
windows_only_copts = [

View File

@ -19,9 +19,6 @@
/* Define to 1 if you have the <glob.h> header file. */
#cmakedefine HAVE_GLOB_H
/* Define to 1 if you have the `pthread' library (-lpthread). */
#cmakedefine HAVE_LIBPTHREAD
/* define if you have google gmock library */
#cmakedefine HAVE_LIB_GMOCK
@ -37,9 +34,6 @@
/* Define if you have the 'pread' function */
#cmakedefine HAVE_PREAD
/* Define if you have POSIX threads libraries and header files. */
#cmakedefine HAVE_PTHREAD
/* Define to 1 if you have the <pwd.h> header file. */
#cmakedefine HAVE_PWD_H
@ -115,10 +109,6 @@
/* define if we should print file offsets in traces instead of symbolizing. */
#cmakedefine PRINT_UNSYMBOLIZED_STACK_TRACES
/* Define to necessary symbol if this constant uses a non-standard name on
your system. */
#cmakedefine PTHREAD_CREATE_JOINABLE
/* The size of `void *', as computed by sizeof. */
#cmakedefine SIZEOF_VOID_P ${SIZEOF_VOID_P}
@ -128,7 +118,4 @@
/* Define if thread-local storage is enabled. */
#cmakedefine GLOG_THREAD_LOCAL_STORAGE
/* Replacement for deprecated syscall(SYS_gettid) on macOS. */
#cmakedefine HAVE_PTHREAD_THREADID_NP ${HAVE_PTHREAD_THREADID_NP}
#endif // GLOG_CONFIG_H

View File

@ -47,6 +47,7 @@
#include <ostream>
#include <sstream>
#include <string>
#include <thread>
#include <utility>
#include <vector>
@ -130,7 +131,8 @@ struct GLOG_EXPORT LogMessageTime {
struct LogMessageInfo {
explicit LogMessageInfo(const char* const severity_,
const char* const filename_, const int& line_number_,
const int& thread_id_, const LogMessageTime& time_)
std::thread::id thread_id_,
const LogMessageTime& time_)
: severity(severity_),
filename(filename_),
line_number(line_number_),
@ -140,7 +142,7 @@ struct LogMessageInfo {
const char* const severity;
const char* const filename;
const int& line_number;
const int& thread_id;
std::thread::id thread_id;
const LogMessageTime& time;
};

View File

@ -579,59 +579,6 @@ struct FlagSaver {
};
#endif
class Thread {
public:
virtual ~Thread() = default;
void SetJoinable(bool) {}
#if defined(GLOG_OS_WINDOWS) && !defined(GLOG_OS_CYGWIN)
void Start() {
handle_ = CreateThread(nullptr, 0, &Thread::InvokeThreadW, this, 0, &th_);
CHECK(handle_) << "CreateThread";
}
void Join() { WaitForSingleObject(handle_, INFINITE); }
#elif defined(HAVE_PTHREAD)
void Start() { pthread_create(&th_, nullptr, &Thread::InvokeThread, this); }
void Join() { pthread_join(th_, nullptr); }
#else
void Start() {}
void Join() {}
#endif
protected:
virtual void Run() = 0;
private:
static void* InvokeThread(void* self) {
(static_cast<Thread*>(self))->Run();
return nullptr;
}
#if defined(GLOG_OS_WINDOWS) && !defined(GLOG_OS_CYGWIN)
static DWORD __stdcall InvokeThreadW(LPVOID self) {
InvokeThread(self);
return 0;
}
HANDLE handle_;
DWORD th_;
#elif defined(HAVE_PTHREAD)
pthread_t th_;
#endif
};
static inline void SleepForMilliseconds(unsigned t) {
#ifndef GLOG_OS_WINDOWS
# if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L
const struct timespec req = {0, t * 1000 * 1000};
nanosleep(&req, nullptr);
# else
usleep(t * 1000);
# endif
#else
Sleep(t);
#endif
}
// Add hook for operator new to ensure there are no memory allocation.
void (*g_new_hook)() = nullptr;

View File

@ -39,6 +39,7 @@
#include <mutex>
#include <shared_mutex>
#include <string>
#include <thread>
#include "base/commandlineflags.h" // to get the program name
#include "base/googleinit.h"
@ -1685,14 +1686,14 @@ void LogMessage::Init(const char* file, int line, LogSeverity severity,
<< logmsgtime_.day() << ' ' << setw(2) << logmsgtime_.hour()
<< ':' << setw(2) << logmsgtime_.min() << ':' << setw(2)
<< logmsgtime_.sec() << "." << setw(6) << logmsgtime_.usec()
<< ' ' << setfill(' ') << setw(5)
<< static_cast<unsigned int>(GetTID()) << setfill('0') << ' '
<< data_->basename_ << ':' << data_->line_ << "] ";
<< ' ' << setfill(' ') << setw(5) << std::this_thread::get_id()
<< setfill('0') << ' ' << data_->basename_ << ':' << data_->line_
<< "] ";
} else {
custom_prefix_callback(
stream(),
LogMessageInfo(LogSeverityNames[severity], data_->basename_,
data_->line_, GetTID(), logmsgtime_),
data_->line_, std::this_thread::get_id(), logmsgtime_),
custom_prefix_callback_data);
stream() << " ";
}
@ -2124,7 +2125,8 @@ string LogSink::ToString(LogSeverity severity, const char* file, int line,
<< ' ' << setw(2) << logmsgtime.hour() << ':' << setw(2)
<< logmsgtime.min() << ':' << setw(2) << logmsgtime.sec() << '.'
<< setw(6) << logmsgtime.usec() << ' ' << setfill(' ') << setw(5)
<< GetTID() << setfill('0') << ' ' << file << ':' << line << "] ";
<< std::this_thread::get_id() << setfill('0') << ' ' << file << ':'
<< line << "] ";
// A call to `write' is enclosed in parenthneses to prevent possible macro
// expansion. On Windows, `write' could be a macro defined for portability.

View File

@ -44,6 +44,7 @@
# include <sys/wait.h>
#endif
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <fstream>
@ -54,6 +55,7 @@
#include <queue>
#include <sstream>
#include <string>
#include <thread>
#include <vector>
#include "base/commandlineflags.h"
@ -1197,12 +1199,9 @@ static vector<string> global_messages;
// helper for TestWaitingLogSink below.
// Thread that does the logic of TestWaitingLogSink
// It's free to use LOG() itself.
class TestLogSinkWriter : public Thread {
class TestLogSinkWriter {
public:
TestLogSinkWriter() {
SetJoinable(true);
Start();
}
TestLogSinkWriter() : t_{&TestLogSinkWriter::Run, this} {}
// Just buffer it (can't use LOG() here).
void Buffer(const string& message) {
@ -1215,11 +1214,12 @@ class TestLogSinkWriter : public Thread {
// Wait for the buffer to clear (can't use LOG() here).
void Wait() {
using namespace std::chrono_literals;
RAW_LOG(INFO, "Waiting");
mutex_.lock();
while (!NoWork()) {
mutex_.unlock();
SleepForMilliseconds(1);
std::this_thread::sleep_for(1ms);
mutex_.lock();
}
RAW_LOG(INFO, "Waited");
@ -1232,6 +1232,8 @@ class TestLogSinkWriter : public Thread {
should_exit_ = true;
}
void Join() { t_.join(); }
private:
// helpers ---------------
@ -1240,12 +1242,13 @@ class TestLogSinkWriter : public Thread {
bool HaveWork() { return !messages_.empty() || should_exit_; }
// Thread body; CAN use LOG() here!
void Run() override {
void Run() {
using namespace std::chrono_literals;
while (true) {
mutex_.lock();
while (!HaveWork()) {
mutex_.unlock();
SleepForMilliseconds(1);
std::this_thread::sleep_for(1ms);
mutex_.lock();
}
if (should_exit_ && messages_.empty()) {
@ -1255,7 +1258,7 @@ class TestLogSinkWriter : public Thread {
// Give the main thread time to log its message,
// so that we get a reliable log capture to compare to golden file.
// Same for the other sleep below.
SleepForMilliseconds(20);
std::this_thread::sleep_for(20ms);
RAW_LOG(INFO, "Sink got a messages"); // only RAW_LOG under mutex_ here
string message = messages_.front();
messages_.pop();
@ -1264,7 +1267,7 @@ class TestLogSinkWriter : public Thread {
// e.g. pushing the message over with an RPC:
size_t messages_left = messages_.size();
mutex_.unlock();
SleepForMilliseconds(20);
std::this_thread::sleep_for(20ms);
// May not use LOG while holding mutex_, because Buffer()
// acquires mutex_, and Buffer is called from LOG(),
// which has its own internal mutex:
@ -1277,6 +1280,7 @@ class TestLogSinkWriter : public Thread {
// data ---------------
std::thread t_;
std::mutex mutex_;
bool should_exit_{false};
queue<string> messages_; // messages to be logged
@ -1288,7 +1292,7 @@ class TestLogSinkWriter : public Thread {
class TestWaitingLogSink : public LogSink {
public:
TestWaitingLogSink() {
tid_ = pthread_self(); // for thread-specific behavior
tid_ = std::this_thread::get_id(); // for thread-specific behavior
AddLogSink(this);
}
~TestWaitingLogSink() override {
@ -1306,7 +1310,7 @@ class TestWaitingLogSink : public LogSink {
// Push it to Writer thread if we are the original logging thread.
// Note: Something like ThreadLocalLogSink is a better choice
// to do thread-specific LogSink logic for real.
if (pthread_equal(tid_, pthread_self())) {
if (tid_ == std::this_thread::get_id()) {
writer_.Buffer(ToString(severity, base_filename, line, logmsgtime,
message, message_len));
}
@ -1314,11 +1318,11 @@ class TestWaitingLogSink : public LogSink {
void WaitTillSent() override {
// Wait for Writer thread if we are the original logging thread.
if (pthread_equal(tid_, pthread_self())) writer_.Wait();
if (tid_ == std::this_thread::get_id()) writer_.Wait();
}
private:
pthread_t tid_;
std::thread::id tid_;
TestLogSinkWriter writer_;
};
@ -1329,15 +1333,16 @@ static void TestLogSinkWaitTillSent() {
// reentered
global_messages.clear();
{
using namespace std::chrono_literals;
TestWaitingLogSink sink;
// Sleeps give the sink threads time to do all their work,
// so that we get a reliable log capture to compare to the golden file.
LOG(INFO) << "Message 1";
SleepForMilliseconds(60);
std::this_thread::sleep_for(60ms);
LOG(ERROR) << "Message 2";
SleepForMilliseconds(60);
std::this_thread::sleep_for(60ms);
LOG(WARNING) << "Message 3";
SleepForMilliseconds(60);
std::this_thread::sleep_for(60ms);
}
for (auto& global_message : global_messages) {
LOG(INFO) << "Sink capture: " << global_message;

View File

@ -1,4 +1,4 @@
// Copyright (c) 2006, Google Inc.
// Copyright (c) 2024, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
@ -34,6 +34,10 @@
#include <cerrno>
#include <cstdarg>
#include <cstdio>
#include <iomanip>
#include <ostream>
#include <streambuf>
#include <thread>
#include "utilities.h"
#ifdef HAVE_UNISTD_H
@ -124,6 +128,26 @@ static bool crashed = false;
static CrashReason crash_reason;
static char crash_buf[kLogBufSize + 1] = {0}; // Will end in '\0'
namespace {
template <std::size_t N>
class StaticStringBuf : public std::streambuf {
public:
StaticStringBuf() {
setp(std::begin(data_), std::end(data_));
setg(std::begin(data_), std::begin(data_), std::end(data_));
}
const char* data() noexcept {
if (pptr() != pbase() && pptr() != epptr() && *(pptr() - 1) != '\0') {
sputc('\0');
}
return data_;
}
private:
char data_[N];
};
} // namespace
GLOG_ATTRIBUTE_FORMAT(printf, 4, 5)
void RawLog__(LogSeverity severity, const char* file, int line,
const char* format, ...) {
@ -132,14 +156,24 @@ void RawLog__(LogSeverity severity, const char* file, int line,
!IsGoogleLoggingInitialized())) {
return; // this stderr log message is suppressed
}
// We do not have any any option other that string streams to obtain the
// thread identifier as the corresponding value is not convertible to an
// integer. Use a statically allocated buffer to avoid dynamic memory
// allocations.
StaticStringBuf<kLogBufSize> sbuf;
std::ostream oss(&sbuf);
oss << std::setw(5) << std::this_thread::get_id();
// can't call localtime_r here: it can allocate
char buffer[kLogBufSize];
char* buf = buffer;
size_t size = sizeof(buffer);
// NOTE: this format should match the specification in base/logging.h
DoRawLog(&buf, &size, "%c00000000 00:00:00.000000 %5u %s:%d] RAW: ",
LogSeverityNames[severity][0], static_cast<unsigned int>(GetTID()),
DoRawLog(&buf, &size, "%c00000000 00:00:00.000000 %s %s:%d] RAW: ",
LogSeverityNames[severity][0], sbuf.data(),
const_basename(const_cast<char*>(file)), line);
// Record the position and size of the buffer after the prefix

View File

@ -1,4 +1,4 @@
// Copyright (c) 2023, Google Inc.
// Copyright (c) 2024, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
@ -34,6 +34,8 @@
#include <algorithm>
#include <csignal>
#include <ctime>
#include <sstream>
#include <thread>
#include "glog/logging.h"
#include "stacktrace.h"
@ -205,14 +207,12 @@ void DumpSignalInfo(int signal_number, siginfo_t* siginfo) {
formatter.AppendString(")");
formatter.AppendString(" received by PID ");
formatter.AppendUint64(static_cast<uint64>(getpid()), 10);
formatter.AppendString(" (TID 0x");
// We assume pthread_t is an integral number or a pointer, rather
// than a complex struct. In some environments, pthread_self()
// returns an uint64 but in some other environments pthread_self()
// returns a pointer.
pthread_t id = pthread_self();
formatter.AppendUint64(
reinterpret_cast<uint64>(reinterpret_cast<const char*>(id)), 16);
formatter.AppendString(" (TID ");
std::ostringstream oss;
oss << std::showbase << std::hex << std::this_thread::get_id();
formatter.AppendString(oss.str().c_str());
formatter.AppendString(") ");
// Only linux has the PID of the signal sender in si_pid.
# ifdef GLOG_OS_LINUX
@ -270,7 +270,7 @@ void InvokeDefaultSignalHandler(int signal_number) {
// dumping stuff while another thread is doing it. Our policy is to let
// the first thread dump stuff and let other threads wait.
// See also comments in FailureSignalHandler().
static pthread_t* g_entered_thread_id_pointer = nullptr;
static std::thread::id* g_entered_thread_id_pointer = nullptr;
// Dumps signal and stack frame information, and invokes the default
// signal handler once our job is done.
@ -285,21 +285,19 @@ void FailureSignalHandler(int signal_number, siginfo_t* signal_info,
// compare and swap operation for platforms that support it. For other
// platforms, we use a naive method that could lead to a subtle race.
// We assume pthread_self() is async signal safe, though it's not
// officially guaranteed.
pthread_t my_thread_id = pthread_self();
// NOTE: We could simply use pthread_t rather than pthread_t* for this,
// if pthread_self() is guaranteed to return non-zero value for thread
// ids, but there is no such guarantee. We need to distinguish if the
std::thread::id my_thread_id = std::this_thread::get_id();
// NOTE: We could simply use std::thread::id rather than std::thread::id* for
// this, if std::thread::get_id() is guaranteed to return non-zero value for
// thread ids, but there is no such guarantee. We need to distinguish if the
// old value (value returned from __sync_val_compare_and_swap) is
// different from the original value (in this case nullptr).
pthread_t* old_thread_id_pointer =
std::thread::id* old_thread_id_pointer =
glog_internal_namespace_::sync_val_compare_and_swap(
&g_entered_thread_id_pointer, static_cast<pthread_t*>(nullptr),
&g_entered_thread_id_pointer, static_cast<std::thread::id*>(nullptr),
&my_thread_id);
if (old_thread_id_pointer != nullptr) {
// We've already entered the signal handler. What should we do?
if (pthread_equal(my_thread_id, *g_entered_thread_id_pointer)) {
if (my_thread_id == *g_entered_thread_id_pointer) {
// It looks the current thread is reentering the signal handler.
// Something must be going wrong (maybe we are reentering by another
// type of signal?). Kill ourself by the default signal handler.
@ -308,7 +306,8 @@ void FailureSignalHandler(int signal_number, siginfo_t* signal_info,
// Another thread is dumping stuff. Let's wait until that thread
// finishes the job and kills the process.
while (true) {
sleep(1);
using namespace std::chrono_literals;
std::this_thread::sleep_for(1s);
}
}
// This is the first time we enter the signal handler. We are going to

View File

@ -1,4 +1,4 @@
// Copyright (c) 2008, Google Inc.
// Copyright (c) 2024, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
@ -32,17 +32,15 @@
// This is a helper binary for testing signalhandler.cc. The actual test
// is done in signalhandler_unittest.sh.
#include "utilities.h"
#if defined(HAVE_PTHREAD)
# include <pthread.h>
#endif
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <sstream>
#include <string>
#include <thread>
#include "glog/logging.h"
#include "utilities.h"
#ifdef GLOG_USE_GFLAGS
# include <gflags/gflags.h>
@ -51,19 +49,13 @@ using namespace GFLAGS_NAMESPACE;
using namespace google;
static void* DieInThread(void*) {
// We assume pthread_t is an integral number or a pointer, rather
// than a complex struct. In some environments, pthread_self()
// returns an uint64 but in some other environments pthread_self()
// returns a pointer.
fprintf(
stderr, "0x%px is dying\n",
static_cast<const void*>(reinterpret_cast<const char*>(pthread_self())));
// Use volatile to prevent from these to be optimized away.
volatile int a = 0;
volatile int b = 1 / a;
static void DieInThread(int* a) {
std::ostringstream oss;
oss << std::showbase << std::hex << std::this_thread::get_id();
fprintf(stderr, "%s is dying\n", oss.str().c_str());
int b = 1 / *a;
fprintf(stderr, "We should have died: b=%d\n", b);
return nullptr;
}
static void WriteToStdout(const char* data, size_t size) {
@ -92,14 +84,8 @@ int main(int argc, char** argv) {
while (true)
;
} else if (command == "die_in_thread") {
# if defined(HAVE_PTHREAD)
pthread_t thread;
pthread_create(&thread, nullptr, &DieInThread, nullptr);
pthread_join(thread, nullptr);
# else
fprintf(stderr, "no pthread\n");
return 1;
# endif
std::thread t{&DieInThread, nullptr};
t.join();
} else if (command == "dump_to_stdout") {
InstallFailureWriter(WriteToStdout);
abort();

View File

@ -1,4 +1,4 @@
// Copyright (c) 2008, Google Inc.
// Copyright (c) 2023, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
@ -252,53 +252,6 @@ bool PidHasChanged() {
return true;
}
pid_t GetTID() {
// On Linux and MacOSX, we try to use gettid().
#if defined GLOG_OS_LINUX || defined GLOG_OS_MACOSX
# ifndef __NR_gettid
# ifdef GLOG_OS_MACOSX
# define __NR_gettid SYS_gettid
# elif !defined __i386__
# error "Must define __NR_gettid for non-x86 platforms"
# else
# define __NR_gettid 224
# endif
# endif
static bool lacks_gettid = false;
if (!lacks_gettid) {
# if (defined(GLOG_OS_MACOSX) && defined(HAVE_PTHREAD_THREADID_NP))
uint64_t tid64;
const int error = pthread_threadid_np(nullptr, &tid64);
pid_t tid = error ? -1 : static_cast<pid_t>(tid64);
# else
auto tid = static_cast<pid_t>(syscall(__NR_gettid));
# endif
if (tid != -1) {
return tid;
}
// Technically, this variable has to be volatile, but there is a small
// performance penalty in accessing volatile variables and there should
// not be any serious adverse effect if a thread does not immediately see
// the value change to "true".
lacks_gettid = true;
}
#endif // GLOG_OS_LINUX || GLOG_OS_MACOSX
// If gettid() could not be used, we use one of the following.
#if defined GLOG_OS_LINUX
return getpid(); // Linux: getpid returns thread ID when gettid is absent
#elif defined GLOG_OS_WINDOWS && !defined GLOG_OS_CYGWIN
return static_cast<pid_t>(GetCurrentThreadId());
#elif defined GLOG_OS_OPENBSD
return getthrid();
#elif defined(HAVE_PTHREAD)
// If none of the techniques above worked, we use pthread_self().
return (pid_t)(uintptr_t)pthread_self();
#else
return -1;
#endif
}
const char* const_basename(const char* filepath) {
const char* base = strrchr(filepath, '/');
#ifdef GLOG_OS_WINDOWS // Look for either path separator in Windows

View File

@ -53,6 +53,7 @@
#define PRIoS __PRIS_PREFIX "o"
#include <string>
#include <thread>
#include "glog/logging.h"
@ -171,8 +172,6 @@ WallTime WallTime_Now();
int32 GetMainThreadPid();
bool PidHasChanged();
pid_t GetTID();
const std::string& MyUserName();
// Get the part of filepath after the last path separator.

View File

@ -107,9 +107,6 @@ enum { STDIN_FILENO = 0, STDOUT_FILENO = 1, STDERR_FILENO = 2 };
# define hash hash_compare
# endif
/* Sleep is in ms, on windows */
# define sleep(secs) Sleep((secs)*1000)
/* Windows doesn't support specifying the number of buckets as a
* hash_map constructor arg, so we leave this blank.
*/
@ -117,25 +114,8 @@ enum { STDIN_FILENO = 0, STDOUT_FILENO = 1, STDERR_FILENO = 2 };
# define DEFAULT_TEMPLATE_ROOTDIR ".."
// ----------------------------------- SYSTEM/PROCESS
typedef int pid_t;
# define getpid _getpid
# endif // _MSC_VER
// ----------------------------------- THREADS
# if defined(HAVE_PTHREAD)
# include <pthread.h>
# else // no PTHREAD
typedef DWORD pthread_t;
typedef DWORD pthread_key_t;
typedef LONG pthread_once_t;
enum { PTHREAD_ONCE_INIT = 0 }; // important that this be 0! for SpinLock
# define pthread_self GetCurrentThreadId
# define pthread_equal(pthread_t_1, pthread_t_2) \
((pthread_t_1) == (pthread_t_2))
# endif // HAVE_PTHREAD
# ifndef HAVE_LOCALTIME_R
extern GLOG_EXPORT std::tm* localtime_r(const std::time_t* timep,
std::tm* result);