Skip to content
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

Merged
merged 16 commits into from
Oct 9, 2019

Conversation

BillyONeal
Copy link
Member

@BillyONeal BillyONeal commented Oct 5, 2019

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)

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.
@BillyONeal BillyONeal requested a review from a team as a code owner October 5, 2019 04:08
azure-devops/run_build.yml Outdated Show resolved Hide resolved
azure-devops/enforce-clang-format.cmd Outdated Show resolved Hide resolved
azure-devops/enforce-clang-format.cmd Outdated Show resolved Hide resolved
azure-devops/run_build.yml Outdated Show resolved Hide resolved
azure-devops/run_build.yml Show resolved Hide resolved
azure-devops/run_build.yml Show resolved Hide resolved
tools/parallelize/parallelize.cpp Outdated Show resolved Hide resolved
tools/parallelize/parallelize.cpp Outdated Show resolved Hide resolved
tools/parallelize/parallelize.cpp Outdated Show resolved Hide resolved
tools/parallelize/parallelize.cpp Show resolved Hide resolved
tools/parallelize/parallelize.cpp Outdated Show resolved Hide resolved
return *this;
}

handle& operator=(const HANDLE hNew) & noexcept {

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.

return std::exchange(hImpl, EmptyPolicy::Empty);
}

[[nodiscard]] HANDLE* out() noexcept {

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;

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.

inheritSa.lpSecurityDescriptor = nullptr;
inheritSa.bInheritHandle = TRUE;
HANDLE read;
if (!CreatePipe(&read, hDevNull.out(), &inheritSa, 0)) {

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

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)

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)

tools/inc/stljobs.h Outdated Show resolved Hide resolved
class environment_block {
public:
environment_block() {
default_block defaultBlock;

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 Show resolved Hide resolved
tools/inc/stljobs.h Outdated Show resolved Hide resolved
case ERROR_OPERATION_ABORTED:
break;
default:
api_failure("StartThreadpoolIo callback", ioResult);

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.


#include <Windows.h>

wchar_t* get_subcommand() {

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. :-)

Copy link
Member Author

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 Show resolved Hide resolved
[[nodiscard]] HANDLE detach() noexcept {
return std::exchange(hImpl, EmptyPolicy::Empty);
void attach(const HANDLE newHandle) & noexcept {
handle moved{newHandle};
Copy link
Member

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.

tools/inc/stljobs.h Show resolved Hide resolved
Copy link

@GorNishanov GorNishanov left a comment

Choose a reason for hiding this comment

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

LGTM!

}
}

[[nodiscard]] LPPROC_THREAD_ATTRIBUTE_LIST get() const {
Copy link
Member

Choose a reason for hiding this comment

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

noexcept?

output_collecting_pipe& operator=(const output_collecting_pipe&) = delete;

void start() {
[[maybe_unused]] const auto oldRunning = running.exchange(true);
Copy link
Member

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.

parent->update_display();
}

void schedule(size_t command) {
Copy link
Member

Choose a reason for hiding this comment

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

const size_t command?

@BillyONeal BillyONeal merged commit 9b2207c into microsoft:master Oct 9, 2019
@BillyONeal BillyONeal deleted the testing branch October 9, 2019 00:12
fengjixuchui added a commit to fengjixuchui/STL that referenced this pull request Oct 9, 2019
Parallelize enforcement of clang-format (microsoft#163)
throw api_exception{api, lastError};
}

void close_handle(const HANDLE toClose) noexcept {
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants