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