Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Implement SetThreadName in PAL for Linux. #27182

Merged
merged 4 commits into from
Nov 11, 2019

Conversation

PJB3005
Copy link

@PJB3005 PJB3005 commented Oct 14, 2019

This means thread names will now show up in gdb, htop, etc... on Linux.

I did not implement this for any other platforms:
I did not have anything to test them with, and pthread_setname_np's API varies wildly across platform. (Hell, on macOS you can't call it from any thread except the thread in question..)

I'm not very familiar with C/C++ and this is my first time contributing, so extra scrutiny would be very much appreciated.

This means thread names will now show up in gdb, htop, etc... on Linux.

I did not implement this for any other platforms:
I did not have anything to test them with,
and pthread_setname_np's API varies wildly.
@dnfclas
Copy link

dnfclas commented Oct 14, 2019

CLA assistant check
All CLA requirements met.

LOGEXIT("SetThreadNameExit");
PERF_EXIT(SetThreadName);

return NO_ERROR != palError ? 0x80004005 : 0; // E_FAIL
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure how to correctly get a hold of the HRESULT macros, or if I should even return a bog standard one like E_FAIL here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that we don't use HRESULT anywhere in PAL, so the macros to translate between win32 error (which is the same as PAL error) and HRESULT are not available. We have a couple of options:

  1. Move the
    /******************* HRESULT types ****************************************/
    #define FACILITY_WINDOWS 8
    #define FACILITY_URT 19
    #define FACILITY_UMI 22
    #define FACILITY_SXS 23
    #define FACILITY_STORAGE 3
    #define FACILITY_SSPI 9
    #define FACILITY_SCARD 16
    #define FACILITY_SETUPAPI 15
    #define FACILITY_SECURITY 9
    #define FACILITY_RPC 1
    #define FACILITY_WIN32 7
    #define FACILITY_CONTROL 10
    #define FACILITY_NULL 0
    #define FACILITY_MSMQ 14
    #define FACILITY_MEDIASERVER 13
    #define FACILITY_INTERNET 12
    #define FACILITY_ITF 4
    #define FACILITY_DPLAY 21
    #define FACILITY_DISPATCH 2
    #define FACILITY_COMPLUS 17
    #define FACILITY_CERT 11
    #define FACILITY_ACS 20
    #define FACILITY_AAF 18
    #define NO_ERROR 0L
    #define SEVERITY_SUCCESS 0
    #define SEVERITY_ERROR 1
    #define SUCCEEDED(Status) ((HRESULT)(Status) >= 0)
    #define FAILED(Status) ((HRESULT)(Status)<0)
    #define IS_ERROR(Status) ((ULONG)(Status) >> 31 == SEVERITY_ERROR) // diff from win32
    #define HRESULT_CODE(hr) ((hr) & 0xFFFF)
    #define SCODE_CODE(sc) ((sc) & 0xFFFF)
    #define HRESULT_FACILITY(hr) (((hr) >> 16) & 0x1fff)
    #define SCODE_FACILITY(sc) (((sc) >> 16) & 0x1fff)
    #define HRESULT_SEVERITY(hr) (((hr) >> 31) & 0x1)
    #define SCODE_SEVERITY(sc) (((sc) >> 31) & 0x1)
    // both macros diff from Win32
    #define MAKE_HRESULT(sev,fac,code) \
    ((HRESULT) (((ULONG)(sev)<<31) | ((ULONG)(fac)<<16) | ((ULONG)(code))) )
    #define MAKE_SCODE(sev,fac,code) \
    ((SCODE) (((ULONG)(sev)<<31) | ((ULONG)(fac)<<16) | ((LONG)(code))) )
    #define FACILITY_NT_BIT 0x10000000
    #define HRESULT_FROM_WIN32(x) ((HRESULT)(x) <= 0 ? ((HRESULT)(x)) : ((HRESULT) (((x) & 0x0000FFFF) | (FACILITY_WIN32 << 16) | 0x80000000)))
    #define __HRESULT_FROM_WIN32(x) HRESULT_FROM_WIN32(x)
    #define HRESULT_FROM_NT(x) ((HRESULT) ((x) | FACILITY_NT_BIT))
    to the pal.h, which would allow you to use the HRESULT_FROM_WIN32(palError) here.
  2. Change the function to return PAL_ERROR instead of HRESULT and do the translation to HRESULT in the wrapper function, I've mentioned in my previous comment. In that case, we should rename the function to have a PAL_ prefix to indicate it is not a 1:1 implementation of the Windows API.

