Mercurial > public > mercurial-scm > hg
comparison mercurial/win32.py @ 20425:ca6aa8362f33
win32: spawndetached returns pid of detached process and not of cmd.exe
win32.spawndetached starts the detached process by `cmd.exe` (or COMSPEC). The
pid it returned was the one of cmd.exe and not the one of the detached process.
When this pid is used to kill the process, the detached process is not killed,
but only cmd.exe.
With this patch the pid of the detached process is written to the pid file.
Killing the process works as expected.
The pid is only evaluated on writing the pid file. It is unnecessary to search
the pid when it is not needed. And more important, it probably does not yet
exist right after the cmd.exe process was started. When the pid is written to
the file, waiting for the start of the detached process has already happened.
Use this functionality instead of writing a 2nd wait function.
Many tests on windows will not fail anymore, all those with the first failing
line "abort: child process failed to start". (The processes still hanging
around from previous test runs have to be killed first. They still block a
tcp port.)
A good test for the functionality of this patch is test-treediscovery.t,
because it starts and kills `hg serve -d` several times.
author | Simon Heimberg <simohe@besonet.ch> |
---|---|
date | Sat, 08 Feb 2014 14:35:07 +0100 |
parents | 1b329f8c7b24 |
children | 1a9ebc83a74c |
comparison
equal
deleted
inserted
replaced
20424:1da346bad3d8 | 20425:ca6aa8362f33 |
---|---|
117 ('srWindow', _SMALL_RECT), | 117 ('srWindow', _SMALL_RECT), |
118 ('dwMaximumWindowSize', _COORD)] | 118 ('dwMaximumWindowSize', _COORD)] |
119 | 119 |
120 _STD_ERROR_HANDLE = _DWORD(-12).value | 120 _STD_ERROR_HANDLE = _DWORD(-12).value |
121 | 121 |
122 # CreateToolhelp32Snapshot, Process32First, Process32Next | |
123 _TH32CS_SNAPPROCESS = 0x00000002 | |
124 _MAX_PATH = 260 | |
125 | |
126 class _tagPROCESSENTRY32(ctypes.Structure): | |
127 _fields_ = [('dwsize', _DWORD), | |
128 ('cntUsage', _DWORD), | |
129 ('th32ProcessID', _DWORD), | |
130 ('th32DefaultHeapID', ctypes.c_void_p), | |
131 ('th32ModuleID', _DWORD), | |
132 ('cntThreads', _DWORD), | |
133 ('th32ParentProcessID', _DWORD), | |
134 ('pcPriClassBase', _LONG), | |
135 ('dwFlags', _DWORD), | |
136 ('szExeFile', ctypes.c_char * _MAX_PATH)] | |
137 | |
138 def __init__(self): | |
139 super(_tagPROCESSENTRY32, self).__init__() | |
140 self.dwsize = ctypes.sizeof(self) | |
141 | |
142 | |
122 # types of parameters of C functions used (required by pypy) | 143 # types of parameters of C functions used (required by pypy) |
123 | 144 |
124 _kernel32.CreateFileA.argtypes = [_LPCSTR, _DWORD, _DWORD, ctypes.c_void_p, | 145 _kernel32.CreateFileA.argtypes = [_LPCSTR, _DWORD, _DWORD, ctypes.c_void_p, |
125 _DWORD, _DWORD, _HANDLE] | 146 _DWORD, _DWORD, _HANDLE] |
126 _kernel32.CreateFileA.restype = _HANDLE | 147 _kernel32.CreateFileA.restype = _HANDLE |
183 _user32.ShowWindow.restype = _BOOL | 204 _user32.ShowWindow.restype = _BOOL |
184 | 205 |
185 _WNDENUMPROC = ctypes.WINFUNCTYPE(_BOOL, _HWND, _LPARAM) | 206 _WNDENUMPROC = ctypes.WINFUNCTYPE(_BOOL, _HWND, _LPARAM) |
186 _user32.EnumWindows.argtypes = [_WNDENUMPROC, _LPARAM] | 207 _user32.EnumWindows.argtypes = [_WNDENUMPROC, _LPARAM] |
187 _user32.EnumWindows.restype = _BOOL | 208 _user32.EnumWindows.restype = _BOOL |
209 | |
210 _kernel32.CreateToolhelp32Snapshot.argtypes = [_DWORD, _DWORD] | |
211 _kernel32.CreateToolhelp32Snapshot.restype = _BOOL | |
212 | |
213 _kernel32.Process32First.argtypes = [_HANDLE, ctypes.c_void_p] | |
214 _kernel32.Process32First.restype = _BOOL | |
215 | |
216 _kernel32.Process32Next.argtypes = [_HANDLE, ctypes.c_void_p] | |
217 _kernel32.Process32Next.restype = _BOOL | |
188 | 218 |
189 def _raiseoserror(name): | 219 def _raiseoserror(name): |
190 err = ctypes.WinError() | 220 err = ctypes.WinError() |
191 raise OSError(err.errno, '%s: %s' % (name, err.strerror)) | 221 raise OSError(err.errno, '%s: %s' % (name, err.strerror)) |
192 | 222 |
307 screenbuf, ctypes.byref(csbi)): | 337 screenbuf, ctypes.byref(csbi)): |
308 return width | 338 return width |
309 width = csbi.srWindow.Right - csbi.srWindow.Left | 339 width = csbi.srWindow.Right - csbi.srWindow.Left |
310 return width | 340 return width |
311 | 341 |
342 def _1stchild(pid): | |
343 '''return the 1st found child of the given pid | |
344 | |
345 None is returned when no child is found''' | |
346 pe = _tagPROCESSENTRY32() | |
347 | |
348 # create handle to list all processes | |
349 ph = _kernel32.CreateToolhelp32Snapshot(_TH32CS_SNAPPROCESS, 0) | |
350 if ph == _INVALID_HANDLE_VALUE: | |
351 raise ctypes.WinError | |
352 try: | |
353 r = _kernel32.Process32First(ph, ctypes.byref(pe)) | |
354 # loop over all processes | |
355 while r: | |
356 if pe.th32ParentProcessID == pid: | |
357 # return first child found | |
358 return pe.th32ProcessID | |
359 r = _kernel32.Process32Next(ph, ctypes.byref(pe)) | |
360 finally: | |
361 _kernel32.CloseHandle(ph) | |
362 if _kernel32.GetLastError() != _ERROR_NO_MORE_FILES: | |
363 raise ctypes.WinError | |
364 return None # no child found | |
365 | |
366 class _tochildpid(int): # pid is _DWORD, which always matches in an int | |
367 '''helper for spawndetached, returns the child pid on conversion to string | |
368 | |
369 Does not resolve the child pid immediately because the child may not yet be | |
370 started. | |
371 ''' | |
372 def childpid(self): | |
373 '''returns the child pid of the first found child of the process | |
374 with this pid''' | |
375 return _1stchild(self) | |
376 def __str__(self): | |
377 # run when the pid is written to the file | |
378 ppid = self.childpid() | |
379 if ppid is None: | |
380 # race, child has exited since check | |
381 # fall back to this pid. Its process will also have disappeared, | |
382 # raising the same error type later as when the child pid would | |
383 # be returned. | |
384 return " %d" % self | |
385 return str(ppid) | |
386 | |
312 def spawndetached(args): | 387 def spawndetached(args): |
313 # No standard library function really spawns a fully detached | 388 # No standard library function really spawns a fully detached |
314 # process under win32 because they allocate pipes or other objects | 389 # process under win32 because they allocate pipes or other objects |
315 # to handle standard streams communications. Passing these objects | 390 # to handle standard streams communications. Passing these objects |
316 # to the child process requires handle inheritance to be enabled | 391 # to the child process requires handle inheritance to be enabled |
337 None, args, None, None, False, _CREATE_NO_WINDOW, | 412 None, args, None, None, False, _CREATE_NO_WINDOW, |
338 env, os.getcwd(), ctypes.byref(si), ctypes.byref(pi)) | 413 env, os.getcwd(), ctypes.byref(si), ctypes.byref(pi)) |
339 if not res: | 414 if not res: |
340 raise ctypes.WinError | 415 raise ctypes.WinError |
341 | 416 |
342 return pi.dwProcessId | 417 # _tochildpid because the process is the child of COMSPEC |
418 return _tochildpid(pi.dwProcessId) | |
343 | 419 |
344 def unlink(f): | 420 def unlink(f): |
345 '''try to implement POSIX' unlink semantics on Windows''' | 421 '''try to implement POSIX' unlink semantics on Windows''' |
346 | 422 |
347 if os.path.isdir(f): | 423 if os.path.isdir(f): |