-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Parallelize enforcement of clang-format #163
Conversation
Add a "tools" directory for test/build support tools. Add jobify.exe from the msvc repo. Extract parts of jobify into stljobs.h, and author wrappers for other test support. Add parallelize.exe which runs a command in parallel over all inputs in a directory.
tools/inc/stljobs.h
Outdated
return *this; | ||
} | ||
|
||
handle& operator=(const HANDLE hNew) & noexcept { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rename this to "attach" by analogy with "detach" that you already have. Since, this is "adopt" ownership operation, I think it should be more conspicuous than plain assignment.
tools/inc/stljobs.h
Outdated
return std::exchange(hImpl, EmptyPolicy::Empty); | ||
} | ||
|
||
[[nodiscard]] HANDLE* out() noexcept { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would remove this API completely. It is dangerous, since, the c-APIs that you pass this handle out are unaware of any invariants this class might enforce. [I believe later in the code review we will discover the pitfall with this API, stay tuned]
inheritSa.nLength = sizeof(inheritSa); | ||
inheritSa.lpSecurityDescriptor = nullptr; | ||
inheritSa.bInheritHandle = TRUE; | ||
HANDLE read; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think read can be handle wrapper here and neither can be hDevNull (remember the warning about having .out()] API earlier?
If CreatePipe failes, it can leave arbitrary garbage in its out parameters, the wrapper will try to close it and can lead to sadness. See MSDN for CreatePipe:
If CreatePipe fails, the contents of the output parameters are indeterminate.
No assumptions should be made about their contents in this event.
tools/inc/stljobs.h
Outdated
inheritSa.lpSecurityDescriptor = nullptr; | ||
inheritSa.bInheritHandle = TRUE; | ||
HANDLE read; | ||
if (!CreatePipe(&read, hDevNull.out(), &inheritSa, 0)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace hDevNull with a raw handle. If the API call was successful, let hDevNull
adopt the resulting raw handle
tools/inc/stljobs.h
Outdated
tp_io() = default; | ||
|
||
explicit tp_io(handle<invalid_handle_value_policy>&& fileHandle_, PTP_WIN32_IO_CALLBACK callback, void* const pv, | ||
const PTP_CALLBACK_ENVIRON pcbe) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const PTP_CALLBACK_ENVIRON pcbe = nullptr
. Since we are going to use the default environment, making the parameter optional will remove the clutter at the point of use (and attempting to look up what is that nullptr last parameter do)
class environment_block { | ||
public: | ||
environment_block() { | ||
default_block defaultBlock; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be helpful to have a comment here clarifying that we are trying to find the size of the environment block which is a zero separated sequence of strings ending with two zeroes. Hence, needing to count first before assigning it to std::string.
tools/inc/stljobs.h
Outdated
case ERROR_OPERATION_ABORTED: | ||
break; | ||
default: | ||
api_failure("StartThreadpoolIo callback", ioResult); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: Failure here indicates a completion of ReadFile API with error, not a failure of StartThreadpoolIo. A future reader (of the code or logs) may be confused about which API failed.
Also, we are throwing here on a threadpool thread that does not have a try-catch and won't nice print anything. I am wondering, if we would want to log something in this case so that we are not silent when it happens. For example, we can log in the api_failure
itself.
tools/jobify/jobify.cpp
Outdated
|
||
#include <Windows.h> | ||
|
||
wchar_t* get_subcommand() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would put a comment clarifying what are we hunting for and an example string it works upon.
Also, I would explain that it returns a pointer to a non-const wchar_t
due to peculiarities of the CreateProcess API which insists on scribbling on its input arguments. :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The non-const wchar_t are because that's what GetCommandLineW returns, not any particularity of CreateProcess (although working with CreateProcess is a nice side benefit of that)
tools/inc/stljobs.h
Outdated
[[nodiscard]] HANDLE detach() noexcept { | ||
return std::exchange(hImpl, EmptyPolicy::Empty); | ||
void attach(const HANDLE newHandle) & noexcept { | ||
handle moved{newHandle}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved
seems like the wrong name here; this isn’t a move assign.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
tools/inc/stljobs.h
Outdated
} | ||
} | ||
|
||
[[nodiscard]] LPPROC_THREAD_ATTRIBUTE_LIST get() const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
noexcept?
tools/inc/stljobs.h
Outdated
output_collecting_pipe& operator=(const output_collecting_pipe&) = delete; | ||
|
||
void start() { | ||
[[maybe_unused]] const auto oldRunning = running.exchange(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Repeating previous comment) Should this be bool
instead of auto
? Ok to leave as-is if you want this.
tools/parallelize/parallelize.cpp
Outdated
parent->update_display(); | ||
} | ||
|
||
void schedule(size_t command) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const size_t command
?
Parallelize enforcement of clang-format (microsoft#163)
throw api_exception{api, lastError}; | ||
} | ||
|
||
void close_handle(const HANDLE toClose) noexcept { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can I chime in like a whole year later and say that a neat trick I discovered years ago is that by marking HANDLES like this parameter with _Post_ptr_invalid_
you get diagnostics from code analysis if you reuse handles that are closed
A component we needed as part of our minimal test harness was some ability to capture command line output from multiple subprocesses in parallel. There's a useful program we can build out of that without having test discovery working yet, just a little helper to run a command against every file in tree(s) in parallel, protected by NT Jobs. This helps flush out bugs in the scheduling machinery without also needing to worry about bugs in the test discovery/execution, as well as just producing a useful program we might use elsewhere.
I expect this to actually be most of the code for the test harness since our tests consist of "grep this tree for .cpp files, compile them with compiler switch cocktails, and ensure the output program works".
Time improvement comparison:
Most recent successful build https://dev.azure.com/vclibs/STL/_build/results?buildId=629 spends 37s enforcing clang-format.
First successful build of this PR https://dev.azure.com/vclibs/STL/_build/results?buildId=635 spends 7s building the support tools and 8s enforcing clang-format (60% less time than serial even with building
parallelize.exe
overhead)