Skip to content

Commit

Permalink
[core] Refactor ThreadName implementation
Browse files Browse the repository at this point in the history
* Add support for macOS and iOS
* Add test_threadname
  • Loading branch information
quink-black authored and maxsharabayko committed Aug 4, 2021
1 parent 1a85c02 commit 73cad8d
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 52 deletions.
182 changes: 130 additions & 52 deletions srtcore/threadname.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
* SRT - Secure, Reliable, Transport
* Copyright (c) 2018 Haivision Systems Inc.
*
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
*
*/

/*****************************************************************************
Expand All @@ -16,85 +16,163 @@ written by
#ifndef INC_SRT_THREADNAME_H
#define INC_SRT_THREADNAME_H

#ifdef __linux__

#if defined(__APPLE__) || defined(__linux__)
#if defined(__linux__)
#include <sys/prctl.h>
#endif

#include <pthread.h>
#endif

#include <cstdio>
#include <cstring>
#include <string>

#include "sync.h"

class ThreadName
{
char old_name[128];
char new_name[128];
bool good;

public:
static const size_t BUFSIZE = 128;
#if defined(__APPLE__) || defined(__linux__)

static bool get(char* namebuf)
class ThreadNameImpl
{
return prctl(PR_GET_NAME, (unsigned long)namebuf, 0, 0) != -1;
}
public:
static const size_t BUFSIZE = 64;
static const bool DUMMY_IMPL = false;

static bool set(const char* name)
{
return prctl(PR_SET_NAME, (unsigned long)name, 0, 0) != -1;
}
static bool get(char* namebuf)
{
#if defined(__linux__)
// since Linux 2.6.11. The buffer should allow space for up to 16
// bytes; the returned string will be null-terminated.
return prctl(PR_GET_NAME, (unsigned long)namebuf, 0, 0) != -1;
#elif defined(__APPLE__)
// since macos(10.6), ios(3.2)
return pthread_getname_np(pthread_self(), namebuf, BUFSIZE) == 0;
#else
#error "unsupported platform"
#endif
}

static bool set(const char* name)
{
#if defined(__linux__)
// The name can be up to 16 bytes long, including the terminating
// null byte. (If the length of the string, including the terminating
// null byte, exceeds 16 bytes, the string is silently truncated.)
return prctl(PR_SET_NAME, (unsigned long)name, 0, 0) != -1;
#elif defined(__APPLE__)
// since macos(10.6), ios(3.2)
return pthread_setname_np(name) == 0;
#else
#error "unsupported platform"
#endif
}

ThreadName(const char* name)
{
if ( (good = get(old_name)) )
ThreadNameImpl(const char* name)
{
snprintf(new_name, 127, "%s", name);
new_name[127] = 0;
prctl(PR_SET_NAME, (unsigned long)new_name, 0, 0);
reset = false;
tid = pthread_self();

if (!get(old_name))
return;

reset = set(name);
if (reset)
return;

// Try with a shorter name. 15 is the upper limit supported by Linux,
// other platforms should support a larger value. So 15 should works
// on all platforms.
size_t max_len = 15;
if (std::strlen(name) > max_len)
reset = set(std::string(name, max_len).c_str());
}
}

~ThreadName()
{
if ( good )
prctl(PR_SET_NAME, (unsigned long)old_name, 0, 0);
}
};
~ThreadNameImpl()
{
if (!reset)
return;

// ensure it's called on the right thread
if (tid == pthread_self())
set(old_name);
}

private:
bool reset;
pthread_t tid;
char old_name[BUFSIZE];
};

#else

#include "sync.h"
class ThreadNameImpl
{
public:
static const bool DUMMY_IMPL = true;
static const size_t BUFSIZE = 64;

// Fallback version, which simply reports the thread name as
// T<numeric-id>, and custom names used with `set` are ignored.
// If you know how to implement this for other systems than
// Linux, you can make another conditional. This one is now
// the "ultimate fallback".
static bool get(char* output)
{
// The default implementation will simply try to get the thread ID
std::ostringstream bs;
bs << "T" << srt::sync::this_thread::get_id();
size_t s = bs.str().copy(output, BUFSIZE - 1);
output[s] = '\0';
return true;
}

static bool set(const char*) { return false; }

ThreadNameImpl(const char*) {}

~ThreadNameImpl() // just to make it "non-trivially-destructible" for compatibility with normal version
{
}
};

#endif // platform dependent impl

// Why delegate to impl:
// 1. to make sure implementation on different platforms have the same interface.
// 2. it's simple to add some wrappers like get(const std::string &).
ThreadNameImpl impl;

class ThreadName
{
public:
static const size_t BUFSIZE = 128;
static const bool DUMMY_IMPL = ThreadNameImpl::DUMMY_IMPL;
static const size_t BUFSIZE = ThreadNameImpl::BUFSIZE;

static bool get(char* output)
{
// The default implementation will simply try to get the thread ID
std::ostringstream bs;
bs << "T" << srt::sync::this_thread::get_id();
size_t s = bs.str().copy(output, BUFSIZE-1);
output[s] = '\0';
return true;
// len should >= BUFSIZE
static bool get(char* output) {
return ThreadNameImpl::get(output);
}
static bool set(const char*) { return false; }

ThreadName(const char*)
static bool get(std::string& name)
{
char buf[BUFSIZE];
bool ret = get(buf);
if (ret)
name = buf;
return ret;
}

~ThreadName() // just to make it "non-trivially-destructible" for compatibility with normal version
// note: set can fail if name is too long. The upper limit is platform
// dependent. strlen(name) <= 15 should work on most of the platform.
static bool set(const char* name) { return ThreadNameImpl::set(name); }

static bool set(const std::string& name) { return set(name.c_str()); }

ThreadName(const char* name)
: impl(name)
{
}

ThreadName(const std::string& name)
: impl(name.c_str())
{
}
};



#endif
#endif
1 change: 1 addition & 0 deletions test/filelist.maf
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ test_muxer.cpp
test_seqno.cpp
test_socket_options.cpp
test_sync.cpp
test_threadname.cpp
test_timer.cpp
test_unitqueue.cpp
test_utilities.cpp
Expand Down
61 changes: 61 additions & 0 deletions test/test_threadname.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#include <algorithm>
#include <string>

#include "gtest/gtest.h"
#include "threadname.h"

TEST(ThreadName, GetSet)
{
std::string name("getset");
char buf[ThreadName::BUFSIZE * 2];

memset(buf, 'a', sizeof(buf));
ASSERT_EQ(ThreadName::get(buf), true);
// ensure doesn't write out-of-range
size_t max = ThreadName::BUFSIZE - 1;
ASSERT_LE(strlen(buf), max);

if (ThreadName::DUMMY_IMPL)
return;

ASSERT_EQ(ThreadName::set(name), true);
memset(buf, 'a', sizeof(buf));
ASSERT_EQ(ThreadName::get(buf), true);
ASSERT_EQ(buf, name);
}

TEST(ThreadName, AutoReset)
{
std::string old_name("old");
std::string new_name("new-name");
if (ThreadName::DUMMY_IMPL)
{
// just make sure the API is correct
ThreadName t("test");
return;
}

ASSERT_EQ(ThreadName::set(old_name), true);
std::string name;
ASSERT_EQ(ThreadName::get(name), true);
ASSERT_EQ(name, old_name);

{
ThreadName threadName(new_name);
ASSERT_EQ(ThreadName::get(name), true);
ASSERT_EQ(name, new_name);
}

ASSERT_EQ(ThreadName::get(name), true);
ASSERT_EQ(name, old_name);

{
new_name.resize(std::max<size_t>(512, ThreadName::BUFSIZE * 2), 'z');
ThreadName threadName(new_name);
ASSERT_EQ(ThreadName::get(name), true);
ASSERT_EQ(new_name.compare(0, name.size(), name), 0);
}

ASSERT_EQ(ThreadName::get(name), true);
ASSERT_EQ(name, old_name);
}

0 comments on commit 73cad8d

Please sign in to comment.