blob: 9ec79f5b64de96d13365fdbaee312a71e5d556d1 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001#
2# BitBake Process based server.
3#
4# Copyright (C) 2010 Bob Foerster <robert@erafx.com>
5#
Brad Bishopc342db32019-05-15 21:57:59 -04006# SPDX-License-Identifier: GPL-2.0-only
Patrick Williamsc124f4f2015-09-15 14:41:29 -05007#
Patrick Williamsc124f4f2015-09-15 14:41:29 -05008
9"""
10 This module implements a multiprocessing.Process based server for bitbake.
11"""
12
13import bb
14import bb.event
Patrick Williamsc124f4f2015-09-15 14:41:29 -050015import logging
16import multiprocessing
Brad Bishopd7bf8c12018-02-25 22:55:05 -050017import threading
18import array
Patrick Williamsc124f4f2015-09-15 14:41:29 -050019import os
Patrick Williamsc124f4f2015-09-15 14:41:29 -050020import sys
21import time
22import select
Brad Bishopd7bf8c12018-02-25 22:55:05 -050023import socket
24import subprocess
25import errno
26import re
27import datetime
28import bb.server.xmlrpcserver
29from bb import daemonize
30from multiprocessing import queues
Patrick Williamsc124f4f2015-09-15 14:41:29 -050031
32logger = logging.getLogger('BitBake')
33
Brad Bishopd7bf8c12018-02-25 22:55:05 -050034class ProcessTimeout(SystemExit):
35 pass
Patrick Williamsc124f4f2015-09-15 14:41:29 -050036
Brad Bishopd7bf8c12018-02-25 22:55:05 -050037class ProcessServer(multiprocessing.Process):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050038 profile_filename = "profile.log"
39 profile_processed_filename = "profile.log.processed"
40
Brad Bishopd7bf8c12018-02-25 22:55:05 -050041 def __init__(self, lock, sock, sockname):
42 multiprocessing.Process.__init__(self)
43 self.command_channel = False
44 self.command_channel_reply = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -050045 self.quit = False
Brad Bishop6e60e8b2018-02-01 10:27:11 -050046 self.heartbeat_seconds = 1 # default, BB_HEARTBEAT_EVENT will be checked once we have a datastore.
47 self.next_heartbeat = time.time()
Patrick Williamsc124f4f2015-09-15 14:41:29 -050048
Brad Bishopd7bf8c12018-02-25 22:55:05 -050049 self.event_handle = None
50 self.haveui = False
Andrew Geisslerb7d28612020-07-24 16:15:54 -050051 self.maxuiwait = 30
Brad Bishopd7bf8c12018-02-25 22:55:05 -050052 self.xmlrpc = False
53
54 self._idlefuns = {}
55
56 self.bitbake_lock = lock
57 self.sock = sock
58 self.sockname = sockname
59
60 def register_idle_function(self, function, data):
61 """Register a function to be called while the server is idle"""
62 assert hasattr(function, '__call__')
63 self._idlefuns[function] = data
Patrick Williamsc124f4f2015-09-15 14:41:29 -050064
65 def run(self):
Brad Bishopd7bf8c12018-02-25 22:55:05 -050066
67 if self.xmlrpcinterface[0]:
68 self.xmlrpc = bb.server.xmlrpcserver.BitBakeXMLRPCServer(self.xmlrpcinterface, self.cooker, self)
69
70 print("Bitbake XMLRPC server address: %s, server port: %s" % (self.xmlrpc.host, self.xmlrpc.port))
Patrick Williamsc124f4f2015-09-15 14:41:29 -050071
Brad Bishop6e60e8b2018-02-01 10:27:11 -050072 heartbeat_event = self.cooker.data.getVar('BB_HEARTBEAT_EVENT')
73 if heartbeat_event:
74 try:
75 self.heartbeat_seconds = float(heartbeat_event)
76 except:
Brad Bishop6e60e8b2018-02-01 10:27:11 -050077 bb.warn('Ignoring invalid BB_HEARTBEAT_EVENT=%s, must be a float specifying seconds.' % heartbeat_event)
Brad Bishopd7bf8c12018-02-25 22:55:05 -050078
79 self.timeout = self.server_timeout or self.cooker.data.getVar('BB_SERVER_TIMEOUT')
80 try:
81 if self.timeout:
82 self.timeout = float(self.timeout)
83 except:
84 bb.warn('Ignoring invalid BB_SERVER_TIMEOUT=%s, must be a float specifying seconds.' % self.timeout)
85
86
87 try:
88 self.bitbake_lock.seek(0)
89 self.bitbake_lock.truncate()
90 if self.xmlrpc:
91 self.bitbake_lock.write("%s %s:%s\n" % (os.getpid(), self.xmlrpc.host, self.xmlrpc.port))
92 else:
93 self.bitbake_lock.write("%s\n" % (os.getpid()))
94 self.bitbake_lock.flush()
95 except Exception as e:
96 print("Error writing to lock file: %s" % str(e))
97 pass
98
99 if self.cooker.configuration.profile:
100 try:
101 import cProfile as profile
102 except:
103 import profile
104 prof = profile.Profile()
105
106 ret = profile.Profile.runcall(prof, self.main)
107
108 prof.dump_stats("profile.log")
109 bb.utils.process_profilelog("profile.log")
110 print("Raw profiling information saved to profile.log and processed statistics to profile.log.processed")
111
112 else:
113 ret = self.main()
114
115 return ret
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500116
117 def main(self):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500118 self.cooker.pre_serve()
119
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500120 bb.utils.set_process_name("Cooker")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500121
122 ready = []
Brad Bishopf058f492019-01-28 23:50:33 -0500123 newconnections = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500124
125 self.controllersock = False
126 fds = [self.sock]
127 if self.xmlrpc:
128 fds.append(self.xmlrpc)
129 print("Entering server connection loop")
130
131 def disconnect_client(self, fds):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500132 print("Disconnecting Client")
Brad Bishopf058f492019-01-28 23:50:33 -0500133 if self.controllersock:
134 fds.remove(self.controllersock)
135 self.controllersock.close()
136 self.controllersock = False
137 if self.haveui:
138 fds.remove(self.command_channel)
139 bb.event.unregister_UIHhandler(self.event_handle, True)
140 self.command_channel_reply.writer.close()
141 self.event_writer.writer.close()
142 self.command_channel.close()
143 self.command_channel = False
144 del self.event_writer
145 self.lastui = time.time()
146 self.cooker.clientComplete()
147 self.haveui = False
148 ready = select.select(fds,[],[],0)[0]
149 if newconnections:
150 print("Starting new client")
151 conn = newconnections.pop(-1)
152 fds.append(conn)
153 self.controllersock = conn
154 elif self.timeout is None and not ready:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500155 print("No timeout, exiting.")
156 self.quit = True
157
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500158 self.lastui = time.time()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500159 while not self.quit:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500160 if self.sock in ready:
Brad Bishopf058f492019-01-28 23:50:33 -0500161 while select.select([self.sock],[],[],0)[0]:
162 controllersock, address = self.sock.accept()
163 if self.controllersock:
164 print("Queuing %s (%s)" % (str(ready), str(newconnections)))
165 newconnections.append(controllersock)
166 else:
167 print("Accepting %s (%s)" % (str(ready), str(newconnections)))
168 self.controllersock = controllersock
169 fds.append(controllersock)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500170 if self.controllersock in ready:
171 try:
Brad Bishopf058f492019-01-28 23:50:33 -0500172 print("Processing Client")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500173 ui_fds = recvfds(self.controllersock, 3)
Brad Bishopf058f492019-01-28 23:50:33 -0500174 print("Connecting Client")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500175
176 # Where to write events to
177 writer = ConnectionWriter(ui_fds[0])
178 self.event_handle = bb.event.register_UIHhandler(writer, True)
179 self.event_writer = writer
180
181 # Where to read commands from
182 reader = ConnectionReader(ui_fds[1])
183 fds.append(reader)
184 self.command_channel = reader
185
186 # Where to send command return values to
187 writer = ConnectionWriter(ui_fds[2])
188 self.command_channel_reply = writer
189
190 self.haveui = True
191
192 except (EOFError, OSError):
193 disconnect_client(self, fds)
194
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500195 if not self.timeout == -1.0 and not self.haveui and self.timeout and \
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500196 (self.lastui + self.timeout) < time.time():
197 print("Server timeout, exiting.")
198 self.quit = True
199
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500200 # If we don't see a UI connection within maxuiwait, its unlikely we're going to see
201 # one. We have had issue with processes hanging indefinitely so timing out UI-less
202 # servers is useful.
203 if not self.haveui and not self.timeout and (self.lastui + self.maxuiwait) < time.time():
204 print("No UI connection within max timeout, exiting to avoid infinite loop.")
205 self.quit = True
206
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500207 if self.command_channel in ready:
208 try:
209 command = self.command_channel.get()
210 except EOFError:
211 # Client connection shutting down
212 ready = []
213 disconnect_client(self, fds)
214 continue
215 if command[0] == "terminateServer":
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500216 self.quit = True
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500217 continue
218 try:
219 print("Running command %s" % command)
220 self.command_channel_reply.send(self.cooker.command.runCommand(command))
221 except Exception as e:
222 logger.exception('Exception in server main event loop running command %s (%s)' % (command, str(e)))
223
224 if self.xmlrpc in ready:
225 self.xmlrpc.handle_requests()
226
227 ready = self.idle_commands(.1, fds)
228
229 print("Exiting")
230 # Remove the socket file so we don't get any more connections to avoid races
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500231 try:
232 os.unlink(self.sockname)
233 except:
234 pass
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500235 self.sock.close()
236
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500237 try:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500238 self.cooker.shutdown(True)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400239 self.cooker.notifier.stop()
240 self.cooker.confignotifier.stop()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500241 except:
242 pass
243
244 self.cooker.post_serve()
245
246 # Finally release the lockfile but warn about other processes holding it open
247 lock = self.bitbake_lock
248 lockfile = lock.name
249 lock.close()
250 lock = None
251
252 while not lock:
253 with bb.utils.timeout(3):
254 lock = bb.utils.lockfile(lockfile, shared=False, retry=False, block=True)
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300255 if lock:
256 # We hold the lock so we can remove the file (hide stale pid data)
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500257 # via unlockfile.
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300258 bb.utils.unlockfile(lock)
259 return
260
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500261 if not lock:
262 # Some systems may not have lsof available
263 procs = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500264 try:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500265 procs = subprocess.check_output(["lsof", '-w', lockfile], stderr=subprocess.STDOUT)
266 except OSError as e:
267 if e.errno != errno.ENOENT:
268 raise
269 if procs is None:
270 # Fall back to fuser if lsof is unavailable
271 try:
272 procs = subprocess.check_output(["fuser", '-v', lockfile], stderr=subprocess.STDOUT)
273 except OSError as e:
274 if e.errno != errno.ENOENT:
275 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500276
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500277 msg = "Delaying shutdown due to active processes which appear to be holding bitbake.lock"
278 if procs:
279 msg += ":\n%s" % str(procs)
280 print(msg)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500281
282 def idle_commands(self, delay, fds=None):
283 nextsleep = delay
284 if not fds:
285 fds = []
286
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600287 for function, data in list(self._idlefuns.items()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500288 try:
289 retval = function(self, data, False)
290 if retval is False:
291 del self._idlefuns[function]
292 nextsleep = None
293 elif retval is True:
294 nextsleep = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600295 elif isinstance(retval, float) and nextsleep:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500296 if (retval < nextsleep):
297 nextsleep = retval
298 elif nextsleep is None:
299 continue
300 else:
301 fds = fds + retval
302 except SystemExit:
303 raise
304 except Exception as exc:
305 if not isinstance(exc, bb.BBHandledException):
306 logger.exception('Running idle function')
307 del self._idlefuns[function]
308 self.quit = True
309
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500310 # Create new heartbeat event?
311 now = time.time()
312 if now >= self.next_heartbeat:
313 # We might have missed heartbeats. Just trigger once in
314 # that case and continue after the usual delay.
315 self.next_heartbeat += self.heartbeat_seconds
316 if self.next_heartbeat <= now:
317 self.next_heartbeat = now + self.heartbeat_seconds
318 heartbeat = bb.event.HeartbeatEvent(now)
319 bb.event.fire(heartbeat, self.cooker.data)
320 if nextsleep and now + nextsleep > self.next_heartbeat:
321 # Shorten timeout so that we we wake up in time for
322 # the heartbeat.
323 nextsleep = self.next_heartbeat - now
324
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500325 if nextsleep is not None:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500326 if self.xmlrpc:
327 nextsleep = self.xmlrpc.get_timeout(nextsleep)
328 try:
329 return select.select(fds,[],[],nextsleep)[0]
330 except InterruptedError:
331 # Ignore EINTR
332 return []
333 else:
334 return select.select(fds,[],[],0)[0]
335
336
337class ServerCommunicator():
338 def __init__(self, connection, recv):
339 self.connection = connection
340 self.recv = recv
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500341
342 def runCommand(self, command):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500343 self.connection.send(command)
344 if not self.recv.poll(30):
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500345 logger.info("No reply from server in 30s")
Andrew Geissler475cb722020-07-10 16:00:51 -0500346 if not self.recv.poll(30):
347 raise ProcessTimeout("Timeout while waiting for a reply from the bitbake server (60s)")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500348 return self.recv.get()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500349
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500350 def updateFeatureSet(self, featureset):
351 _, error = self.runCommand(["setFeatures", featureset])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500352 if error:
353 logger.error("Unable to set the cooker to the correct featureset: %s" % error)
354 raise BaseException(error)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500355
356 def getEventHandle(self):
357 handle, error = self.runCommand(["getUIHandlerNum"])
358 if error:
359 logger.error("Unable to get UI Handler Number: %s" % error)
360 raise BaseException(error)
361
362 return handle
363
364 def terminateServer(self):
365 self.connection.send(['terminateServer'])
366 return
367
368class BitBakeProcessServerConnection(object):
369 def __init__(self, ui_channel, recv, eq, sock):
370 self.connection = ServerCommunicator(ui_channel, recv)
371 self.events = eq
372 # Save sock so it doesn't get gc'd for the life of our connection
373 self.socket_connection = sock
374
375 def terminate(self):
376 self.socket_connection.close()
377 self.connection.connection.close()
378 self.connection.recv.close()
379 return
380
381class BitBakeServer(object):
382 start_log_format = '--- Starting bitbake server pid %s at %s ---'
383 start_log_datetime_format = '%Y-%m-%d %H:%M:%S.%f'
384
385 def __init__(self, lock, sockname, configuration, featureset):
386
387 self.configuration = configuration
388 self.featureset = featureset
389 self.sockname = sockname
390 self.bitbake_lock = lock
391 self.readypipe, self.readypipein = os.pipe()
392
393 # Create server control socket
394 if os.path.exists(sockname):
395 os.unlink(sockname)
396
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800397 # Place the log in the builddirectory alongside the lock file
398 logfile = os.path.join(os.path.dirname(self.bitbake_lock.name), "bitbake-cookerdaemon.log")
399
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500400 self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
401 # AF_UNIX has path length issues so chdir here to workaround
402 cwd = os.getcwd()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500403 try:
404 os.chdir(os.path.dirname(sockname))
405 self.sock.bind(os.path.basename(sockname))
406 finally:
407 os.chdir(cwd)
408 self.sock.listen(1)
409
410 os.set_inheritable(self.sock.fileno(), True)
411 startdatetime = datetime.datetime.now()
412 bb.daemonize.createDaemon(self._startServer, logfile)
413 self.sock.close()
414 self.bitbake_lock.close()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800415 os.close(self.readypipein)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500416
417 ready = ConnectionReader(self.readypipe)
Brad Bishopf058f492019-01-28 23:50:33 -0500418 r = ready.poll(5)
419 if not r:
420 bb.note("Bitbake server didn't start within 5 seconds, waiting for 90")
421 r = ready.poll(90)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500422 if r:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800423 try:
424 r = ready.get()
425 except EOFError:
426 # Trap the child exitting/closing the pipe and error out
427 r = None
Brad Bishopf058f492019-01-28 23:50:33 -0500428 if not r or r[0] != "r":
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500429 ready.close()
Brad Bishopf058f492019-01-28 23:50:33 -0500430 bb.error("Unable to start bitbake server (%s)" % str(r))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500431 if os.path.exists(logfile):
432 logstart_re = re.compile(self.start_log_format % ('([0-9]+)', '([0-9-]+ [0-9:.]+)'))
433 started = False
434 lines = []
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300435 lastlines = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500436 with open(logfile, "r") as f:
437 for line in f:
438 if started:
439 lines.append(line)
440 else:
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300441 lastlines.append(line)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500442 res = logstart_re.match(line.rstrip())
443 if res:
444 ldatetime = datetime.datetime.strptime(res.group(2), self.start_log_datetime_format)
445 if ldatetime >= startdatetime:
446 started = True
447 lines.append(line)
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300448 if len(lastlines) > 60:
449 lastlines = lastlines[-60:]
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500450 if lines:
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300451 if len(lines) > 60:
452 bb.error("Last 60 lines of server log for this session (%s):\n%s" % (logfile, "".join(lines[-60:])))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500453 else:
454 bb.error("Server log for this session (%s):\n%s" % (logfile, "".join(lines)))
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300455 elif lastlines:
456 bb.error("Server didn't start, last 60 loglines (%s):\n%s" % (logfile, "".join(lastlines)))
457 else:
458 bb.error("%s doesn't exist" % logfile)
459
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500460 raise SystemExit(1)
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300461
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500462 ready.close()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500463
464 def _startServer(self):
465 print(self.start_log_format % (os.getpid(), datetime.datetime.now().strftime(self.start_log_datetime_format)))
Brad Bishope2d5b612018-11-23 10:55:50 +1300466 sys.stdout.flush()
467
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500468 server = ProcessServer(self.bitbake_lock, self.sock, self.sockname)
469 self.configuration.setServerRegIdleCallback(server.register_idle_function)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800470 os.close(self.readypipe)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500471 writer = ConnectionWriter(self.readypipein)
Brad Bishop08902b02019-08-20 09:16:51 -0400472 try:
473 self.cooker = bb.cooker.BBCooker(self.configuration, self.featureset)
474 except bb.BBHandledException:
475 return None
Brad Bishopf058f492019-01-28 23:50:33 -0500476 writer.send("r")
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800477 writer.close()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500478 server.cooker = self.cooker
479 server.server_timeout = self.configuration.server_timeout
480 server.xmlrpcinterface = self.configuration.xmlrpcinterface
481 print("Started bitbake server pid %d" % os.getpid())
Brad Bishope2d5b612018-11-23 10:55:50 +1300482 sys.stdout.flush()
483
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500484 server.start()
485
486def connectProcessServer(sockname, featureset):
487 # Connect to socket
488 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
489 # AF_UNIX has path length issues so chdir here to workaround
490 cwd = os.getcwd()
491
Brad Bishopf058f492019-01-28 23:50:33 -0500492 readfd = writefd = readfd1 = writefd1 = readfd2 = writefd2 = None
493 eq = command_chan_recv = command_chan = None
494
495 sock.settimeout(10)
496
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500497 try:
Brad Bishope2d5b612018-11-23 10:55:50 +1300498 try:
499 os.chdir(os.path.dirname(sockname))
Brad Bishopf058f492019-01-28 23:50:33 -0500500 finished = False
501 while not finished:
502 try:
503 sock.connect(os.path.basename(sockname))
504 finished = True
505 except IOError as e:
506 if e.errno == errno.EWOULDBLOCK:
507 pass
Richard Purdie3da11142019-02-05 21:34:37 +0000508 raise
Brad Bishope2d5b612018-11-23 10:55:50 +1300509 finally:
510 os.chdir(cwd)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500511
512 # Send an fd for the remote to write events to
513 readfd, writefd = os.pipe()
514 eq = BBUIEventQueue(readfd)
515 # Send an fd for the remote to recieve commands from
516 readfd1, writefd1 = os.pipe()
517 command_chan = ConnectionWriter(writefd1)
518 # Send an fd for the remote to write commands results to
519 readfd2, writefd2 = os.pipe()
520 command_chan_recv = ConnectionReader(readfd2)
521
522 sendfds(sock, [writefd, readfd1, writefd2])
523
524 server_connection = BitBakeProcessServerConnection(command_chan, command_chan_recv, eq, sock)
525
526 # Close the ends of the pipes we won't use
527 for i in [writefd, readfd1, writefd2]:
528 os.close(i)
529
530 server_connection.connection.updateFeatureSet(featureset)
531
532 except (Exception, SystemExit) as e:
533 if command_chan_recv:
534 command_chan_recv.close()
535 if command_chan:
536 command_chan.close()
537 for i in [writefd, readfd1, writefd2]:
538 try:
Brad Bishope2d5b612018-11-23 10:55:50 +1300539 if i:
540 os.close(i)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500541 except OSError:
542 pass
543 sock.close()
544 raise
545
546 return server_connection
547
548def sendfds(sock, fds):
549 '''Send an array of fds over an AF_UNIX socket.'''
550 fds = array.array('i', fds)
551 msg = bytes([len(fds) % 256])
552 sock.sendmsg([msg], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)])
553
554def recvfds(sock, size):
555 '''Receive an array of fds over an AF_UNIX socket.'''
556 a = array.array('i')
557 bytes_size = a.itemsize * size
558 msg, ancdata, flags, addr = sock.recvmsg(1, socket.CMSG_LEN(bytes_size))
559 if not msg and not ancdata:
560 raise EOFError
561 try:
562 if len(ancdata) != 1:
563 raise RuntimeError('received %d items of ancdata' %
564 len(ancdata))
565 cmsg_level, cmsg_type, cmsg_data = ancdata[0]
566 if (cmsg_level == socket.SOL_SOCKET and
567 cmsg_type == socket.SCM_RIGHTS):
568 if len(cmsg_data) % a.itemsize != 0:
569 raise ValueError
570 a.frombytes(cmsg_data)
571 assert len(a) % 256 == msg[0]
572 return list(a)
573 except (ValueError, IndexError):
574 pass
575 raise RuntimeError('Invalid data received')
576
577class BBUIEventQueue:
578 def __init__(self, readfd):
579
580 self.eventQueue = []
581 self.eventQueueLock = threading.Lock()
582 self.eventQueueNotify = threading.Event()
583
584 self.reader = ConnectionReader(readfd)
585
586 self.t = threading.Thread()
587 self.t.setDaemon(True)
588 self.t.run = self.startCallbackHandler
589 self.t.start()
590
591 def getEvent(self):
592 self.eventQueueLock.acquire()
593
594 if len(self.eventQueue) == 0:
595 self.eventQueueLock.release()
596 return None
597
598 item = self.eventQueue.pop(0)
599
600 if len(self.eventQueue) == 0:
601 self.eventQueueNotify.clear()
602
603 self.eventQueueLock.release()
604 return item
605
606 def waitEvent(self, delay):
607 self.eventQueueNotify.wait(delay)
608 return self.getEvent()
609
610 def queue_event(self, event):
611 self.eventQueueLock.acquire()
612 self.eventQueue.append(event)
613 self.eventQueueNotify.set()
614 self.eventQueueLock.release()
615
616 def send_event(self, event):
617 self.queue_event(pickle.loads(event))
618
619 def startCallbackHandler(self):
620 bb.utils.set_process_name("UIEventQueue")
621 while True:
622 try:
623 self.reader.wait()
624 event = self.reader.get()
625 self.queue_event(event)
626 except EOFError:
627 # Easiest way to exit is to close the file descriptor to cause an exit
628 break
629 self.reader.close()
630
631class ConnectionReader(object):
632
633 def __init__(self, fd):
634 self.reader = multiprocessing.connection.Connection(fd, writable=False)
635 self.rlock = multiprocessing.Lock()
636
637 def wait(self, timeout=None):
638 return multiprocessing.connection.wait([self.reader], timeout)
639
640 def poll(self, timeout=None):
641 return self.reader.poll(timeout)
642
643 def get(self):
644 with self.rlock:
645 res = self.reader.recv_bytes()
646 return multiprocessing.reduction.ForkingPickler.loads(res)
647
648 def fileno(self):
649 return self.reader.fileno()
650
651 def close(self):
652 return self.reader.close()
653
654
655class ConnectionWriter(object):
656
657 def __init__(self, fd):
658 self.writer = multiprocessing.connection.Connection(fd, readable=False)
659 self.wlock = multiprocessing.Lock()
660 # Why bb.event needs this I have no idea
661 self.event = self
662
663 def send(self, obj):
664 obj = multiprocessing.reduction.ForkingPickler.dumps(obj)
665 with self.wlock:
666 self.writer.send_bytes(obj)
667
668 def fileno(self):
669 return self.writer.fileno()
670
671 def close(self):
672 return self.writer.close()