I would prefer option 1

PALIMPORT
HRESULT
PALAPI
SetThreadName(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actual Windows API is named SetThreadDescription and the SetThreadName is just a wrapper to this function defined in src/utilcode/winfix.cpp. The PAL either implements Windows APIs 1:1 or use PAL_ prefix in for names of functions that are not Windows APIs.
Can you please rename this function to SetThreadDescription and add a wrapper function named SetThreadName that just calls this function? You can put it at the end of the winfix.cpp file (add #else // !FEATURE_PAL before the #endif and put the wrapper there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the result is ignored by the calling code anyway. Since this is just a diagnostic aid API, it may be best to just make a PAL_ function that returns void.

src/pal/src/thread/thread.cpp Outdated Show resolved Hide resolved
pTargetThread->Lock(pThread);

/* translate the wide char lpPathName string to multibyte string */
if(0 == (nameSize = WideCharToMultiByte( CP_ACP, 0, lpThreadDescription, -1, NULL, 0,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer moving the nameSize assignment before the if. The same for the nameBuf below. It makes it easier to read and step through the code during debugging.

LOGEXIT("SetThreadNameExit");
PERF_EXIT(SetThreadName);

return NO_ERROR != palError ? 0x80004005 : 0; // E_FAIL
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that we don't use HRESULT anywhere in PAL, so the macros to translate between win32 error (which is the same as PAL error) and HRESULT are not available. We have a couple of options:

  1. Move the
    /******************* HRESULT types ****************************************/
    #define FACILITY_WINDOWS 8
    #define FACILITY_URT 19
    #define FACILITY_UMI 22
    #define FACILITY_SXS 23
    #define FACILITY_STORAGE 3
    #define FACILITY_SSPI 9
    #define FACILITY_SCARD 16
    #define FACILITY_SETUPAPI 15
    #define FACILITY_SECURITY 9
    #define FACILITY_RPC 1
    #define FACILITY_WIN32 7
    #define FACILITY_CONTROL 10
    #define FACILITY_NULL 0
    #define FACILITY_MSMQ 14
    #define FACILITY_MEDIASERVER 13
    #define FACILITY_INTERNET 12
    #define FACILITY_ITF 4
    #define FACILITY_DPLAY 21
    #define FACILITY_DISPATCH 2
    #define FACILITY_COMPLUS 17
    #define FACILITY_CERT 11
    #define FACILITY_ACS 20
    #define FACILITY_AAF 18
    #define NO_ERROR 0L
    #define SEVERITY_SUCCESS 0
    #define SEVERITY_ERROR 1
    #define SUCCEEDED(Status) ((HRESULT)(Status) >= 0)
    #define FAILED(Status) ((HRESULT)(Status)<0)
    #define IS_ERROR(Status) ((ULONG)(Status) >> 31 == SEVERITY_ERROR) // diff from win32
    #define HRESULT_CODE(hr) ((hr) & 0xFFFF)
    #define SCODE_CODE(sc) ((sc) & 0xFFFF)
    #define HRESULT_FACILITY(hr) (((hr) >> 16) & 0x1fff)
    #define SCODE_FACILITY(sc) (((sc) >> 16) & 0x1fff)
    #define HRESULT_SEVERITY(hr) (((hr) >> 31) & 0x1)
    #define SCODE_SEVERITY(sc) (((sc) >> 31) & 0x1)
    // both macros diff from Win32
    #define MAKE_HRESULT(sev,fac,code) \
    ((HRESULT) (((ULONG)(sev)<<31) | ((ULONG)(fac)<<16) | ((ULONG)(code))) )
    #define MAKE_SCODE(sev,fac,code) \
    ((SCODE) (((ULONG)(sev)<<31) | ((ULONG)(fac)<<16) | ((LONG)(code))) )
    #define FACILITY_NT_BIT 0x10000000
    #define HRESULT_FROM_WIN32(x) ((HRESULT)(x) <= 0 ? ((HRESULT)(x)) : ((HRESULT) (((x) & 0x0000FFFF) | (FACILITY_WIN32 << 16) | 0x80000000)))
    #define __HRESULT_FROM_WIN32(x) HRESULT_FROM_WIN32(x)
    #define HRESULT_FROM_NT(x) ((HRESULT) ((x) | FACILITY_NT_BIT))
    to the pal.h, which would allow you to use the HRESULT_FROM_WIN32(palError) here.
  2. Change the function to return PAL_ERROR instead of HRESULT and do the translation to HRESULT in the wrapper function, I've mentioned in my previous comment. In that case, we should rename the function to have a PAL_ prefix to indicate it is not a 1:1 implementation of the Windows API.

I would prefer option 1

src/pal/src/thread/thread.cpp Outdated Show resolved Hide resolved
}

// Null terminate early.
// pthread_setname_np only accepts up to 16 chars.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do names of all threads that the runtime look reasonable when they are cutoff to 15 characters?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(This can be tweaked in follow up change.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Threadpool and finalizer threads at least are recognizable from my testing.

Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thank you!

@jkotas
Copy link
Member

jkotas commented Oct 25, 2019

Build breaks on Mac:

Undefined symbols for architecture x86_64:
"_SetThreadDescription", referenced from:
SetThreadName(void*, char16_t const*) in libutilcodestaticnohost.a(winfix.cpp.o)

Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please take a look at the Mac build breaks?

// pthread_setname_np only accepts up to 16 chars.
nameBuf[15] = '\0';

error = pthread_setname_np(pTargetThread->GetPThreadSelf(), nameBuf);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On FreeBSD, this is pthread_set_name_np() (and requires <pthread_np.h>). On macOS, this is pthread_setname_np(), only the thread name is passed as an argument (setting the name for the calling thread); it also takes a char* rather than a const char*, so I often copy it on the stack with strdupa() when calling it.

No need to implement all these flavors in this PR, but these are all the differences as far as I can tell, so the #ifdef __linux__ block could encase just this pthread call for all the supported platforms. (Maybe with a #else returning some "not supported" PAL_ERROR.) Maybe as a followup patch?

if (error != 0)
{
palError = ERROR_INTERNAL_ERROR;
goto InternalSetThreadDescriptionExit;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This goto can be removed (it jumps to the next line).


InternalSetThreadDescriptionExit:

if (NULL != pTargetThread)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pTargetThread won't be NULL here (if it is, then the call to lock will blow up).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it possible that InternalGetThreadDataFromHandle up above will fail and then keep the pTargetThread pointer NULL? If that were to happen this check would be necessary.

@janvorli
Copy link
Member

janvorli commented Nov 5, 2019

@PJB3005 can you please take a look at the Mac build breaks, as @jkotas has mentioned about 10 days ago? Since we will be migrating to a new repo that will share coreclr, corefx and core-setup stuff, we have about a week large window for merging this change here, otherwise it would have to be ported to the new repo.

@PJB3005
Copy link
Author

PJB3005 commented Nov 6, 2019

Oh jeez it's already been 10 days.

I don't really have a mac that I can test this on, and I can't figure out anything just looking at the build logs.

@maryamariyan
Copy link
Member

Thank you for your contribution. As announced in dotnet/coreclr#27549 this repository will be moving to dotnet/runtime on November 13. If you would like to continue working on this PR after this date, the easiest way to move the change to dotnet/runtime is:

  1. In your coreclr repository clone, create patch by running git format-patch origin
  2. In your runtime repository clone, apply the patch by running git apply --directory src/coreclr <path to the patch created in step 1>

@janvorli
Copy link
Member

janvorli commented Nov 8, 2019

I don't really have a mac that I can test this on, and I can't figure out anything just looking at the build logs.

Ok, let me try it on my Mac

@janvorli
Copy link
Member

janvorli commented Nov 8, 2019

@PJB3005 I've found the OSX build break issue. The SetThreadDescription needs to be added after the following line, prefixed with #:

The libmscordbi.dylib depends on libmscordac.dylib exporting some of the PAL symbols so that it can use the PAL linked into the libmscordac.dylib.

@PJB3005
Copy link
Author

PJB3005 commented Nov 10, 2019

That seems to have worked, thanks!

monojenkins pushed a commit to monojenkins/mono that referenced this pull request May 6, 2020
… Linux

Fixes dotnet/runtime#33673

This issue is a side-effect of adding support for setting thread names on Linux in dotnet/coreclr#27182.

cc @janvorli @lpereira @SteveL-MSFT
akoeplinger pushed a commit to dotnet/runtime that referenced this pull request May 12, 2020
… Linux (#34064)

Fixes #33673

This issue is a side-effect of adding support for setting thread names on Linux in dotnet/coreclr#27182.

Co-authored-by: Dan Moseley <danmose@microsoft.com>
Co-authored-by: Aleksey Kliger (λgeek) <akliger@gmail.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
7 participants