Add Cygwin support.
1 # vim: set ts=8 sts=2 sw=2 tw=99 et:
3 # This file is part of AMBuild.
5 # AMBuild is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # AMBuild is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with AMBuild. If not, see <http://www.gnu.org/licenses/>.
21 from collections import deque
24 from . process import Channel, Error, Special
25 from ambuild2 import util
28 if 'LOG' in os.environ:
30 logging.basicConfig(level=logging.INFO)
32 for index, arg in enumerate(sys.argv):
34 name = sys.argv[index + 1]
36 pipe = sys.argv[index + 1]
38 # Cygwin seems to lose the initial \ in translation or something.
39 if pipe.startswith('\\.\\'):
40 pipe = '\\\\.\\' + pipe
42 proxy = NamedPipeProxy(name, pipe)
43 channel = NamedPipe.from_proxy(proxy)
46 channel.send(Special.Connected)
47 impl.child_main(channel)
49 class NamedPipeProxy(object):
50 def __init__(self, name, path):
58 kChannelsKey = 'channels'
60 class NamedPipe(Channel):
61 def __init__(self, name, handle, path):
62 super(NamedPipe, self).__init__(name)
65 self.read_op = winapi.Overlapped()
68 # False if nothing is posted to the IOCP
69 self.waiting_input = False
71 # False if we're not yet connected to the other side.
72 self.connected = False
74 # Used for receiving data in chunks.
75 self.input_buffer = bytearray(4096)
76 self.input_obj = (ctypes.c_byte * 4096).from_buffer(self.input_buffer)
77 self.input_overflow = bytearray()
80 if self.waiting_input:
81 winapi.CancelIo(self.handle)
83 winapi.CloseHandle(self.write_op.event())
84 winapi.CloseHandle(self.handle)
86 def send_impl(self, obj, channels=()):
87 if channels and len(channels) and type(obj) is not dict:
88 raise Exception('must have dictionary message to send channels')
91 assert kChannelsKey not in obj
93 # Unlike posix we can cheat here and just send the paths for the pipes.
95 for channel in channels:
96 assert type(channel) is NamedPipeProxy
97 proxies.append(channel)
98 obj[kChannelsKey] = proxies
100 # Sends in AMBuild IPC are synchronous, so we must always wait for IO to
102 if not self.write_op:
103 self.write_op = winapi.Overlapped()
104 self.write_op.hEvent = winapi.CreateEvent(manual=True, initial=False)
105 self.write_op.quiet()
107 # Reset the event just in case...? Though in theory it should be reset
108 # automatically by WriteFile() if it immediately succeeds.
109 winapi.ResetEvent(self.write_op.hEvent)
111 # Prepare the data to send. Pad with 4 bytes since bytearray() needs to be
112 # at least 8 due to a ctypes bug, and we're going to prepend 4 bytes.
113 data = util.pickle.dumps(obj)
114 data_bytes = bytearray(data)
115 while len(data_bytes) < 8 - kPrefixLength:
118 prefix = struct.pack('i', len(data))
119 data = bytearray(prefix) + data
121 buffer = (ctypes.c_byte * len(data)).from_buffer(data)
122 nwritten = ctypes.c_int()
124 result = winapi.fnWriteFile(
126 ctypes.cast(buffer, ctypes.c_void_p),
128 ctypes.byref(nwritten),
129 ctypes.cast(ctypes.pointer(self.write_op), ctypes.c_void_p)
132 if winapi.GetLastError() != winapi.ERROR_IO_PENDING:
133 raise winapi.WinError()
135 if not winapi.WaitForSingleObject(self.write_op.hEvent, winapi.INFINITE):
136 raise Exception('unexpected return value from WaitForSingleObject()')
141 def complete_connection(self, nbytes):
142 if not self.waiting_input:
143 # We could already be connected, if we're on the child or if the parent
144 # has used connect_sync().
145 if not self.connected:
147 if not self.connect_async():
148 # Connection hasn't occurred yet, so just keep waiting.
151 # If we got here, we're definitely connected.
152 if not self.connected:
154 self.connected = True
155 self.waiting_input = False
157 # If we're already waiting for input, but we got 0 bytes, just ignore
158 # this event (it's probably faked from our own code).
159 if self.waiting_input and not nbytes:
162 self.waiting_input = False
166 bytes_read = ctypes.c_int()
167 result = winapi.fnReadFile(
169 ctypes.cast(self.input_obj, ctypes.c_void_p),
170 len(self.input_buffer),
171 ctypes.byref(bytes_read),
172 ctypes.cast(ctypes.pointer(self.read_op), ctypes.c_void_p)
176 if winapi.GetLastError() == winapi.ERROR_IO_PENDING:
177 self.waiting_input = True
179 raise winapi.WinError()
181 # Even if the ReadFile() succeeds, and we have data immediately
182 # available, Windows still posts to the IOCP. This makes very little
183 # sense to me, but the IOCP API is awful in general. We just return
184 # immediately if this is the case, to avoid reading the data twice.
185 self.waiting_input = True
187 # Respond to a GetQueuedCompletionStatus().
188 def complete_incoming_io(self, nbytes):
189 if not self.complete_connection(nbytes):
198 message = self.receive_bytes(nbytes)
200 messages.append(message)
202 message = self.seek_remaining_message()
205 messages.append(message)
207 if not self.waiting_input:
210 # We should always be awaiting input again, otherwise, we'll never receive
211 # more events from the IO Completion Port.
212 assert self.waiting_input
215 # Shorthand for complete_incoming_io(0)
216 def initiate_io(self):
217 if not self.complete_connection(0):
221 # Process newly received bytes.
222 def receive_bytes(self, nbytes):
223 total_length = len(self.input_overflow) + nbytes
225 # Do we have enough for a message prefix?
227 self.input_overflow += self.input_buffer
230 # Get the message prefix.
231 msg_prefix = self.input_overflow[0:kPrefixLength]
232 if len(msg_prefix) < kPrefixLength:
233 start = len(msg_prefix)
234 end = kPrefixLength - start
235 msg_prefix = self.input_buffer[start:end]
237 # See if we have enough bytes to cover the message.
238 msg_size, = struct.unpack('i', bytes(msg_prefix))
239 if total_length - kPrefixLength < msg_size:
240 self.input_overflow += self.input_buffer
243 # This is a horrible amount of copying... maybe consider using a deque
244 # of bytes or something if it's a performance problem.
245 if not len(self.input_overflow):
246 message = util.pickle.loads(
247 bytes(self.input_buffer[kPrefixLength : kPrefixLength + msg_size])
249 self.input_overflow += self.input_buffer[kPrefixLength + msg_size : nbytes]
251 # Steal remaining bytes from the input buffer, if needed.
252 if len(self.input_overflow) < kPrefixLength + msg_size:
253 msg_remaining = kPrefixLength + msg_size - len(self.input_overflow)
254 self.input_overflow += self.input_buffer[:msg_remaining]
258 message = util.pickle.loads(
259 bytes(self.input_overflow[kPrefixLength : kPrefixLength + msg_size])
262 # Adjust the input buffer.
263 del self.input_overflow[0 : kPrefixLength + msg_size]
264 self.input_overflow += self.input_buffer[msg_remaining : nbytes]
268 # Process any remaining messages.
269 def seek_remaining_message(self):
270 if len(self.input_overflow) < kPrefixLength:
273 msg_size, = struct.unpack('i', bytes(self.input_overflow[0:kPrefixLength]))
274 if len(self.input_overflow) < kPrefixLength + msg_size:
277 message = util.pickle.loads(
278 bytes(self.input_overflow[kPrefixLength : kPrefixLength + msg_size])
280 del self.input_overflow[0 : kPrefixLength + msg_size]
284 def connect_async(self):
285 result = winapi.fnConnectNamedPipe(self.handle, ctypes.byref(self.read_op))
286 assert not result # MSDN says this is impossible with async pipes
288 gle = winapi.GetLastError()
289 if gle == winapi.ERROR_PIPE_CONNECTED:
290 assert not self.waiting_input
293 if gle != winapi.ERROR_IO_PENDING:
294 raise winapi.WinError()
298 def connect_sync(self, proc_handle):
299 event = winapi.CreateEvent(manual=True, initial=False)
301 # Suppress completion status posting, since this overlapped is temporary.
302 overlapped = winapi.Overlapped(event)
306 result = winapi.fnConnectNamedPipe(self.handle, ctypes.byref(overlapped))
307 assert not result # MSDN says this is impossible with async pipes
309 gle = winapi.GetLastError()
310 if gle != winapi.ERROR_PIPE_CONNECTED:
311 if gle != winapi.ERROR_IO_PENDING:
312 raise winapi.WinError()
315 # We need to wait for either process death or for pipe connection.
316 result = winapi.WaitForMultipleObjects(
317 handles=[overlapped.hEvent, proc_handle],
322 if result != winapi.WAIT_OBJECT_0:
323 if result == winapi.WAIT_OBJECT_0 + 1:
324 raise Exception('child process died before connecting to pipe')
325 raise Exception('unexpected WaitForMultipleObjects return code: {0}'.format(result))
327 winapi.CloseHandle(event)
329 # Fake this to true so we don't try to connect asynchronously.
330 self.connected = True
334 def from_proxy(cls, proxy):
335 handle = winapi.OpenPipe(proxy.path)
336 pipe = cls(proxy.name, handle, proxy.path)
337 pipe.connected = True
341 def connect(cls, channel, name):
342 pipe = cls.from_proxy(channel)
344 pipe.send(Special.Connected)
349 pipe, path = winapi.CreateNamedPipe()
350 parent = cls(name, pipe, path)
351 child = NamedPipeProxy(name, path)
354 class MessagePump(process.MessagePump):
356 super(MessagePump, self).__init__()
357 self.port = winapi.CreateIoCompletionPort()
361 self.pending = deque()
364 super(LinuxMessagePump, self).close()
365 winapi.CloseHandle(self.port)
367 def shouldProcessEvents(self):
368 if not super(MessagePump, self).shouldProcessEvents():
370 return len(self.channels) or len(self.pending)
372 def createChannel(self, name):
373 return NamedPipe.new(name)
375 def registerChannel(self, channel, listener):
376 assert channel.handle.value not in self.channels
381 winapi.RegisterIoCompletion(self.port, channel.handle, key)
382 self.listeners[key] = channel, listener
383 self.channels[channel.handle.value] = key
386 def addChannel(self, channel, listener):
387 key = self.registerChannel(channel, listener)
389 # On Windows, we must initiate a recv() that would block in order to
390 # receive io completion events. Since that could return actual data,
391 # we enqueue these back into the message loop.
392 channel.initiate_io()
394 def dropChannel(self, channel):
395 key = self.channels[channel.handle.value]
396 del self.channels[channel.handle.value]
397 del self.listeners[key]
399 def processMessage(self, message, channel, listener):
400 if message == Special.Closing:
401 self.handle_channel_error(channel, listener, Error.NormalShutdown)
405 listener.receiveMessage(channel, message)
406 except Exception as exn:
407 traceback.print_exc()
408 self.handle_channel_error(channel, listener, Error.User)
410 def processEvents(self):
412 if not self.poll_io_port():
418 key, message = self.pending.popleft()
419 if key not in self.listeners:
422 channel, listener = self.listeners[key]
423 self.processMessage(message, channel, listener)
425 def poll_io_port(self):
426 result, nbytes, key, overlapped = winapi.GetQueuedCompletionStatus(self.port, winapi.INFINITE)
428 # There is no way to remove a file from a completion port, so just as a
429 # precaution, we check this.
430 if key.value not in self.listeners:
433 address = ctypes.addressof(overlapped)
434 channel, listener = self.listeners[key.value]
436 # We only care about read events, so if we're receiving some kind of white
438 if ctypes.addressof(channel.read_op) != address:
439 raise Exception('unexpected IO completion on {0}'.format(listener.name))
442 self.handle_channel_error(channel, listener, Error.EOF)
447 messages = channel.complete_incoming_io(nbytes=nbytes.value)
448 except WindowsError as exn:
449 if exn.winerror != winapi.ERROR_BROKEN_PIPE:
450 traceback.print_exc()
451 except Exception as exn:
452 traceback.print_exc()
455 self.handle_channel_error(channel, listener, Error.EOF)
458 for message in messages:
459 self.pending.append((key.value, message))
461 # Finished processing this status change.
464 def handle_channel_error(self, channel, listener, error):
465 self.dropChannel(channel)
466 listener.receiveError(channel, error)
468 class ProcessHost(process.ProcessHost):
469 def __init__(self, id, proc, channel):
470 super(ProcessHost, self).__init__(id, proc, channel)
472 class ProcessManager(process.ProcessManager):
473 def __init__(self, pump):
474 super(ProcessManager, self).__init__(pump)
476 def create_process_and_pipe(self, id, listener):
478 parent, child = NamedPipe.new(listener.name)
481 proc = winapi.Process.spawn(child)
483 # Watch for changes on the parent channel. We don't use addChannel(), since
484 # that will try to asynchronously connect. We want synchronous connections.
485 self.pump.registerChannel(parent, listener)
487 # Require synchronous connection or process death.
488 parent.connect_sync(proc.handle)
490 return ProcessHost(id, proc, parent)
492 def close_process(self, host):