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 } |
| 22 |
| 23 // Don't use stderr for errors because VS has large buffers on them, leading |
| 24 // to confusing error output. |
| 25 static void Error(const wchar_t* msg, ...) { |
| 26 tstring new_msg = tstring(L"limiter fatal error: ") + msg + L"\n"; |
| 27 va_list args; |
| 28 va_start(args, msg); |
| 29 vwprintf(new_msg.c_str(), args); |
| 30 va_end(args); |
| 31 } |
| 32 |
| 33 static void Warn(const wchar_t* msg, ...) { |
| 34 if (!g_is_debug) |
| 35 return; |
| 36 tstring new_msg = tstring(L"limiter warning: ") + msg + L"\n"; |
| 37 va_list args; |
| 38 va_start(args, msg); |
| 39 vwprintf(new_msg.c_str(), args); |
| 40 va_end(args); |
| 41 } |
| 42 |
| 43 static tstring ErrorMessageToString(DWORD err) { |
| 44 TCHAR* msg_buf = NULL; |
| 45 DWORD rc = FormatMessage( |
| 46 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, |
| 47 NULL, // lpSource |
| 48 err, |
| 49 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
| 50 reinterpret_cast<LPTSTR>(&msg_buf), |
| 51 0, // nSize |
| 52 NULL); // Arguments |
| 53 if (!rc) |
| 54 return L"unknown error"; |
| 55 tstring ret(msg_buf); |
| 56 LocalFree(msg_buf); |
| 57 return ret; |
| 58 } |
| 59 |
| 60 static DWORD RunExe(const tstring& exe_name) { |
| 61 STARTUPINFO startup_info = { sizeof(STARTUPINFO) }; |
| 62 PROCESS_INFORMATION process_info; |
| 63 DWORD exit_code; |
| 64 |
| 65 GetStartupInfo(&startup_info); |
| 66 tstring cmdline = tstring(GetCommandLine()); |
| 67 |
| 68 size_t first_space = cmdline.find(' '); |
| 69 if (first_space == -1) { |
| 70 // I'm not sure why this would ever happen, but just in case... |
| 71 cmdline = exe_name; |
| 72 } else { |
| 73 cmdline = exe_name + cmdline.substr(first_space); |
| 74 } |
| 75 |
| 76 if (!CreateProcess(NULL, // lpApplicationName |
| 77 const_cast<TCHAR*>(cmdline.c_str()), |
| 78 NULL, // lpProcessAttributes |
| 79 NULL, // lpThreadAttributes |
| 80 TRUE, // bInheritHandles |
| 81 0, // dwCreationFlags, |
| 82 NULL, // lpEnvironment, |
| 83 NULL, // lpCurrentDirectory, |
| 84 &startup_info, |
| 85 &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]), |
| 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", |
| 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 = max_concurrent_str ? _wtoi(max_concurrent_str) : 0; |
| 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(), |
| 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, 60 * 1000 /* ms */); |
| 201 } else { |
| 202 // TODO(iannucci): Maybe we should error out here instead of falling |
| 203 // back to a sleep-poll |
| 204 Sleep(5 * 1000 /* ms */); |
| 205 } |
| 206 } else { |
| 207 Warn(L"Got error %d while waiting for pipe: %s", error, |
| 208 ErrorMessageToString(error).c_str()); |
| 209 return INVALID_HANDLE_VALUE; |
| 210 } |
| 211 } |
| 212 |
| 213 return pipe; |
| 214 } |
| 215 |
| 216 static int WaitAndRun(const tstring& shimmed_exe, |
| 217 const tstring& base_pipename) { |
| 218 ULONGLONG start_time = 0, end_time = 0; |
| 219 tstring pipename = L"\\\\.\\pipe\\" + base_pipename; |
| 220 tstring event_name = L"Local\\EVENT_" + base_pipename; |
| 221 |
| 222 // This event lets us do better than strict polling, but we don't rely on it |
| 223 // (in case a process crashes before signalling the event). |
| 224 HANDLE event = CreateEvent( |
| 225 NULL, // Default security attributes |
| 226 FALSE, // Manual reset |
| 227 FALSE, // Initial state |
| 228 event_name.c_str()); |
| 229 |
| 230 if (g_is_debug) |
| 231 start_time = GetTickCount64(); |
| 232 |
| 233 HANDLE pipe = |
| 234 WaitForPipe(pipename, event, |
| 235 GetMaxConcurrency(base_pipename, CONCURRENCY_METRIC_DEFAULT)); |
| 236 |
| 237 if (g_is_debug) { |
| 238 end_time = GetTickCount64(); |
| 239 wprintf(L" took %.2fs to acquire semaphore.\n", |
| 240 (end_time - start_time) / 1000.0); |
| 241 } |
| 242 |
| 243 DWORD ret = RunExe(shimmed_exe); |
| 244 |
| 245 if (pipe != INVALID_HANDLE_VALUE) |
| 246 CloseHandle(pipe); |
| 247 if (event != NULL) |
| 248 SetEvent(event); |
| 249 |
| 250 return ret; |
| 251 } |
| 252 |
| 253 void Usage(const tstring& msg) { |
| 254 tstring usage(msg); |
| 255 usage += L"\n" |
| 256 L"Usage: SHIMED_NAME__SEMAPHORE_NAME\n" |
| 257 L"\n" |
| 258 L" SHIMMED_NAME - ex. 'link.exe' or 'lib.exe'\n" |
| 259 L" - can be exe, bat, or com\n" |
| 260 L" - must exist in PATH\n" |
| 261 L"\n" |
| 262 L" SEMAPHORE_NAME - ex. 'SOME_NAME' or 'GROOVY_SEMAPHORE'\n" |
| 263 L"\n" |
| 264 L" Example:\n" |
| 265 L" link.exe__LINK_LIMITER.exe\n" |
| 266 L" lib.exe__LINK_LIMITER.exe\n" |
| 267 L" * Both will limit on the same semaphore\n" |
| 268 L"\n" |
| 269 L" link.exe__LINK_LIMITER.exe\n" |
| 270 L" lib.exe__LIB_LIMITER.exe\n" |
| 271 L" * Both will limit on independent semaphores\n" |
| 272 L"\n" |
| 273 L" This program is meant to be run after renaming it into the\n" |
| 274 L" above format. Once you have done so, executing it will block\n" |
| 275 L" on the availability of the semaphore SEMAPHORE_NAME. Once\n" |
| 276 L" the semaphore is obtained, it will execute SHIMMED_NAME, \n" |
| 277 L" passing through all arguments as-is.\n" |
| 278 L"\n" |
| 279 L" The maximum concurrency can be manually set by setting the\n" |
| 280 L" environment variable <SEMAPHORE_NAME>_MAXCONCURRENCY to an\n" |
| 281 L" integer value (1, 254).\n" |
| 282 L" * This value must be set the same for ALL invocations.\n" |
| 283 L" * If the value is not set, it defaults to (num_cores-1).\n" |
| 284 L"\n" |
| 285 L" The semaphore is automatically released when the program\n" |
| 286 L" completes normally, OR if the program crashes (or even if\n" |
| 287 L" limiter itself crashes).\n"; |
| 288 Error(usage.c_str()); |
| 289 exit(-1); |
| 290 } |
| 291 |
| 292 // Input command line is assumed to be of the form: |
| 293 // |
| 294 // thing.exe__PIPE_NAME.exe ... |
| 295 // |
| 296 // Specifically, wait for a semaphore (whose concurrency is specified by |
| 297 // LIMITER_MAXCONCURRENT), and then pass through everything once we have |
| 298 // acquired the semaphore. |
| 299 // |
| 300 // argv[0] is parsed for: |
| 301 // * exe_to_shim_including_extension.exe |
| 302 // * This could also be a bat or com. Anything that CreateProcess will |
| 303 // accept. |
| 304 // * "__" |
| 305 // * We search for this separator from the end of argv[0], so the exe name |
| 306 // could contain a double underscore if necessary. |
| 307 // * PIPE_NAME |
| 308 // * Can only contain single underscores, not a double underscore. |
| 309 // * i.e. HELLO_WORLD_PIPE will work, but HELLO__WORLD_PIPE will not. |
| 310 // * This would allow the shimmed exe to contain arbitrary numbers of |
| 311 // underscores. We control the pipe name, but not necessarily the thing |
| 312 // we're shimming. |
| 313 // |
| 314 int wmain(int, wchar_t** argv) { |
| 315 tstring shimmed_plus_pipename = argv[0]; |
| 316 size_t last_slash = shimmed_plus_pipename.find_last_of(L"/\\"); |
| 317 if (last_slash != tstring::npos) { |
| 318 shimmed_plus_pipename = shimmed_plus_pipename.substr(last_slash + 1); |
| 319 } |
| 320 |
| 321 size_t separator = shimmed_plus_pipename.rfind(L"__"); |
| 322 if (separator == tstring::npos) { |
| 323 Usage(L"Cannot parse argv[0]. No '__' found. " |
| 324 L"Should be like '[...(\\|/)]link.exe__PIPE_NAME.exe'"); |
| 325 } |
| 326 tstring shimmed_exe = shimmed_plus_pipename.substr(0, separator); |
| 327 tstring base_pipename = shimmed_plus_pipename.substr(separator + 2); |
| 328 |
| 329 size_t dot = base_pipename.find(L'.'); |
| 330 if (dot == tstring::npos) { |
| 331 Usage(L"Expected an executable extension in argv[0]. No '.' found."); |
| 332 } |
| 333 base_pipename = base_pipename.substr(0, dot); |
| 334 |
| 335 return WaitAndRun(shimmed_exe, base_pipename); |
| 336 } |
| 337 |
OLD | NEW |