blob: 65e1eab527cca9d7ccbe2c79f07d745a9ec943f7 [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
Andrew Geissler635e0e42020-08-21 15:58:33 -050037class ProcessServer():
Patrick Williamsc124f4f2015-09-15 14:41:29 -050038 profile_filename = "profile.log"
39 profile_processed_filename = "profile.log.processed"
40
Andrew Geissler635e0e42020-08-21 15:58:33 -050041 def __init__(self, lock, sock, sockname, server_timeout, xmlrpcinterface):
Brad Bishopd7bf8c12018-02-25 22:55:05 -050042 self.command_channel = False
43 self.command_channel_reply = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -050044 self.quit = False
Brad Bishop6e60e8b2018-02-01 10:27:11 -050045 self.heartbeat_seconds = 1 # default, BB_HEARTBEAT_EVENT will be checked once we have a datastore.
46 self.next_heartbeat = time.time()
Patrick Williamsc124f4f2015-09-15 14:41:29 -050047
Brad Bishopd7bf8c12018-02-25 22:55:05 -050048 self.event_handle = None
Andrew Geissler635e0e42020-08-21 15:58:33 -050049 self.hadanyui = False
Brad Bishopd7bf8c12018-02-25 22:55:05 -050050 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
Andrew Geissler635e0e42020-08-21 15:58:33 -050060 self.server_timeout = server_timeout
61 self.xmlrpcinterface = xmlrpcinterface
62
Brad Bishopd7bf8c12018-02-25 22:55:05 -050063 def register_idle_function(self, function, data):
64 """Register a function to be called while the server is idle"""
65 assert hasattr(function, '__call__')
66 self._idlefuns[function] = data
Patrick Williamsc124f4f2015-09-15 14:41:29 -050067
68 def run(self):
Brad Bishopd7bf8c12018-02-25 22:55:05 -050069
70 if self.xmlrpcinterface[0]:
71 self.xmlrpc = bb.server.xmlrpcserver.BitBakeXMLRPCServer(self.xmlrpcinterface, self.cooker, self)
72
73 print("Bitbake XMLRPC server address: %s, server port: %s" % (self.xmlrpc.host, self.xmlrpc.port))
Patrick Williamsc124f4f2015-09-15 14:41:29 -050074
Brad Bishop6e60e8b2018-02-01 10:27:11 -050075 heartbeat_event = self.cooker.data.getVar('BB_HEARTBEAT_EVENT')
76 if heartbeat_event:
77 try:
78 self.heartbeat_seconds = float(heartbeat_event)
79 except:
Brad Bishop6e60e8b2018-02-01 10:27:11 -050080 bb.warn('Ignoring invalid BB_HEARTBEAT_EVENT=%s, must be a float specifying seconds.' % heartbeat_event)
Brad Bishopd7bf8c12018-02-25 22:55:05 -050081
82 self.timeout = self.server_timeout or self.cooker.data.getVar('BB_SERVER_TIMEOUT')
83 try:
84 if self.timeout:
85 self.timeout = float(self.timeout)
86 except:
87 bb.warn('Ignoring invalid BB_SERVER_TIMEOUT=%s, must be a float specifying seconds.' % self.timeout)
88
89
90 try:
91 self.bitbake_lock.seek(0)
92 self.bitbake_lock.truncate()
93 if self.xmlrpc:
94 self.bitbake_lock.write("%s %s:%s\n" % (os.getpid(), self.xmlrpc.host, self.xmlrpc.port))
95 else:
96 self.bitbake_lock.write("%s\n" % (os.getpid()))
97 self.bitbake_lock.flush()
98 except Exception as e:
99 print("Error writing to lock file: %s" % str(e))
100 pass
101
102 if self.cooker.configuration.profile:
103 try:
104 import cProfile as profile
105 except:
106 import profile
107 prof = profile.Profile()
108
109 ret = profile.Profile.runcall(prof, self.main)
110
111 prof.dump_stats("profile.log")
112 bb.utils.process_profilelog("profile.log")
113 print("Raw profiling information saved to profile.log and processed statistics to profile.log.processed")
114
115 else:
116 ret = self.main()
117
118 return ret
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500119
120 def main(self):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500121 self.cooker.pre_serve()
122
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500123 bb.utils.set_process_name("Cooker")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500124
125 ready = []
Brad Bishopf058f492019-01-28 23:50:33 -0500126 newconnections = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500127
128 self.controllersock = False
129 fds = [self.sock]
130 if self.xmlrpc:
131 fds.append(self.xmlrpc)
132 print("Entering server connection loop")
133
134 def disconnect_client(self, fds):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500135 print("Disconnecting Client")
Brad Bishopf058f492019-01-28 23:50:33 -0500136 if self.controllersock:
137 fds.remove(self.controllersock)
138 self.controllersock.close()
139 self.controllersock = False
140 if self.haveui:
141 fds.remove(self.command_channel)
142 bb.event.unregister_UIHhandler(self.event_handle, True)
143 self.command_channel_reply.writer.close()
144 self.event_writer.writer.close()
145 self.command_channel.close()
146 self.command_channel = False
147 del self.event_writer
148 self.lastui = time.time()
149 self.cooker.clientComplete()
150 self.haveui = False
151 ready = select.select(fds,[],[],0)[0]
152 if newconnections:
153 print("Starting new client")
154 conn = newconnections.pop(-1)
155 fds.append(conn)
156 self.controllersock = conn
157 elif self.timeout is None and not ready:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500158 print("No timeout, exiting.")
159 self.quit = True
160
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500161 self.lastui = time.time()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500162 while not self.quit:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500163 if self.sock in ready:
Brad Bishopf058f492019-01-28 23:50:33 -0500164 while select.select([self.sock],[],[],0)[0]:
165 controllersock, address = self.sock.accept()
166 if self.controllersock:
167 print("Queuing %s (%s)" % (str(ready), str(newconnections)))
168 newconnections.append(controllersock)
169 else:
170 print("Accepting %s (%s)" % (str(ready), str(newconnections)))
171 self.controllersock = controllersock
172 fds.append(controllersock)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500173 if self.controllersock in ready:
174 try:
Brad Bishopf058f492019-01-28 23:50:33 -0500175 print("Processing Client")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500176 ui_fds = recvfds(self.controllersock, 3)
Brad Bishopf058f492019-01-28 23:50:33 -0500177 print("Connecting Client")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500178
179 # Where to write events to
180 writer = ConnectionWriter(ui_fds[0])
181 self.event_handle = bb.event.register_UIHhandler(writer, True)
182 self.event_writer = writer
183
184 # Where to read commands from
185 reader = ConnectionReader(ui_fds[1])
186 fds.append(reader)
187 self.command_channel = reader
188
189 # Where to send command return values to
190 writer = ConnectionWriter(ui_fds[2])
191 self.command_channel_reply = writer
192
193 self.haveui = True
Andrew Geissler635e0e42020-08-21 15:58:33 -0500194 self.hadanyui = True
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500195
196 except (EOFError, OSError):
197 disconnect_client(self, fds)
198
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500199 if not self.timeout == -1.0 and not self.haveui and self.timeout and \
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500200 (self.lastui + self.timeout) < time.time():
201 print("Server timeout, exiting.")
202 self.quit = True
203
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500204 # If we don't see a UI connection within maxuiwait, its unlikely we're going to see
205 # one. We have had issue with processes hanging indefinitely so timing out UI-less
206 # servers is useful.
Andrew Geissler635e0e42020-08-21 15:58:33 -0500207 if not self.hadanyui and not self.xmlrpc and not self.timeout and (self.lastui + self.maxuiwait) < time.time():
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500208 print("No UI connection within max timeout, exiting to avoid infinite loop.")
209 self.quit = True
210
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500211 if self.command_channel in ready:
212 try:
213 command = self.command_channel.get()
214 except EOFError:
215 # Client connection shutting down
216 ready = []
217 disconnect_client(self, fds)
218 continue
219 if command[0] == "terminateServer":
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500220 self.quit = True
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500221 continue
222 try:
223 print("Running command %s" % command)
224 self.command_channel_reply.send(self.cooker.command.runCommand(command))
225 except Exception as e:
226 logger.exception('Exception in server main event loop running command %s (%s)' % (command, str(e)))
227
228 if self.xmlrpc in ready:
229 self.xmlrpc.handle_requests()
230
231 ready = self.idle_commands(.1, fds)
232
233 print("Exiting")
234 # Remove the socket file so we don't get any more connections to avoid races
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500235 try:
236 os.unlink(self.sockname)
237 except:
238 pass
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500239 self.sock.close()
240
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500241 try:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500242 self.cooker.shutdown(True)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400243 self.cooker.notifier.stop()
244 self.cooker.confignotifier.stop()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500245 except:
246 pass
247
248 self.cooker.post_serve()
249
Andrew Geissler635e0e42020-08-21 15:58:33 -0500250 # Flush logs before we release the lock
251 sys.stdout.flush()
252 sys.stderr.flush()
253
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500254 # Finally release the lockfile but warn about other processes holding it open
255 lock = self.bitbake_lock
256 lockfile = lock.name
257 lock.close()
258 lock = None
259
260 while not lock:
261 with bb.utils.timeout(3):
262 lock = bb.utils.lockfile(lockfile, shared=False, retry=False, block=True)
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300263 if lock:
264 # We hold the lock so we can remove the file (hide stale pid data)
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500265 # via unlockfile.
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300266 bb.utils.unlockfile(lock)
267 return
268
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500269 if not lock:
270 # Some systems may not have lsof available
271 procs = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500272 try:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500273 procs = subprocess.check_output(["lsof", '-w', lockfile], stderr=subprocess.STDOUT)
274 except OSError as e:
275 if e.errno != errno.ENOENT:
276 raise
277 if procs is None:
278 # Fall back to fuser if lsof is unavailable
279 try:
280 procs = subprocess.check_output(["fuser", '-v', lockfile], stderr=subprocess.STDOUT)
281 except OSError as e:
282 if e.errno != errno.ENOENT:
283 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500284
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500285 msg = "Delaying shutdown due to active processes which appear to be holding bitbake.lock"
286 if procs:
287 msg += ":\n%s" % str(procs)
288 print(msg)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500289
290 def idle_commands(self, delay, fds=None):
291 nextsleep = delay
292 if not fds:
293 fds = []
294
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600295 for function, data in list(self._idlefuns.items()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500296 try:
297 retval = function(self, data, False)
298 if retval is False:
299 del self._idlefuns[function]
300 nextsleep = None
301 elif retval is True:
302 nextsleep = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600303 elif isinstance(retval, float) and nextsleep:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500304 if (retval < nextsleep):
305 nextsleep = retval
306 elif nextsleep is None:
307 continue
308 else:
309 fds = fds + retval
310 except SystemExit:
311 raise
312 except Exception as exc:
313 if not isinstance(exc, bb.BBHandledException):
314 logger.exception('Running idle function')
315 del self._idlefuns[function]
316 self.quit = True
317
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500318 # Create new heartbeat event?
319 now = time.time()
320 if now >= self.next_heartbeat:
321 # We might have missed heartbeats. Just trigger once in
322 # that case and continue after the usual delay.
323 self.next_heartbeat += self.heartbeat_seconds
324 if self.next_heartbeat <= now:
325 self.next_heartbeat = now + self.heartbeat_seconds
326 heartbeat = bb.event.HeartbeatEvent(now)
327 bb.event.fire(heartbeat, self.cooker.data)
328 if nextsleep and now + nextsleep > self.next_heartbeat:
329 # Shorten timeout so that we we wake up in time for
330 # the heartbeat.
331 nextsleep = self.next_heartbeat - now
332
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500333 if nextsleep is not None:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500334 if self.xmlrpc:
335 nextsleep = self.xmlrpc.get_timeout(nextsleep)
336 try:
337 return select.select(fds,[],[],nextsleep)[0]
338 except InterruptedError:
339 # Ignore EINTR
340 return []
341 else:
342 return select.select(fds,[],[],0)[0]
343
344
345class ServerCommunicator():
346 def __init__(self, connection, recv):
347 self.connection = connection
348 self.recv = recv
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500349
350 def runCommand(self, command):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500351 self.connection.send(command)
352 if not self.recv.poll(30):
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500353 logger.info("No reply from server in 30s")
Andrew Geissler475cb722020-07-10 16:00:51 -0500354 if not self.recv.poll(30):
355 raise ProcessTimeout("Timeout while waiting for a reply from the bitbake server (60s)")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500356 return self.recv.get()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500357
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500358 def updateFeatureSet(self, featureset):
359 _, error = self.runCommand(["setFeatures", featureset])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500360 if error:
361 logger.error("Unable to set the cooker to the correct featureset: %s" % error)
362 raise BaseException(error)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500363
364 def getEventHandle(self):
365 handle, error = self.runCommand(["getUIHandlerNum"])
366 if error:
367 logger.error("Unable to get UI Handler Number: %s" % error)
368 raise BaseException(error)
369
370 return handle
371
372 def terminateServer(self):
373 self.connection.send(['terminateServer'])
374 return
375
376class BitBakeProcessServerConnection(object):
377 def __init__(self, ui_channel, recv, eq, sock):
378 self.connection = ServerCommunicator(ui_channel, recv)
379 self.events = eq
380 # Save sock so it doesn't get gc'd for the life of our connection
381 self.socket_connection = sock
382
383 def terminate(self):
384 self.socket_connection.close()
385 self.connection.connection.close()
386 self.connection.recv.close()
387 return
388
389class BitBakeServer(object):
390 start_log_format = '--- Starting bitbake server pid %s at %s ---'
391 start_log_datetime_format = '%Y-%m-%d %H:%M:%S.%f'
392
393 def __init__(self, lock, sockname, configuration, featureset):
394
395 self.configuration = configuration
396 self.featureset = featureset
397 self.sockname = sockname
398 self.bitbake_lock = lock
399 self.readypipe, self.readypipein = os.pipe()
400
401 # Create server control socket
402 if os.path.exists(sockname):
403 os.unlink(sockname)
404
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800405 # Place the log in the builddirectory alongside the lock file
406 logfile = os.path.join(os.path.dirname(self.bitbake_lock.name), "bitbake-cookerdaemon.log")
407
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500408 self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
409 # AF_UNIX has path length issues so chdir here to workaround
410 cwd = os.getcwd()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500411 try:
412 os.chdir(os.path.dirname(sockname))
413 self.sock.bind(os.path.basename(sockname))
414 finally:
415 os.chdir(cwd)
416 self.sock.listen(1)
417
418 os.set_inheritable(self.sock.fileno(), True)
419 startdatetime = datetime.datetime.now()
420 bb.daemonize.createDaemon(self._startServer, logfile)
421 self.sock.close()
422 self.bitbake_lock.close()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800423 os.close(self.readypipein)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500424
425 ready = ConnectionReader(self.readypipe)
Brad Bishopf058f492019-01-28 23:50:33 -0500426 r = ready.poll(5)
427 if not r:
428 bb.note("Bitbake server didn't start within 5 seconds, waiting for 90")
429 r = ready.poll(90)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500430 if r:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800431 try:
432 r = ready.get()
433 except EOFError:
434 # Trap the child exitting/closing the pipe and error out
435 r = None
Brad Bishopf058f492019-01-28 23:50:33 -0500436 if not r or r[0] != "r":
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500437 ready.close()
Brad Bishopf058f492019-01-28 23:50:33 -0500438 bb.error("Unable to start bitbake server (%s)" % str(r))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500439 if os.path.exists(logfile):
440 logstart_re = re.compile(self.start_log_format % ('([0-9]+)', '([0-9-]+ [0-9:.]+)'))
441 started = False
442 lines = []
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300443 lastlines = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500444 with open(logfile, "r") as f:
445 for line in f:
446 if started:
447 lines.append(line)
448 else:
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300449 lastlines.append(line)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500450 res = logstart_re.match(line.rstrip())
451 if res:
452 ldatetime = datetime.datetime.strptime(res.group(2), self.start_log_datetime_format)
453 if ldatetime >= startdatetime:
454 started = True
455 lines.append(line)
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300456 if len(lastlines) > 60:
457 lastlines = lastlines[-60:]
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500458 if lines:
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300459 if len(lines) > 60:
460 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 -0500461 else:
462 bb.error("Server log for this session (%s):\n%s" % (logfile, "".join(lines)))
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300463 elif lastlines:
464 bb.error("Server didn't start, last 60 loglines (%s):\n%s" % (logfile, "".join(lastlines)))
465 else:
466 bb.error("%s doesn't exist" % logfile)
467
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500468 raise SystemExit(1)
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300469
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500470 ready.close()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500471
472 def _startServer(self):
473 print(self.start_log_format % (os.getpid(), datetime.datetime.now().strftime(self.start_log_datetime_format)))
Brad Bishope2d5b612018-11-23 10:55:50 +1300474 sys.stdout.flush()
475
Brad Bishop08902b02019-08-20 09:16:51 -0400476 try:
Andrew Geissler635e0e42020-08-21 15:58:33 -0500477 server = ProcessServer(self.bitbake_lock, self.sock, self.sockname, self.configuration.server_timeout, self.configuration.xmlrpcinterface)
478 os.close(self.readypipe)
479 writer = ConnectionWriter(self.readypipein)
480 try:
481 self.cooker = bb.cooker.BBCooker(self.configuration, self.featureset, server.register_idle_function)
482 except bb.BBHandledException:
483 return None
484 writer.send("r")
485 writer.close()
486 server.cooker = self.cooker
487 print("Started bitbake server pid %d" % os.getpid())
488 sys.stdout.flush()
Brad Bishope2d5b612018-11-23 10:55:50 +1300489
Andrew Geissler635e0e42020-08-21 15:58:33 -0500490 server.run()
491 finally:
492 # Flush any ,essages/errors to the logfile before exit
493 sys.stdout.flush()
494 sys.stderr.flush()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500495
496def connectProcessServer(sockname, featureset):
497 # Connect to socket
498 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
499 # AF_UNIX has path length issues so chdir here to workaround
500 cwd = os.getcwd()
501
Brad Bishopf058f492019-01-28 23:50:33 -0500502 readfd = writefd = readfd1 = writefd1 = readfd2 = writefd2 = None
503 eq = command_chan_recv = command_chan = None
504
505 sock.settimeout(10)
506
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500507 try:
Brad Bishope2d5b612018-11-23 10:55:50 +1300508 try:
509 os.chdir(os.path.dirname(sockname))
Brad Bishopf058f492019-01-28 23:50:33 -0500510 finished = False
511 while not finished:
512 try:
513 sock.connect(os.path.basename(sockname))
514 finished = True
515 except IOError as e:
516 if e.errno == errno.EWOULDBLOCK:
517 pass
Richard Purdie3da11142019-02-05 21:34:37 +0000518 raise
Brad Bishope2d5b612018-11-23 10:55:50 +1300519 finally:
520 os.chdir(cwd)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500521
522 # Send an fd for the remote to write events to
523 readfd, writefd = os.pipe()
524 eq = BBUIEventQueue(readfd)
525 # Send an fd for the remote to recieve commands from
526 readfd1, writefd1 = os.pipe()
527 command_chan = ConnectionWriter(writefd1)
528 # Send an fd for the remote to write commands results to
529 readfd2, writefd2 = os.pipe()
530 command_chan_recv = ConnectionReader(readfd2)
531
532 sendfds(sock, [writefd, readfd1, writefd2])
533
534 server_connection = BitBakeProcessServerConnection(command_chan, command_chan_recv, eq, sock)
535
536 # Close the ends of the pipes we won't use
537 for i in [writefd, readfd1, writefd2]:
538 os.close(i)
539
540 server_connection.connection.updateFeatureSet(featureset)
541
542 except (Exception, SystemExit) as e:
543 if command_chan_recv:
544 command_chan_recv.close()
545 if command_chan:
546 command_chan.close()
547 for i in [writefd, readfd1, writefd2]:
548 try:
Brad Bishope2d5b612018-11-23 10:55:50 +1300549 if i:
550 os.close(i)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500551 except OSError:
552 pass
553 sock.close()
554 raise
555
556 return server_connection
557
558def sendfds(sock, fds):
559 '''Send an array of fds over an AF_UNIX socket.'''
560 fds = array.array('i', fds)
561 msg = bytes([len(fds) % 256])
562 sock.sendmsg([msg], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)])
563
564def recvfds(sock, size):
565 '''Receive an array of fds over an AF_UNIX socket.'''
566 a = array.array('i')
567 bytes_size = a.itemsize * size
568 msg, ancdata, flags, addr = sock.recvmsg(1, socket.CMSG_LEN(bytes_size))
569 if not msg and not ancdata:
570 raise EOFError
571 try:
572 if len(ancdata) != 1:
573 raise RuntimeError('received %d items of ancdata' %
574 len(ancdata))
575 cmsg_level, cmsg_type, cmsg_data = ancdata[0]
576 if (cmsg_level == socket.SOL_SOCKET and
577 cmsg_type == socket.SCM_RIGHTS):
578 if len(cmsg_data) % a.itemsize != 0:
579 raise ValueError
580 a.frombytes(cmsg_data)
581 assert len(a) % 256 == msg[0]
582 return list(a)
583 except (ValueError, IndexError):
584 pass
585 raise RuntimeError('Invalid data received')
586
587class BBUIEventQueue:
588 def __init__(self, readfd):
589
590 self.eventQueue = []
591 self.eventQueueLock = threading.Lock()
592 self.eventQueueNotify = threading.Event()
593
594 self.reader = ConnectionReader(readfd)
595
596 self.t = threading.Thread()
597 self.t.setDaemon(True)
598 self.t.run = self.startCallbackHandler
599 self.t.start()
600
601 def getEvent(self):
602 self.eventQueueLock.acquire()
603
604 if len(self.eventQueue) == 0:
605 self.eventQueueLock.release()
606 return None
607
608 item = self.eventQueue.pop(0)
609
610 if len(self.eventQueue) == 0:
611 self.eventQueueNotify.clear()
612
613 self.eventQueueLock.release()
614 return item
615
616 def waitEvent(self, delay):
617 self.eventQueueNotify.wait(delay)
618 return self.getEvent()
619
620 def queue_event(self, event):
621 self.eventQueueLock.acquire()
622 self.eventQueue.append(event)
623 self.eventQueueNotify.set()
624 self.eventQueueLock.release()
625
626 def send_event(self, event):
627 self.queue_event(pickle.loads(event))
628
629 def startCallbackHandler(self):
630 bb.utils.set_process_name("UIEventQueue")
631 while True:
632 try:
633 self.reader.wait()
634 event = self.reader.get()
635 self.queue_event(event)
636 except EOFError:
637 # Easiest way to exit is to close the file descriptor to cause an exit
638 break
639 self.reader.close()
640
641class ConnectionReader(object):
642
643 def __init__(self, fd):
644 self.reader = multiprocessing.connection.Connection(fd, writable=False)
645 self.rlock = multiprocessing.Lock()
646
647 def wait(self, timeout=None):
648 return multiprocessing.connection.wait([self.reader], timeout)
649
650 def poll(self, timeout=None):
651 return self.reader.poll(timeout)
652
653 def get(self):
654 with self.rlock:
655 res = self.reader.recv_bytes()
656 return multiprocessing.reduction.ForkingPickler.loads(res)
657
658 def fileno(self):
659 return self.reader.fileno()
660
661 def close(self):
662 return self.reader.close()
663
664
665class ConnectionWriter(object):
666
667 def __init__(self, fd):
668 self.writer = multiprocessing.connection.Connection(fd, readable=False)
669 self.wlock = multiprocessing.Lock()
670 # Why bb.event needs this I have no idea
671 self.event = self
672
673 def send(self, obj):
674 obj = multiprocessing.reduction.ForkingPickler.dumps(obj)
675 with self.wlock:
676 self.writer.send_bytes(obj)
677
678 def fileno(self):
679 return self.writer.fileno()
680
681 def close(self):
682 return self.writer.close()