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 #ifndef SHIM_EXE |
| 18 # error You must define SHIM_EXE when building (like "link.exe" or "lib.exe") |
| 19 #endif |
| 20 |
| 21 typedef std::basic_string<TCHAR> tstring; |
| 22 |
| 23 const bool g_is_debug = (_wgetenv(L"LIMITER_DEBUG") != NULL); |
| 24 const int g_wait_time = 30 * 1000; // 30 seconds |
| 25 const LPTSTR g_pipe_name = L"\\\\.\\pipe\\LIMITER_PIPE"; |
| 26 const LPTSTR g_event_name = L"Local\\LIMITER_EVENT"; |
| 27 const LPTSTR g_envvar_name = L"LIMITER_MAXCONCURRENT"; |
| 28 |
| 29 // Don't use stderr for errors because VS has large buffers on them, leading |
| 30 // to confusing error output. |
| 31 static void Fatal(const wchar_t* msg) { |
| 32 wprintf(L"limiter(" SHIM_EXE L") fatal error: %s\n", msg); |
| 33 exit(1); |
| 34 } |
| 35 |
| 36 static tstring ErrorMessageToString(DWORD err) { |
| 37 wchar_t* msg_buf = NULL; |
| 38 DWORD rc = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | |
| 39 FORMAT_MESSAGE_FROM_SYSTEM, |
| 40 NULL, |
| 41 err, |
| 42 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
| 43 reinterpret_cast<LPTSTR>(&msg_buf), |
| 44 0, |
| 45 NULL); |
| 46 if (!rc) |
| 47 return L"unknown error"; |
| 48 tstring ret(msg_buf); |
| 49 LocalFree(msg_buf); |
| 50 return ret; |
| 51 } |
| 52 |
| 53 static DWORD RunExe() { |
| 54 STARTUPINFO startup_info = { sizeof(STARTUPINFO) }; |
| 55 PROCESS_INFORMATION process_info; |
| 56 DWORD exit_code; |
| 57 |
| 58 GetStartupInfo(&startup_info); |
| 59 tstring cmdline = tstring(GetCommandLine()); |
| 60 |
| 61 size_t first_space = cmdline.find(' ')+1; |
| 62 if (first_space == -1) { |
| 63 // I'm not sure why this would ever happen, but just in case... |
| 64 cmdline = SHIM_EXE; |
| 65 } else { |
| 66 cmdline = SHIM_EXE L" " + cmdline.substr(first_space); |
| 67 } |
| 68 |
| 69 if (!CreateProcess(NULL, |
| 70 const_cast<TCHAR*>(cmdline.c_str()), |
| 71 NULL, |
| 72 NULL, |
| 73 TRUE, |
| 74 0, |
| 75 NULL, |
| 76 NULL, |
| 77 &startup_info, &process_info)) { |
| 78 tstring error = ErrorMessageToString(GetLastError()); |
| 79 Fatal(error.c_str()); |
| 80 } |
| 81 CloseHandle(process_info.hThread); |
| 82 WaitForSingleObject(process_info.hProcess, INFINITE); |
| 83 GetExitCodeProcess(process_info.hProcess, &exit_code); |
| 84 CloseHandle(process_info.hProcess); |
| 85 return exit_code; |
| 86 } |
| 87 |
| 88 int CpuConcurrencyMetric() { |
| 89 int max_concurrent = 0; |
| 90 std::vector<char> buffer(1); |
| 91 BOOL ok = false; |
| 92 DWORD last_error = 0; |
| 93 do { |
| 94 DWORD bufsize = buffer.size(); |
| 95 ok = GetLogicalProcessorInformation( |
| 96 reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION>(&buffer[0]), |
| 97 &bufsize); |
| 98 last_error = GetLastError(); |
| 99 if (!ok && last_error == ERROR_INSUFFICIENT_BUFFER && |
| 100 bufsize > buffer.size()) |
| 101 { |
| 102 buffer.resize(bufsize); |
| 103 } |
| 104 } while(!ok && last_error == ERROR_INSUFFICIENT_BUFFER); |
| 105 |
| 106 if (!ok) { |
| 107 if (g_is_debug) |
| 108 wprintf(L"Error while getting number of cores. Try setting the " |
| 109 L" environment variable '%s' to (num_cores-1): %d\n", |
| 110 g_envvar_name, last_error); |
| 111 exit(RunExe()); |
| 112 } |
| 113 |
| 114 PSYSTEM_LOGICAL_PROCESSOR_INFORMATION pproc_info = |
| 115 reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION>(&buffer[0]); |
| 116 int num_entries = buffer.size() / |
| 117 sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); |
| 118 |
| 119 for(int i = 0; i < num_entries; ++i) { |
| 120 SYSTEM_LOGICAL_PROCESSOR_INFORMATION &info = pproc_info[i]; |
| 121 if(info.Relationship == RelationProcessorCore) { |
| 122 max_concurrent += 1; |
| 123 } |
| 124 } |
| 125 |
| 126 // Leave one core for other tasks |
| 127 return max_concurrent-1; |
| 128 } |
| 129 |
| 130 |
| 131 // TODO(defaults): Create a better heuristic than # of CPUs. It seems likely |
| 132 // that the right value will, in fact, be based on the memory capacity of the |
| 133 // machine, not on the number of CPUs. |
| 134 enum ConcurrencyMetricEnum { |
| 135 CONCURRENCY_METRIC_ONE, |
| 136 CONCURRENCY_METRIC_CPU, |
| 137 CONCURRENCY_METRIC_DEFAULT = CONCURRENCY_METRIC_CPU |
| 138 }; |
| 139 |
| 140 int GetMaxConcurrency( |
| 141 ConcurrencyMetricEnum metric = CONCURRENCY_METRIC_DEFAULT) |
| 142 { |
| 143 static int max_concurrent = -1; |
| 144 |
| 145 if (max_concurrent == -1) { |
| 146 const LPTSTR max_concurrent_str = _wgetenv(g_envvar_name); |
| 147 max_concurrent = _wtoi(max_concurrent_str ? max_concurrent_str : L"0"); |
| 148 |
| 149 if (max_concurrent == 0) { |
| 150 switch(metric) { |
| 151 case CONCURRENCY_METRIC_ONE: |
| 152 max_concurrent = 1; |
| 153 break; |
| 154 case CONCURRENCY_METRIC_CPU: |
| 155 max_concurrent = CpuConcurrencyMetric(); |
| 156 break; |
| 157 } |
| 158 } |
| 159 |
| 160 max_concurrent = std::min(std::max(max_concurrent, 1), |
| 161 PIPE_UNLIMITED_INSTANCES); |
| 162 } |
| 163 |
| 164 return max_concurrent; |
| 165 } |
| 166 |
| 167 |
| 168 // Input command line is assumed to be of the form: |
| 169 // |
| 170 // SHIM_EXE ... |
| 171 // |
| 172 // Specifically, wait for a semaphore (whose concurrency is specified by |
| 173 // LIMITER_MAXCONCURRENT), and then pass through everything once we have |
| 174 // acquired the semaphore. |
| 175 int wmain(int, wchar_t**) { |
| 176 ULONGLONG start_time = 0, end_time = 0; |
| 177 |
| 178 if (g_is_debug) |
| 179 start_time = GetTickCount64(); |
| 180 |
| 181 // This event lets us do better than strict polling, but we don't rely on it |
| 182 // (in case a process crashes before signalling the event). |
| 183 HANDLE hEvent = CreateEvent( |
| 184 NULL, // Default security attributes |
| 185 FALSE, // Manual reset |
| 186 FALSE, // Initial state |
| 187 g_event_name); |
| 188 |
| 189 // We're using a named pipe instead of a semaphore so the Kernel can clean up |
| 190 // after us if we crash while holding onto the pipe (A real semaphore will |
| 191 // not release on process termination). |
| 192 HANDLE hPipe = INVALID_HANDLE_VALUE; |
| 193 for(;;) { |
| 194 hPipe = CreateNamedPipe( |
| 195 g_pipe_name, |
| 196 PIPE_ACCESS_DUPLEX, |
| 197 PIPE_TYPE_BYTE, |
| 198 GetMaxConcurrency(), |
| 199 1, // nOutBufferSize |
| 200 1, // nInBufferSize |
| 201 0, // nDefaultTimeOut |
| 202 NULL // Default security attributes (noinherit) |
| 203 ); |
| 204 if (hPipe != INVALID_HANDLE_VALUE) |
| 205 break; |
| 206 |
| 207 DWORD error = GetLastError(); |
| 208 if (error == ERROR_PIPE_BUSY) { |
| 209 if (hEvent) { |
| 210 WaitForSingleObject(hEvent, g_wait_time); |
| 211 } else { |
| 212 Sleep(g_wait_time); |
| 213 } |
| 214 } else { |
| 215 if (g_is_debug) { |
| 216 std::wostringstream strm; |
| 217 strm << L"Got error " << error << L": " |
| 218 << ErrorMessageToString(error) << L"\n"; |
| 219 wprintf(strm.str().c_str()); |
| 220 } |
| 221 return RunExe(); |
| 222 } |
| 223 } |
| 224 |
| 225 if (g_is_debug) { |
| 226 end_time = GetTickCount64(); |
| 227 wprintf(L" took %.2fs to acquire semaphore.\n", |
| 228 (end_time - start_time) / 1000.0); |
| 229 } |
| 230 |
| 231 DWORD ret = RunExe(); |
| 232 |
| 233 CloseHandle(hPipe); |
| 234 if (hEvent != NULL) { |
| 235 SetEvent(hEvent); |
| 236 } |
| 237 |
| 238 return ret; |
| 239 } |
OLD | NEW |