blob: 481ffe13952e3021fb3c3db1e1a42697968c83b9 [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
17import asyncore
18import cPickle
19import socket
20
21
22class UnpicklingForwardingReaderChannel(asyncore.dispatcher):
23 """Provides an unpickling, forwarding asyncore dispatch channel reader.
24
25 Inferior dotest.py processes with side-channel-based test results will
26 send test result event data in a pickled format, one event at a time.
27 This class supports reconstructing the pickled data and forwarding it
28 on to its final destination.
29
30 The channel data is written in the form:
31 {num_payload_bytes}#{payload_bytes}
32
33 The bulk of this class is devoted to reading and parsing out
34 the payload bytes.
35 """
36 def __init__(self, file_object, async_map, forwarding_func):
37 asyncore.dispatcher.__init__(self, sock=file_object, map=async_map)
38
39 self.header_contents = ''
40 self.packet_bytes_remaining = 0
41 self.reading_header = True
42 self.ibuffer = ''
43 self.forwarding_func = forwarding_func
44 if forwarding_func is None:
45 # This whole class is useless if we do nothing with the
46 # unpickled results.
47 raise Exception("forwarding function must be set")
48
49 def deserialize_payload(self):
50 """Unpickles the collected input buffer bytes and forwards."""
51 if len(self.ibuffer) > 0:
52 self.forwarding_func(cPickle.loads(self.ibuffer))
53 self.ibuffer = ''
54
55 def consume_header_bytes(self, data):
56 """Consumes header bytes from the front of data.
57 @param data the incoming data stream bytes
58 @return any data leftover after consuming header bytes.
59 """
60 # We're done if there is no content.
61 if not data or (len(data) == 0):
62 return None
63
64 for index in range(len(data)):
65 byte = data[index]
66 if byte != '#':
67 # Header byte.
68 self.header_contents += byte
69 else:
70 # End of header.
71 self.packet_bytes_remaining = int(self.header_contents)
72 self.header_contents = ''
73 self.reading_header = False
74 return data[(index+1):]
75
76 # If we made it here, we've exhausted the data and
77 # we're still parsing header content.
78 return None
79
80 def consume_payload_bytes(self, data):
81 """Consumes payload bytes from the front of data.
82 @param data the incoming data stream bytes
83 @return any data leftover after consuming remaining payload bytes.
84 """
85 if not data or (len(data) == 0):
86 # We're done and there's nothing to do.
87 return None
88
89 data_len = len(data)
90 if data_len <= self.packet_bytes_remaining:
91 # We're consuming all the data provided.
92 self.ibuffer += data
93 self.packet_bytes_remaining -= data_len
94
95 # If we're no longer waiting for payload bytes,
96 # we flip back to parsing header bytes and we
97 # unpickle the payload contents.
98 if self.packet_bytes_remaining < 1:
99 self.reading_header = True
100 self.deserialize_payload()
101
102 # We're done, no more data left.
103 return None
104 else:
105 # We're only consuming a portion of the data since
106 # the data contains more than the payload amount.
107 self.ibuffer += data[:self.packet_bytes_remaining]
108 data = data[self.packet_bytes_remaining:]
109
110 # We now move on to reading the header.
111 self.reading_header = True
112 self.packet_bytes_remaining = 0
113
114 # And we can deserialize the payload.
115 self.deserialize_payload()
116
117 # Return the remaining data.
118 return data
119
120 def handle_read(self):
121 data = self.recv(8192)
122 # print 'driver socket READ: %d bytes' % len(data)
123
124 while data and (len(data) > 0):
125 # If we're reading the header, gather header bytes.
126 if self.reading_header:
127 data = self.consume_header_bytes(data)
128 else:
129 data = self.consume_payload_bytes(data)
130
131 def handle_close(self):
132 # print "socket reader: closing port"
133 self.close()
134
135
136class UnpicklingForwardingListenerChannel(asyncore.dispatcher):
137 """Provides a socket listener asyncore channel for unpickling/forwarding.
138
139 This channel will listen on a socket port (use 0 for host-selected). Any
140 client that connects will have an UnpicklingForwardingReaderChannel handle
141 communication over the connection.
142
143 The dotest parallel test runners, when collecting test results, open the
144 test results side channel over a socket. This channel handles connections
145 from inferiors back to the test runner. Each worker fires up a listener
146 for each inferior invocation. This simplifies the asyncore.loop() usage,
147 one of the reasons for implementing with asyncore. This listener shuts
148 down once a single connection is made to it.
149 """
Todd Fiala871b2e52015-09-22 22:47:34 +0000150 def __init__(self, async_map, host, port, backlog_count, forwarding_func):
Todd Fiala68615ce2015-09-15 21:38:04 +0000151 asyncore.dispatcher.__init__(self, map=async_map)
152 self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
153 self.set_reuse_addr()
154 self.bind((host, port))
155 self.address = self.socket.getsockname()
Todd Fiala871b2e52015-09-22 22:47:34 +0000156 self.listen(backlog_count)
Todd Fiala68615ce2015-09-15 21:38:04 +0000157 self.handler = None
158 self.async_map = async_map
159 self.forwarding_func = forwarding_func
160 if forwarding_func is None:
161 # This whole class is useless if we do nothing with the
162 # unpickled results.
163 raise Exception("forwarding function must be set")
164
165 def handle_accept(self):
166 (sock, addr) = self.socket.accept()
167 if sock and addr:
168 # print 'Incoming connection from %s' % repr(addr)
169 self.handler = UnpicklingForwardingReaderChannel(
170 sock, self.async_map, self.forwarding_func)
171
172 def handle_close(self):
173 self.close()