blob: a09cbff49835ea51b6c27d3a9ae2cc9b8f6b0357 [file] [log] [blame]
Todd Fiala68615ce2015-09-15 21:38:04 +00001"""
2 The LLVM Compiler Infrastructure
3
4This file is distributed under the University of Illinois Open Source
5License. See LICENSE.TXT for details.
6
7Sync lldb and related source from a local machine to a remote machine.
8
9This facilitates working on the lldb sourcecode on multiple machines
10and multiple OS types, verifying changes across all.
11
12
13This module provides asyncore channels used within the LLDB test
14framework.
15"""
16
Zachary Turner35d017f2015-10-23 17:04:29 +000017from __future__ import print_function
Zachary Turnerc1b7cd72015-11-05 19:22:28 +000018from __future__ import absolute_import
Zachary Turner35d017f2015-10-23 17:04:29 +000019
Zachary Turner19474e12015-11-03 19:20:39 +000020
Zachary Turnerc1b7cd72015-11-05 19:22:28 +000021# System modules
Todd Fiala68615ce2015-09-15 21:38:04 +000022import asyncore
Todd Fiala68615ce2015-09-15 21:38:04 +000023import socket
24
Zachary Turnerc1b7cd72015-11-05 19:22:28 +000025# Third-party modules
Zachary Turner814236d2015-10-21 17:48:52 +000026from six.moves import cPickle
Todd Fiala68615ce2015-09-15 21:38:04 +000027
Zachary Turnerc1b7cd72015-11-05 19:22:28 +000028# LLDB modules
29
Todd Fiala68615ce2015-09-15 21:38:04 +000030class UnpicklingForwardingReaderChannel(asyncore.dispatcher):
31 """Provides an unpickling, forwarding asyncore dispatch channel reader.
32
33 Inferior dotest.py processes with side-channel-based test results will
34 send test result event data in a pickled format, one event at a time.
35 This class supports reconstructing the pickled data and forwarding it
36 on to its final destination.
37
38 The channel data is written in the form:
39 {num_payload_bytes}#{payload_bytes}
40
41 The bulk of this class is devoted to reading and parsing out
42 the payload bytes.
43 """
44 def __init__(self, file_object, async_map, forwarding_func):
45 asyncore.dispatcher.__init__(self, sock=file_object, map=async_map)
46
Zachary Turnerfe868aca2015-12-02 23:07:33 +000047 self.header_contents = b""
Todd Fiala68615ce2015-09-15 21:38:04 +000048 self.packet_bytes_remaining = 0
49 self.reading_header = True
Todd Fialac8431112015-12-02 21:45:15 +000050 self.ibuffer = b''
Todd Fiala68615ce2015-09-15 21:38:04 +000051 self.forwarding_func = forwarding_func
52 if forwarding_func is None:
53 # This whole class is useless if we do nothing with the
54 # unpickled results.
55 raise Exception("forwarding function must be set")
56
57 def deserialize_payload(self):
58 """Unpickles the collected input buffer bytes and forwards."""
59 if len(self.ibuffer) > 0:
60 self.forwarding_func(cPickle.loads(self.ibuffer))
Todd Fialac8431112015-12-02 21:45:15 +000061 self.ibuffer = b''
Todd Fiala68615ce2015-09-15 21:38:04 +000062
63 def consume_header_bytes(self, data):
64 """Consumes header bytes from the front of data.
65 @param data the incoming data stream bytes
66 @return any data leftover after consuming header bytes.
67 """
68 # We're done if there is no content.
69 if not data or (len(data) == 0):
70 return None
71
Zachary Turnerfe868aca2015-12-02 23:07:33 +000072 full_header_len = 4
73
74 assert(len(self.header_contents) < full_header_len)
75
76 bytes_avail = len(data)
77 bytes_needed = full_header_len - len(self.header_contents)
78 header_bytes_avail = min(bytes_needed, bytes_avail)
79 self.header_contents += data[:header_bytes_avail]
80 if len(self.header_contents) == full_header_len:
81 import struct
82 # End of header.
83 self.packet_bytes_remaining = struct.unpack("!I", self.header_contents)[0]
84 self.header_contents = b""
85 self.reading_header = False
86 return data[header_bytes_avail:]
Todd Fiala68615ce2015-09-15 21:38:04 +000087
88 # If we made it here, we've exhausted the data and
89 # we're still parsing header content.
90 return None
91
92 def consume_payload_bytes(self, data):
93 """Consumes payload bytes from the front of data.
94 @param data the incoming data stream bytes
95 @return any data leftover after consuming remaining payload bytes.
96 """
97 if not data or (len(data) == 0):
98 # We're done and there's nothing to do.
99 return None
100
101 data_len = len(data)
102 if data_len <= self.packet_bytes_remaining:
103 # We're consuming all the data provided.
104 self.ibuffer += data
105 self.packet_bytes_remaining -= data_len
106
107 # If we're no longer waiting for payload bytes,
108 # we flip back to parsing header bytes and we
109 # unpickle the payload contents.
110 if self.packet_bytes_remaining < 1:
111 self.reading_header = True
112 self.deserialize_payload()
113
114 # We're done, no more data left.
115 return None
116 else:
117 # We're only consuming a portion of the data since
118 # the data contains more than the payload amount.
119 self.ibuffer += data[:self.packet_bytes_remaining]
120 data = data[self.packet_bytes_remaining:]
121
122 # We now move on to reading the header.
123 self.reading_header = True
124 self.packet_bytes_remaining = 0
125
126 # And we can deserialize the payload.
127 self.deserialize_payload()
128
129 # Return the remaining data.
130 return data
131
132 def handle_read(self):
133 data = self.recv(8192)
Zachary Turner35d017f2015-10-23 17:04:29 +0000134 # print('driver socket READ: %d bytes' % len(data))
Todd Fiala68615ce2015-09-15 21:38:04 +0000135
136 while data and (len(data) > 0):
137 # If we're reading the header, gather header bytes.
138 if self.reading_header:
139 data = self.consume_header_bytes(data)
140 else:
141 data = self.consume_payload_bytes(data)
142
143 def handle_close(self):
Zachary Turner35d017f2015-10-23 17:04:29 +0000144 # print("socket reader: closing port")
Todd Fiala68615ce2015-09-15 21:38:04 +0000145 self.close()
146
147
148class UnpicklingForwardingListenerChannel(asyncore.dispatcher):
149 """Provides a socket listener asyncore channel for unpickling/forwarding.
150
151 This channel will listen on a socket port (use 0 for host-selected). Any
152 client that connects will have an UnpicklingForwardingReaderChannel handle
153 communication over the connection.
154
155 The dotest parallel test runners, when collecting test results, open the
156 test results side channel over a socket. This channel handles connections
157 from inferiors back to the test runner. Each worker fires up a listener
158 for each inferior invocation. This simplifies the asyncore.loop() usage,
159 one of the reasons for implementing with asyncore. This listener shuts
160 down once a single connection is made to it.
161 """
Todd Fiala871b2e52015-09-22 22:47:34 +0000162 def __init__(self, async_map, host, port, backlog_count, forwarding_func):
Todd Fiala68615ce2015-09-15 21:38:04 +0000163 asyncore.dispatcher.__init__(self, map=async_map)
164 self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
165 self.set_reuse_addr()
166 self.bind((host, port))
167 self.address = self.socket.getsockname()
Todd Fiala871b2e52015-09-22 22:47:34 +0000168 self.listen(backlog_count)
Todd Fiala68615ce2015-09-15 21:38:04 +0000169 self.handler = None
170 self.async_map = async_map
171 self.forwarding_func = forwarding_func
172 if forwarding_func is None:
173 # This whole class is useless if we do nothing with the
174 # unpickled results.
175 raise Exception("forwarding function must be set")
176
177 def handle_accept(self):
178 (sock, addr) = self.socket.accept()
179 if sock and addr:
Zachary Turner35d017f2015-10-23 17:04:29 +0000180 # print('Incoming connection from %s' % repr(addr))
Todd Fiala68615ce2015-09-15 21:38:04 +0000181 self.handler = UnpicklingForwardingReaderChannel(
182 sock, self.async_map, self.forwarding_func)
183
184 def handle_close(self):
185 self.close()