Add ICU message format support
[chromium-blink-merge.git] / tools / win / link_limiter / limiter.cc
blobcbb1f2a8593561745dc43ceeaa25d06126fad460
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.
5 #include <stdio.h>
6 #include <stdlib.h>
8 #define NOMINMAX
9 #include <windows.h>
11 #include <algorithm>
12 #include <iterator>
13 #include <sstream>
14 #include <string>
15 #include <vector>
17 typedef std::basic_string<TCHAR> tstring;
19 namespace {
20 const bool g_is_debug = (_wgetenv(L"LIMITER_DEBUG") != NULL);
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);
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);
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;
60 static DWORD RunExe(const tstring& exe_name) {
61 STARTUPINFO startup_info = { sizeof(STARTUPINFO) };
62 PROCESS_INFORMATION process_info;
63 DWORD exit_code;
65 GetStartupInfo(&startup_info);
66 tstring cmdline = tstring(GetCommandLine());
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);
76 if (!CreateProcess(NULL, // lpApplicationName
77 &cmdline[0],
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;
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;
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);
113 } while (!ok && last_error == ERROR_INSUFFICIENT_BUFFER);
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;
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);
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;
134 // Leave one core for other tasks
135 return max_concurrent - 1;
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
147 static int GetMaxConcurrency(const tstring& base_pipename,
148 ConcurrencyMetricEnum metric) {
149 static int max_concurrent = -1;
151 if (max_concurrent == -1) {
152 tstring envvar_name = base_pipename + L"_MAXCONCURRENCY";
154 const LPTSTR max_concurrent_str = _wgetenv(envvar_name.c_str());
155 max_concurrent = max_concurrent_str ? _wtoi(max_concurrent_str) : 0;
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;
170 max_concurrent = std::min(std::max(max_concurrent, 1),
171 PIPE_UNLIMITED_INSTANCES);
174 return max_concurrent;
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;
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 */);
206 } else {
207 Warn(L"Got error %d while waiting for pipe: %s", error,
208 ErrorMessageToString(error).c_str());
209 return INVALID_HANDLE_VALUE;
213 return pipe;
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;
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());
230 if (g_is_debug)
231 start_time = GetTickCount64();
233 HANDLE pipe =
234 WaitForPipe(pipename, event,
235 GetMaxConcurrency(base_pipename, CONCURRENCY_METRIC_DEFAULT));
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);
243 DWORD ret = RunExe(shimmed_exe);
245 if (pipe != INVALID_HANDLE_VALUE)
246 CloseHandle(pipe);
247 if (event != NULL)
248 SetEvent(event);
250 return ret;
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);
292 // Input command line is assumed to be of the form:
294 // thing.exe__PIPE_NAME.exe ...
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.
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.
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);
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'");
326 tstring shimmed_exe = shimmed_plus_pipename.substr(0, separator);
327 tstring base_pipename = shimmed_plus_pipename.substr(separator + 2);
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.");
333 base_pipename = base_pipename.substr(0, dot);
335 return WaitAndRun(shimmed_exe, base_pipename);