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