载入中...
搜索中...
未找到
StderrCapture.cpp
浏览该文件的文档.
2
3#include <atomic>
4#include <cstdio>
5#include <cstdlib>
6#include <mutex>
7#include <string>
8#include <thread>
9#include <utility>
10
11#if defined(_WIN32)
12#include <fcntl.h>
13#include <io.h>
14#else
15#include <fcntl.h>
16#include <unistd.h>
17#endif
18
19namespace eve::dev {
20namespace {
21
22// 64 KiB keeps a chatty validation run from blocking the writing thread while
23// the drain thread is between reads.
24constexpr int kPipeBytes = 64 * 1024;
25
26std::mutex g_mutex;
27std::thread g_thread;
28StderrLineSink g_sink;
29std::atomic<bool> g_active{false};
30int g_savedFd = -1;
31int g_readFd = -1;
32bool g_atexitRegistered = false;
33
34int stderrFd() {
35#if defined(_WIN32)
36 return _fileno(stderr);
37#else
38 return fileno(stderr);
39#endif
40}
41
42void closeFd(int fd) {
43#if defined(_WIN32)
44 _close(fd);
45#else
46 close(fd);
47#endif
48}
49
50int duplicateFd(int fd) {
51#if defined(_WIN32)
52 return _dup(fd);
53#else
54 return dup(fd);
55#endif
56}
57
58int replaceFd(int source, int target) {
59#if defined(_WIN32)
60 return _dup2(source, target);
61#else
62 return dup2(source, target);
63#endif
64}
65
66int writeFd(int fd, const char* data, int size) {
67#if defined(_WIN32)
68 return _write(fd, data, static_cast<unsigned>(size));
69#else
70 return static_cast<int>(write(fd, data, static_cast<std::size_t>(size)));
71#endif
72}
73
74int readFd(int fd, char* data, int size) {
75#if defined(_WIN32)
76 return _read(fd, data, static_cast<unsigned>(size));
77#else
78 return static_cast<int>(read(fd, data, static_cast<std::size_t>(size)));
79#endif
80}
81
83void flushCompleteLines(std::string& pending) {
84 for (;;) {
85 const auto newline = pending.find('\n');
86 if (newline == std::string::npos) break;
87 std::string line = pending.substr(0, newline);
88 pending.erase(0, newline + 1);
89 if (!line.empty() && line.back() == '\r') line.pop_back();
90 if (g_sink) g_sink(line);
91 }
92}
93
94void drainLoop() {
95 std::string pending;
96 char buffer[4096];
97 for (;;) {
98 const int read = readFd(g_readFd, buffer, static_cast<int>(sizeof(buffer)));
99 if (read <= 0) break;
100 // Preserve the stream first: diagnostics must never be swallowed just
101 // because the console sink is slow or detached.
102 if (g_savedFd >= 0) {
103 int offset = 0;
104 while (offset < read) {
105 const int written = writeFd(g_savedFd, buffer + offset, read - offset);
106 if (written <= 0) break;
107 offset += written;
108 }
109 }
110 pending.append(buffer, static_cast<std::size_t>(read));
111 flushCompleteLines(pending);
112 }
113 if (!pending.empty()) {
114 std::string line = std::move(pending);
115 pending.clear();
116 if (!line.empty() && line.back() == '\r') line.pop_back();
117 if (g_sink) g_sink(line);
118 }
119}
120
121} // namespace
122
124 std::lock_guard<std::mutex> lock(g_mutex);
125 if (g_active.load()) return StderrCaptureStatus::Active;
126 if (!sink) return StderrCaptureStatus::Rejected;
127
128 if (!g_atexitRegistered) {
129 // Restore the real stderr before static destructors run, so a late
130 // exit-time message cannot land in a pipe whose reader is gone.
131 std::atexit(&stopStderrCapture);
132 g_atexitRegistered = true;
133 }
134
135 int fds[2] = {-1, -1};
136#if defined(_WIN32)
137 if (_pipe(fds, kPipeBytes, _O_BINARY | _O_NOINHERIT) != 0) return StderrCaptureStatus::Unavailable;
138#else
139 if (pipe(fds) != 0) return StderrCaptureStatus::Unavailable;
140#endif
141
142 std::fflush(stderr);
143 const int target = stderrFd();
144 const int saved = duplicateFd(target);
145 if (saved < 0) {
146 closeFd(fds[0]);
147 closeFd(fds[1]);
149 }
150 if (replaceFd(fds[1], target) < 0) {
151 closeFd(saved);
152 closeFd(fds[0]);
153 closeFd(fds[1]);
155 }
156 // Descriptor 2 now owns the write end; keeping this extra handle would hold
157 // the pipe open forever and hide EOF from the drain thread.
158 closeFd(fds[1]);
159
160 g_savedFd = saved;
161 g_readFd = fds[0];
162 g_sink = std::move(sink);
163 g_active.store(true);
164 g_thread = std::thread(drainLoop);
166}
167
169 std::lock_guard<std::mutex> lock(g_mutex);
170 if (!g_active.load()) return;
171
172 // Restore descriptor 2 first: that releases the last pipe writer, so the
173 // drain thread observes EOF and can be joined instead of racing a closed
174 // handle. The saved descriptor stays open across the join so the remaining
175 // buffered lines still reach the original stream.
176 std::fflush(stderr);
177 const int saved = g_savedFd;
178 if (saved >= 0) replaceFd(saved, stderrFd());
179 if (g_thread.joinable()) g_thread.join();
180 if (saved >= 0) closeFd(saved);
181 if (g_readFd >= 0) closeFd(g_readFd);
182
183 g_savedFd = -1;
184 g_readFd = -1;
185 g_sink = StderrLineSink{};
186 g_active.store(false);
187}
188
189bool stderrCaptureActive() { return g_active.load(); }
190
191} // namespace eve::dev
LogicalId target
std::vector< QuestEvent > pending
size_t offset
std::unique_ptr< gpgpu::GpuBuffer > buffer
Definition OnnxGpgpu.cpp:26
Mirror process stderr into an in-process line sink without hiding it.
float size
Definition TreeMesh.cpp:156
const UnitySourceAsset & source
std::function< void(const std::string &)> StderrLineSink
Sink invoked on the drain thread for every complete stderr line.
StderrCaptureStatus
Outcome of a capture request.
void stopStderrCapture()
Restore stderr and join the drain thread. No-op when inactive.
bool stderrCaptureActive()
Whether stderr is currently mirrored into a sink.
StderrCaptureStatus startStderrCapture(StderrLineSink sink)
Start mirroring stderr into sink.
T read(const RuntimeTensor &v, size_t i)
Reads read.