OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include <stdio.h> | |
6 #include <stdlib.h> | |
7 | |
8 #define NOMINMAX | |
9 #include <windows.h> | |
10 | |
11 #include <algorithm> | |
12 #include <iterator> | |
13 #include <sstream> | |
14 #include <string> | |
15 #include <vector> | |
16 | |
17 typedef std::basic_string<TCHAR> tstring; | |
18 | |
19 namespace { | |
20 const bool g_is_debug = (_wgetenv(L"LIMITER_DEBUG") != NULL); | |
21 const int g_wait_time_in_ms = 30 * 1000; // 30 seconds | |
22 } | |
23 | |
24 // Don't use stderr for errors because VS has large buffers on them, leading | |
25 // to confusing error output. | |
26 static void Error(const wchar_t* msg, ...) { | |
27 tstring new_msg = tstring(L"limiter fatal error: ") + msg + L"\n"; | |
28 va_list args; | |
29 va_start(args, msg); | |
30 vwprintf(new_msg.c_str(), args); | |
31 va_end(args); | |
32 } | |
33 | |
34 static void Warn(const wchar_t* msg, ...) { | |
35 if (!g_is_debug) | |
36 return; | |
37 tstring new_msg = tstring(L"limiter warning: ") + msg + L"\n"; | |
38 va_list args; | |
39 va_start(args, msg); | |
40 vwprintf(new_msg.c_str(), args); | |
41 va_end(args); | |
42 } | |
43 | |
44 static tstring ErrorMessageToString(DWORD err) { | |
45 TCHAR* msg_buf = NULL; | |
46 DWORD rc = FormatMessage( | |
47 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, | |
48 NULL, | |
49 err, | |
50 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), | |
51 reinterpret_cast<LPTSTR>(&msg_buf), | |
52 0, | |
53 NULL); | |
54 if (!rc) | |
55 return L"unknown error"; | |
56 tstring ret(msg_buf); | |
57 LocalFree(msg_buf); | |
58 return ret; | |
59 } | |
60 | |
61 static DWORD RunExe(const tstring& exe_name) { | |
62 STARTUPINFO startup_info = { sizeof(STARTUPINFO) }; | |
63 PROCESS_INFORMATION process_info; | |
64 DWORD exit_code; | |
65 | |
66 GetStartupInfo(&startup_info); | |
67 tstring cmdline = tstring(GetCommandLine()); | |
68 | |
69 size_t first_space = cmdline.find(' '); | |
70 if (first_space == -1) { | |
71 // I'm not sure why this would ever happen, but just in case... | |
72 cmdline = exe_name; | |
73 } else { | |
74 cmdline = exe_name + cmdline.substr(first_space); | |
75 } | |
76 | |
77 if (!CreateProcess(NULL, | |
78 const_cast<TCHAR*>(cmdline.c_str()), | |
79 NULL, | |
80 NULL, | |
81 TRUE, | |
82 0, | |
83 NULL, | |
84 NULL, | |
85 &startup_info, &process_info)) { | |
86 Error(L"Error in CreateProcess[%s]: %s", | |
87 cmdline.c_str(), ErrorMessageToString(GetLastError()).c_str()); | |
88 return MAXDWORD; | |
89 } | |
90 CloseHandle(process_info.hThread); | |
91 WaitForSingleObject(process_info.hProcess, INFINITE); | |
92 GetExitCodeProcess(process_info.hProcess, &exit_code); | |
93 CloseHandle(process_info.hProcess); | |
94 return exit_code; | |
95 } | |
96 | |
97 // Returns 0 if there was an error | |
98 static int CpuConcurrencyMetric(const tstring& envvar_name) { | |
99 int max_concurrent = 0; | |
100 std::vector<char> buffer(1); | |
101 BOOL ok = false; | |
102 DWORD last_error = 0; | |
103 do { | |
104 DWORD bufsize = buffer.size(); | |
105 ok = GetLogicalProcessorInformation( | |
106 reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION>(&buffer[0]), | |
Lei Zhang
2012/08/03 05:03:23
4 spaces here.
| |
107 &bufsize); | |
108 last_error = GetLastError(); | |
109 if (!ok && last_error == ERROR_INSUFFICIENT_BUFFER && | |
110 bufsize > buffer.size()) { | |
111 buffer.resize(bufsize); | |
112 } | |
113 } while (!ok && last_error == ERROR_INSUFFICIENT_BUFFER); | |
114 | |
115 if (!ok) { | |
116 Warn(L"Error while getting number of cores. Try setting the " | |
117 L" environment variable '%s' to (num_cores-1): %s", | |
Lei Zhang
2012/08/03 05:03:23
foo - bar instead of foo-bar here and below.
| |
118 envvar_name.c_str(), ErrorMessageToString(last_error).c_str()); | |
119 return 0; | |
120 } | |
121 | |
122 PSYSTEM_LOGICAL_PROCESSOR_INFORMATION pproc_info = | |
123 reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION>(&buffer[0]); | |
124 int num_entries = buffer.size() / | |
125 sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); | |
126 | |
127 for (int i = 0; i < num_entries; ++i) { | |
128 SYSTEM_LOGICAL_PROCESSOR_INFORMATION& info = pproc_info[i]; | |
129 if (info.Relationship == RelationProcessorCore) { | |
130 ++max_concurrent; | |
131 } | |
132 } | |
133 | |
134 // Leave one core for other tasks | |
135 return max_concurrent-1; | |
136 } | |
137 | |
138 // TODO(defaults): Create a better heuristic than # of CPUs. It seems likely | |
139 // that the right value will, in fact, be based on the memory capacity of the | |
140 // machine, not on the number of CPUs. | |
141 enum ConcurrencyMetricEnum { | |
142 CONCURRENCY_METRIC_ONE, | |
143 CONCURRENCY_METRIC_CPU, | |
144 CONCURRENCY_METRIC_DEFAULT = CONCURRENCY_METRIC_CPU | |
145 }; | |
146 | |
147 static int GetMaxConcurrency(const tstring& base_pipename, | |
148 ConcurrencyMetricEnum metric) { | |
149 static int max_concurrent = -1; | |
150 | |
151 if (max_concurrent == -1) { | |
152 tstring envvar_name = base_pipename + L"_MAXCONCURRENCY"; | |
153 | |
154 const LPTSTR max_concurrent_str = _wgetenv(envvar_name.c_str()); | |
155 max_concurrent = _wtoi(max_concurrent_str ? max_concurrent_str : L"0"); | |
Lei Zhang
2012/08/03 05:03:23
= max_concurrent_str ? _wtoi(max_concurrent_str) :
iannucci
2012/08/03 05:50:34
Not sure about this one. I was attempting to preve
Lei Zhang
2012/08/03 06:09:54
Yep, I know, but my suggestion also avoids calling
| |
156 | |
157 if (max_concurrent == 0) { | |
158 switch (metric) { | |
159 case CONCURRENCY_METRIC_CPU: | |
160 max_concurrent = CpuConcurrencyMetric(envvar_name); | |
161 if (max_concurrent) | |
162 break; | |
163 // else fall through | |
164 case CONCURRENCY_METRIC_ONE: | |
165 max_concurrent = 1; | |
166 break; | |
167 } | |
168 } | |
169 | |
170 max_concurrent = std::min(std::max(max_concurrent, 1), | |
171 PIPE_UNLIMITED_INSTANCES); | |
172 } | |
173 | |
174 return max_concurrent; | |
175 } | |
176 | |
177 static HANDLE WaitForPipe(const tstring& pipename, | |
178 HANDLE event, | |
179 int max_concurrency) { | |
180 // We're using a named pipe instead of a semaphore so the Kernel can clean up | |
181 // after us if we crash while holding onto the pipe (A real semaphore will | |
182 // not release on process termination). | |
183 HANDLE pipe = INVALID_HANDLE_VALUE; | |
184 for (;;) { | |
185 pipe = CreateNamedPipe( | |
186 pipename.c_str(), | |
Lei Zhang
2012/08/03 05:03:23
4 spaces
| |
187 PIPE_ACCESS_DUPLEX, | |
188 PIPE_TYPE_BYTE, | |
189 max_concurrency, | |
190 1, // nOutBufferSize | |
191 1, // nInBufferSize | |
192 0, // nDefaultTimeOut | |
193 NULL); // Default security attributes (noinherit) | |
194 if (pipe != INVALID_HANDLE_VALUE) | |
195 break; | |
196 | |
197 DWORD error = GetLastError(); | |
198 if (error == ERROR_PIPE_BUSY) { | |
199 if (event) { | |
200 WaitForSingleObject(event, g_wait_time_in_ms); | |
201 } else { | |
202 Sleep(g_wait_time_in_ms); | |
203 } | |
204 } else { | |
205 Warn(L"Got error %d while waiting for pipe: %s", error, | |
206 ErrorMessageToString(error).c_str()); | |
207 return INVALID_HANDLE_VALUE; | |
208 } | |
209 } | |
210 | |
211 return pipe; | |
212 } | |
213 | |
214 static int WaitAndRun(const tstring& shimmed_exe, | |
215 const tstring& base_pipename) { | |
216 ULONGLONG start_time = 0, end_time = 0; | |
217 tstring pipename = L"\\\\.\\pipe\\" + base_pipename; | |
218 tstring event_name = L"Local\\EVENT_" + base_pipename; | |
219 | |
220 // This event lets us do better than strict polling, but we don't rely on it | |
221 // (in case a process crashes before signalling the event). | |
222 HANDLE event = CreateEvent( | |
223 NULL, // Default security attributes | |
Lei Zhang
2012/08/03 05:03:23
put this on the previous line or 4 space indent.
| |
224 FALSE, // Manual reset | |
225 FALSE, // Initial state | |
226 event_name.c_str()); | |
227 | |
228 if (g_is_debug) | |
229 start_time = GetTickCount64(); | |
230 | |
231 HANDLE pipe = | |
232 WaitForPipe(pipename, event, | |
233 GetMaxConcurrency(base_pipename, CONCURRENCY_METRIC_DEFAULT)); | |
234 | |
235 if (g_is_debug) { | |
236 end_time = GetTickCount64(); | |
237 wprintf(L" took %.2fs to acquire semaphore.\n", | |
238 (end_time - start_time) / 1000.0); | |
Lei Zhang
2012/08/03 05:03:23
indent
| |
239 } | |
240 | |
241 DWORD ret = RunExe(shimmed_exe); | |
242 | |
243 if (pipe != INVALID_HANDLE_VALUE) | |
244 CloseHandle(pipe); | |
245 if (event != NULL) | |
246 SetEvent(event); | |
247 | |
248 return ret; | |
249 } | |
250 | |
251 void Usage(const tstring& msg) { | |
252 tstring usage(msg); | |
253 usage += L"\n" | |
254 L"Usage: SHIMED_NAME__SEMAPHORE_NAME\n" | |
255 L"\n" | |
256 L" SHIMMED_NAME - ex. 'link.exe' or 'lib.exe'\n" | |
257 L" - can be exe, bat, or com\n" | |
258 L" - must exist in PATH\n" | |
259 L"\n" | |
260 L" SEMAPHORE_NAME - ex. 'SOME_NAME' or 'GROOVY_SEMAPHORE'\n" | |
261 L"\n" | |
262 L" Example:\n" | |
263 L" link.exe__LINK_LIMITER.exe\n" | |
264 L" lib.exe__LINK_LIMITER.exe\n" | |
265 L" * Both will limit on the same semaphore\n" | |
266 L"\n" | |
267 L" link.exe__LINK_LIMITER.exe\n" | |
268 L" lib.exe__LIB_LIMITER.exe\n" | |
269 L" * Both will limit on independent semaphores\n" | |
270 L"\n" | |
271 L" This program is meant to be run after renaming it into the\n" | |
272 L" above format. Once you have done so, executing it will block\n" | |
273 L" on the availability of the semaphore SEMAPHORE_NAME. Once\n" | |
274 L" the semaphore is obtained, it will execute SHIMMED_NAME, \n" | |
275 L" passing through all arguments as-is.\n" | |
276 L"\n" | |
277 L" The maximum concurrency can be manually set by setting the\n" | |
278 L" environment variable <SEMAPHORE_NAME>_MAXCONCURRENCY to an\n" | |
279 L" integer value (1, 254).\n" | |
280 L" * This value must be set the same for ALL invocations.\n" | |
281 L" * If the value is not set, it defaults to (num_cores-1).\n" | |
282 L"\n" | |
283 L" The semaphore is automatically released when the program\n" | |
284 L" completes normally, OR if the program crashes (or even if\n" | |
285 L" limiter itself crashes).\n"; | |
286 Error(usage.c_str()); | |
287 exit(-1); | |
288 } | |
289 | |
290 // Input command line is assumed to be of the form: | |
291 // | |
292 // thing.exe__PIPE_NAME.exe ... | |
293 // | |
294 // Specifically, wait for a semaphore (whose concurrency is specified by | |
295 // LIMITER_MAXCONCURRENT), and then pass through everything once we have | |
296 // acquired the semaphore. | |
297 // | |
298 // argv[0] is parsed for: | |
299 // * exe_to_shim_including_extension.exe | |
300 // * This could also be a bat or com. Anything that CreateProcess will | |
301 // accept. | |
302 // * "__" | |
303 // * We search for this separator from the end of argv[0], so the exe name | |
304 // could contain a double underscore if necessary. | |
305 // * PIPE_NAME | |
306 // * Can only contain single underscores, not a double underscore. | |
307 // * i.e. HELLO_WORLD_PIPE will work, but HELLO__WORLD_PIPE will not. | |
308 // * This would allow the shimmed exe to contain arbitrary numbers of | |
309 // underscores. We control the pipe name, but not necessarily the thing | |
310 // we're shimming. | |
311 // | |
312 int wmain(int, wchar_t** argv) { | |
313 tstring shimmed_plus_pipename = argv[0]; | |
314 size_t last_slash = shimmed_plus_pipename.find_last_of(L"/\\"); | |
315 if (last_slash != tstring::npos) { | |
316 shimmed_plus_pipename = shimmed_plus_pipename.substr(last_slash + 1); | |
317 } | |
318 | |
319 size_t separator = shimmed_plus_pipename.rfind(L"__"); | |
320 if (separator == tstring::npos) { | |
321 Usage(L"Cannot parse argv[0]. No '__' found. " | |
322 L"Should be like '[...(\\|/)]link.exe__PIPE_NAME.exe'"); | |
323 } | |
324 tstring shimmed_exe = shimmed_plus_pipename.substr(0, separator); | |
325 tstring base_pipename = shimmed_plus_pipename.substr(separator + 2); | |
326 | |
327 size_t dot = base_pipename.find(L'.'); | |
328 if (dot == tstring::npos) { | |
329 Usage(L"Expected an executable extension in argv[0]. No '.' found."); | |
330 } | |
331 base_pipename = base_pipename.substr(0, dot); | |
332 | |
333 return WaitAndRun(shimmed_exe, base_pipename); | |
334 } | |
335 | |
OLD | NEW |