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.
17 typedef std::basic_string
<TCHAR
> tstring
;
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";
29 vwprintf(new_msg
.c_str(), args
);
33 static void Warn(const wchar_t* msg
, ...) {
36 tstring new_msg
= tstring(L
"limiter warning: ") + msg
+ L
"\n";
39 vwprintf(new_msg
.c_str(), 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
,
49 MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
),
50 reinterpret_cast<LPTSTR
>(&msg_buf
),
54 return L
"unknown error";
60 static DWORD
RunExe(const tstring
& exe_name
) {
61 STARTUPINFO startup_info
= { sizeof(STARTUPINFO
) };
62 PROCESS_INFORMATION process_info
;
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...
73 cmdline
= exe_name
+ cmdline
.substr(first_space
);
76 if (!CreateProcess(NULL
, // lpApplicationName
78 NULL
, // lpProcessAttributes
79 NULL
, // lpThreadAttributes
80 TRUE
, // bInheritHandles
81 0, // dwCreationFlags,
82 NULL
, // lpEnvironment,
83 NULL
, // lpCurrentDirectory,
86 Error(L
"Error in CreateProcess[%s]: %s",
87 cmdline
.c_str(), ErrorMessageToString(GetLastError()).c_str());
90 CloseHandle(process_info
.hThread
);
91 WaitForSingleObject(process_info
.hProcess
, INFINITE
);
92 GetExitCodeProcess(process_info
.hProcess
, &exit_code
);
93 CloseHandle(process_info
.hProcess
);
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);
102 DWORD last_error
= 0;
104 DWORD bufsize
= buffer
.size();
105 ok
= GetLogicalProcessorInformation(
106 reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION
>(&buffer
[0]),
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
);
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());
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
) {
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) {
159 case CONCURRENCY_METRIC_CPU
:
160 max_concurrent
= CpuConcurrencyMetric(envvar_name
);
164 case CONCURRENCY_METRIC_ONE
:
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
,
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
;
185 pipe
= CreateNamedPipe(
192 0, // nDefaultTimeOut
193 NULL
); // Default security attributes (noinherit)
194 if (pipe
!= INVALID_HANDLE_VALUE
)
197 DWORD error
= GetLastError();
198 if (error
== ERROR_PIPE_BUSY
) {
200 WaitForSingleObject(event
, 60 * 1000 /* ms */);
202 // TODO(iannucci): Maybe we should error out here instead of falling
203 // back to a sleep-poll
204 Sleep(5 * 1000 /* ms */);
207 Warn(L
"Got error %d while waiting for pipe: %s", error
,
208 ErrorMessageToString(error
).c_str());
209 return INVALID_HANDLE_VALUE
;
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
231 start_time
= GetTickCount64();
234 WaitForPipe(pipename
, event
,
235 GetMaxConcurrency(base_pipename
, CONCURRENCY_METRIC_DEFAULT
));
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
)
253 void Usage(const tstring
& msg
) {
256 L
"Usage: SHIMED_NAME__SEMAPHORE_NAME\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"
262 L
" SEMAPHORE_NAME - ex. 'SOME_NAME' or 'GROOVY_SEMAPHORE'\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"
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"
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"
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"
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());
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
305 // * We search for this separator from the end of argv[0], so the exe name
306 // could contain a double underscore if necessary.
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
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
);