-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
Use anonymous pipes for stdout/stderr redirection. #2472
base: devel
Are you sure you want to change the base?
Conversation
Codecov Report
@@ Coverage Diff @@
## devel #2472 +/- ##
==========================================
- Coverage 91.49% 91.28% -0.20%
==========================================
Files 181 185 +4
Lines 7519 7596 +77
==========================================
+ Hits 6879 6934 +55
- Misses 640 662 +22 |
Closes catchorg#2444. Avoid the overhead of creating and deleting temporary files. Anonymous pipes have a limited buffer and require an active reader to ensure the writer does not become blocked. Use a separate thread to ensure the buffer does not get stuck full.
7170296
to
ac345f8
Compare
@horenmar - Please let me know what you think of how this approached worked out. |
6aa8be8
to
713e31c
Compare
@horenmar - it looks like you triggered a new validation run (thanks!). For this one:
For pthread_create - that's not something I wrote directly (actually I can't find "pthread_create" at all in this repo). I'm guessing it's some kind of build setting needed on Linux since it's using threads. The closest reference I could find in the repo was:
Is there some place -pthread needs to be enabled in a build script somewhere for this change to work? (Enabling threading on Linux is something I'm not familiar with.) Thanks! |
Technically, first one. 😃 GHA don't run by default for PRs by new contributors, because people are asshats and try to use free resources for mining. But that's not the important part. The underlying issue is that libstdc++ (and IIRC libc++ as well) rely on pthread library for their implementation of std threading support. This means that building code that uses The code you've found detects whether Catch2 is being compiled in a way that allows us to optionally use threads (MSVC always has thread support, Enabling thread support for a target in CMake is simple enough, but the issue is whether doing so is something I want to do.
This would mean that the threading requirement would have to be optional and the implementation of the experimental capture would have to properly handle the option that there are no threads enabled. One more thing I don't like about using threads in the experimental capture is that given the issue in 1), I don't think it could ever be promoted into non-experimental implementation. * Actually it is slightly more complex, as the compiler flag does something different than the linker flag, and their behaviour still differs between GCC and Clang. Let nobody say that the C++ toolchains are sane. |
@horenmar - thanks for the reply! With pipes, something needs to keep reading the pipe, or the OS buffer can get full and the writer will block. The only solutions I'm aware of for this problem are: We could investigate option b, though I suspect platform support for non-blocking reads is less common than threads. I can understand the desire to support Catch2 on more-limited platforms and thus avoid the use of threads as a hard requirement. At the same time, I think for capturing stdout/stderr, pipes are the best solution, as they avoid the problems with temp files we've seen earlier. Other currently problems with files seem likely either just to go away or be more readily soleable as well (perhaps including #1902, #1991). A few overall options I can think of:
There may be other options I'm missing as well. I tend to think option 4 is likely the best option - thoughts? EDIT:
Maybe any implementation that goes all the way down to the handle level is going not to be supported on the most limited platforms, so maybe saying such features only work on more full-featured platforms is just fine, and in keeping with what's possible without this PR anyway. (i.e., so maybe option 1 is more attractive) Thoughts? |
@horenmar - one option is just to go with the original plan and make the pipes implementation be just for Windows, and keep the existing temp file-based code for other environments. (Though since basically the same code works on Linux as well, when threads are enabled, it would probably make the most sense to have the #if be something like "MSVC/Windows or Pthreads enabled", since that works too.) I'm not sure what types of more constrained systems you're most interested in supporting, so I'm curious to hear your thoughts on how to consider more limited systems. On that topic, it's worth mentioning that there may be some systems where pipes + threads would be a better option than temp files. For example, Stroustrup mentions in chapter 15 of The C++ Programming Language how some systems compile without using files. At runtime, some systems have no filesystem (or may have a read-only filesystem). Interestingly, I found one person looking for a unit testing framework for a system with no filesystem, and the one he found didn't work because it used a temp file to redirect stdout/stderr: So I think which technique works best depends on the system, and I'm not sure which of the following systems are in-scope for what you'd want to support:
|
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 think this is fine, but it will have to be a parallel implementation to the current temp-file based approach, so that there will be 3 different capture impls in the end.
(Also please set up your IDE to use .clang-format
from the repo)
I am not convinced that a quarter-baked
🤷 I don't think it will provide useful error messages like this, but that can be fixed later on. |
Keep previous implementation when threads are not available.
Sure, I've moved it just to join in the destructor. |
I'm not sure how much coverage there is for both variations of CATCH_CONFIG_NEW_CAPTURE (temp file vs pipe/thread). Ideally, we'd have:
|
@horenmar - any further thoughts on this PR? thanks! |
@horenmar - does this PR look good to you? Thanks! |
So I finally got around to this after redoing the redirect to avoid issues I refactored the code here on top of the current interface in branch
against binary compiled on the above mentioned branch, and against binary time to run in ms
From this, the difference does not seem to rise beyond noise. I am also seeing an issue, where with the optimized implementation, You can play with this with the I am reasonably sure I can come up with a more complex synchronization
Given this, I am currently not convinced that this is worth merging - the |
@horenmar - thanks for taking a look! I think the benefits of a pipes approach would be functional as much as, or more than, performance. For example, printf working, as your test shows, or launching a child process (at least on Windows) that writes to the same stdout/stderr stream. I've had situations where we needed to do this kind of redirection before. It can be a little tricky to get it working correctly, but once it's working reliably, we haven't had further issues. My best guess is there's a missing flush (or assumption of futher buffering) at a higher layer that's causing the missing writes. For example, I think it's more complex to get this code right with a combined thread and pipe for stdout vs stderr - I tend to think it's simpler and more reliable to have distinct channels for each. Did you see cases where the original PR here was sometimes not getting all the text through? |
Re functionality: When the original issue was open, the temp files implementation was already there, but it could not handle running many tests in single process (the limit was platform dependent, but even the SelfTest binary would reach it very quickly). The underlying issue was that Catch2 would create at least 2 temp files per partial test case run, and quickly run into temp file limit. However this limitation was lifted with the linked commit, so the temp file approach works perfectly on Windows, and reasonably well on Linux*.
That's what is in the updated branch as well, I am not going to write code polling multiple fds unless I really have to 😃
No, but the performance was completely awful. In the devel-piped-redirect branch, there are three commits -> the first one updates this PR naively to the current interface, the second one refactors it for reasonable performance and the third one contains some changes to the test with The refactoring in second commit makes it so that when the capture is deactivated, the main thread first flushes the FD, then replaces it with the original FD of that stream, and then gets the buffer from the reading thread. However, it is simple to lose output here, because the reading thread might not have been scheduled to finish reading the pipe yet, before the main thread takes the mutex protecting the combined output. * Older libcs also exhibit a lost-print issue, but only when running many tests in single process. I never reproduced it when running just a single test in the process. |
Thanks for the additional context. With the original approach, I'm guessing it was the thread that was the higher cost than the pipe creation - do we know which was expensive, or was it both? Would setting up the pipes/threads at process startup, and keeping them installed until process cleanup, be an option? Then it could just capture whatever had been passed in since the last test output was connected; if something in the stack failed to flush, it would still be captured but would just show up in the next test instead of getting lost. Would that be any better? |
Closes #2444.
Avoid the overhead of creating and deleting temporary files.
Anonymous pipes have a limited buffer and require an active reader to
ensure the writer does not become blocked. Use a separate thread to
ensure the buffer does not get stuck full.
A couple of questions: