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):