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