blob: a0955722e336b454c0d2a5aa0e4640bfef7133d1 [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
Andrew Geisslerc9f78652020-09-18 14:11:35 -050028import pickle
Brad Bishopd7bf8c12018-02-25 22:55:05 -050029import bb.server.xmlrpcserver
30from bb import daemonize
31from multiprocessing import queues
Patrick Williamsc124f4f2015-09-15 14:41:29 -050032
33logger = logging.getLogger('BitBake')
34
Brad Bishopd7bf8c12018-02-25 22:55:05 -050035class ProcessTimeout(SystemExit):
36 pass
Patrick Williamsc124f4f2015-09-15 14:41:29 -050037
Andrew Geisslerc9f78652020-09-18 14:11:35 -050038def serverlog(msg):
39 print(str(os.getpid()) + " " + datetime.datetime.now().strftime('%H:%M:%S.%f') + " " + msg)
40 sys.stdout.flush()
41
Andrew Geissler635e0e42020-08-21 15:58:33 -050042class ProcessServer():
Patrick Williamsc124f4f2015-09-15 14:41:29 -050043 profile_filename = "profile.log"
44 profile_processed_filename = "profile.log.processed"
45
Andrew Geisslerc9f78652020-09-18 14:11:35 -050046 def __init__(self, lock, lockname, sock, sockname, server_timeout, xmlrpcinterface):
Brad Bishopd7bf8c12018-02-25 22:55:05 -050047 self.command_channel = False
48 self.command_channel_reply = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -050049 self.quit = False
Brad Bishop6e60e8b2018-02-01 10:27:11 -050050 self.heartbeat_seconds = 1 # default, BB_HEARTBEAT_EVENT will be checked once we have a datastore.
51 self.next_heartbeat = time.time()
Patrick Williamsc124f4f2015-09-15 14:41:29 -050052
Brad Bishopd7bf8c12018-02-25 22:55:05 -050053 self.event_handle = None
Andrew Geissler635e0e42020-08-21 15:58:33 -050054 self.hadanyui = False
Brad Bishopd7bf8c12018-02-25 22:55:05 -050055 self.haveui = False
Andrew Geisslerb7d28612020-07-24 16:15:54 -050056 self.maxuiwait = 30
Brad Bishopd7bf8c12018-02-25 22:55:05 -050057 self.xmlrpc = False
58
59 self._idlefuns = {}
60
61 self.bitbake_lock = lock
Andrew Geisslerc9f78652020-09-18 14:11:35 -050062 self.bitbake_lock_name = lockname
Brad Bishopd7bf8c12018-02-25 22:55:05 -050063 self.sock = sock
64 self.sockname = sockname
65
Andrew Geissler635e0e42020-08-21 15:58:33 -050066 self.server_timeout = server_timeout
Andrew Geisslerc9f78652020-09-18 14:11:35 -050067 self.timeout = self.server_timeout
Andrew Geissler635e0e42020-08-21 15:58:33 -050068 self.xmlrpcinterface = xmlrpcinterface
69
Brad Bishopd7bf8c12018-02-25 22:55:05 -050070 def register_idle_function(self, function, data):
71 """Register a function to be called while the server is idle"""
72 assert hasattr(function, '__call__')
73 self._idlefuns[function] = data
Patrick Williamsc124f4f2015-09-15 14:41:29 -050074
75 def run(self):
Brad Bishopd7bf8c12018-02-25 22:55:05 -050076
77 if self.xmlrpcinterface[0]:
78 self.xmlrpc = bb.server.xmlrpcserver.BitBakeXMLRPCServer(self.xmlrpcinterface, self.cooker, self)
79
Andrew Geisslerc9f78652020-09-18 14:11:35 -050080 serverlog("Bitbake XMLRPC server address: %s, server port: %s" % (self.xmlrpc.host, self.xmlrpc.port))
Brad Bishopd7bf8c12018-02-25 22:55:05 -050081
82 try:
83 self.bitbake_lock.seek(0)
84 self.bitbake_lock.truncate()
85 if self.xmlrpc:
86 self.bitbake_lock.write("%s %s:%s\n" % (os.getpid(), self.xmlrpc.host, self.xmlrpc.port))
87 else:
88 self.bitbake_lock.write("%s\n" % (os.getpid()))
89 self.bitbake_lock.flush()
90 except Exception as e:
Andrew Geisslerc9f78652020-09-18 14:11:35 -050091 serverlog("Error writing to lock file: %s" % str(e))
Brad Bishopd7bf8c12018-02-25 22:55:05 -050092 pass
93
94 if self.cooker.configuration.profile:
95 try:
96 import cProfile as profile
97 except:
98 import profile
99 prof = profile.Profile()
100
101 ret = profile.Profile.runcall(prof, self.main)
102
103 prof.dump_stats("profile.log")
104 bb.utils.process_profilelog("profile.log")
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500105 serverlog("Raw profiling information saved to profile.log and processed statistics to profile.log.processed")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500106
107 else:
108 ret = self.main()
109
110 return ret
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500111
112 def main(self):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500113 self.cooker.pre_serve()
114
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500115 bb.utils.set_process_name("Cooker")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500116
117 ready = []
Brad Bishopf058f492019-01-28 23:50:33 -0500118 newconnections = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500119
120 self.controllersock = False
121 fds = [self.sock]
122 if self.xmlrpc:
123 fds.append(self.xmlrpc)
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500124 seendata = False
125 serverlog("Entering server connection loop")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500126
127 def disconnect_client(self, fds):
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500128 serverlog("Disconnecting Client")
Brad Bishopf058f492019-01-28 23:50:33 -0500129 if self.controllersock:
130 fds.remove(self.controllersock)
131 self.controllersock.close()
132 self.controllersock = False
133 if self.haveui:
134 fds.remove(self.command_channel)
135 bb.event.unregister_UIHhandler(self.event_handle, True)
136 self.command_channel_reply.writer.close()
137 self.event_writer.writer.close()
138 self.command_channel.close()
139 self.command_channel = False
140 del self.event_writer
141 self.lastui = time.time()
142 self.cooker.clientComplete()
143 self.haveui = False
144 ready = select.select(fds,[],[],0)[0]
145 if newconnections:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500146 serverlog("Starting new client")
Brad Bishopf058f492019-01-28 23:50:33 -0500147 conn = newconnections.pop(-1)
148 fds.append(conn)
149 self.controllersock = conn
Andrew Geissler5f350902021-07-23 13:09:54 -0400150 elif not self.timeout and not ready:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500151 serverlog("No timeout, exiting.")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500152 self.quit = True
153
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500154 self.lastui = time.time()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500155 while not self.quit:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500156 if self.sock in ready:
Brad Bishopf058f492019-01-28 23:50:33 -0500157 while select.select([self.sock],[],[],0)[0]:
158 controllersock, address = self.sock.accept()
159 if self.controllersock:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500160 serverlog("Queuing %s (%s)" % (str(ready), str(newconnections)))
Brad Bishopf058f492019-01-28 23:50:33 -0500161 newconnections.append(controllersock)
162 else:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500163 serverlog("Accepting %s (%s)" % (str(ready), str(newconnections)))
Brad Bishopf058f492019-01-28 23:50:33 -0500164 self.controllersock = controllersock
165 fds.append(controllersock)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500166 if self.controllersock in ready:
167 try:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500168 serverlog("Processing Client")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500169 ui_fds = recvfds(self.controllersock, 3)
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500170 serverlog("Connecting Client")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500171
172 # Where to write events to
173 writer = ConnectionWriter(ui_fds[0])
174 self.event_handle = bb.event.register_UIHhandler(writer, True)
175 self.event_writer = writer
176
177 # Where to read commands from
178 reader = ConnectionReader(ui_fds[1])
179 fds.append(reader)
180 self.command_channel = reader
181
182 # Where to send command return values to
183 writer = ConnectionWriter(ui_fds[2])
184 self.command_channel_reply = writer
185
186 self.haveui = True
Andrew Geissler635e0e42020-08-21 15:58:33 -0500187 self.hadanyui = True
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500188
189 except (EOFError, OSError):
190 disconnect_client(self, fds)
191
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500192 if not self.timeout == -1.0 and not self.haveui and self.timeout and \
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500193 (self.lastui + self.timeout) < time.time():
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500194 serverlog("Server timeout, exiting.")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500195 self.quit = True
196
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500197 # If we don't see a UI connection within maxuiwait, its unlikely we're going to see
198 # one. We have had issue with processes hanging indefinitely so timing out UI-less
199 # servers is useful.
Andrew Geissler635e0e42020-08-21 15:58:33 -0500200 if not self.hadanyui and not self.xmlrpc and not self.timeout and (self.lastui + self.maxuiwait) < time.time():
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500201 serverlog("No UI connection within max timeout, exiting to avoid infinite loop.")
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500202 self.quit = True
203
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500204 if self.command_channel in ready:
205 try:
206 command = self.command_channel.get()
207 except EOFError:
208 # Client connection shutting down
209 ready = []
210 disconnect_client(self, fds)
211 continue
212 if command[0] == "terminateServer":
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500213 self.quit = True
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500214 continue
215 try:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500216 serverlog("Running command %s" % command)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500217 self.command_channel_reply.send(self.cooker.command.runCommand(command))
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500218 serverlog("Command Completed")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500219 except Exception as e:
Andrew Geisslerf0343792020-11-18 10:42:21 -0600220 serverlog('Exception in server main event loop running command %s (%s)' % (command, str(e)))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500221 logger.exception('Exception in server main event loop running command %s (%s)' % (command, str(e)))
222
223 if self.xmlrpc in ready:
224 self.xmlrpc.handle_requests()
225
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500226 if not seendata and hasattr(self.cooker, "data"):
227 heartbeat_event = self.cooker.data.getVar('BB_HEARTBEAT_EVENT')
228 if heartbeat_event:
229 try:
230 self.heartbeat_seconds = float(heartbeat_event)
231 except:
232 bb.warn('Ignoring invalid BB_HEARTBEAT_EVENT=%s, must be a float specifying seconds.' % heartbeat_event)
233
234 self.timeout = self.server_timeout or self.cooker.data.getVar('BB_SERVER_TIMEOUT')
235 try:
236 if self.timeout:
237 self.timeout = float(self.timeout)
238 except:
239 bb.warn('Ignoring invalid BB_SERVER_TIMEOUT=%s, must be a float specifying seconds.' % self.timeout)
240 seendata = True
241
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500242 ready = self.idle_commands(.1, fds)
243
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500244 if len(threading.enumerate()) != 1:
245 serverlog("More than one thread left?: " + str(threading.enumerate()))
246
247 serverlog("Exiting")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500248 # Remove the socket file so we don't get any more connections to avoid races
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500249 try:
250 os.unlink(self.sockname)
251 except:
252 pass
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500253 self.sock.close()
254
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500255 try:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500256 self.cooker.shutdown(True)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400257 self.cooker.notifier.stop()
258 self.cooker.confignotifier.stop()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500259 except:
260 pass
261
262 self.cooker.post_serve()
263
Andrew Geissler635e0e42020-08-21 15:58:33 -0500264 # Flush logs before we release the lock
265 sys.stdout.flush()
266 sys.stderr.flush()
267
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500268 # Finally release the lockfile but warn about other processes holding it open
269 lock = self.bitbake_lock
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500270 lockfile = self.bitbake_lock_name
271
272 def get_lock_contents(lockfile):
273 try:
274 with open(lockfile, "r") as f:
275 return f.readlines()
276 except FileNotFoundError:
277 return None
278
279 lockcontents = get_lock_contents(lockfile)
280 serverlog("Original lockfile contents: " + str(lockcontents))
281
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500282 lock.close()
283 lock = None
284
285 while not lock:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500286 i = 0
287 lock = None
288 while not lock and i < 30:
289 lock = bb.utils.lockfile(lockfile, shared=False, retry=False, block=False)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500290 if not lock:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500291 newlockcontents = get_lock_contents(lockfile)
292 if newlockcontents != lockcontents:
293 # A new server was started, the lockfile contents changed, we can exit
294 serverlog("Lockfile now contains different contents, exiting: " + str(newlockcontents))
295 return
296 time.sleep(0.1)
297 i += 1
298 if lock:
299 # We hold the lock so we can remove the file (hide stale pid data)
300 # via unlockfile.
301 bb.utils.unlockfile(lock)
302 serverlog("Exiting as we could obtain the lock")
303 return
304
305 if not lock:
306 # Some systems may not have lsof available
307 procs = None
308 try:
309 procs = subprocess.check_output(["lsof", '-w', lockfile], stderr=subprocess.STDOUT)
310 except subprocess.CalledProcessError:
311 # File was deleted?
312 continue
313 except OSError as e:
314 if e.errno != errno.ENOENT:
315 raise
316 if procs is None:
317 # Fall back to fuser if lsof is unavailable
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500318 try:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500319 procs = subprocess.check_output(["fuser", '-v', lockfile], stderr=subprocess.STDOUT)
320 except subprocess.CalledProcessError:
321 # File was deleted?
322 continue
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500323 except OSError as e:
324 if e.errno != errno.ENOENT:
325 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500326
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500327 msg = "Delaying shutdown due to active processes which appear to be holding bitbake.lock"
328 if procs:
329 msg += ":\n%s" % str(procs.decode("utf-8"))
330 serverlog(msg)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500331
332 def idle_commands(self, delay, fds=None):
333 nextsleep = delay
334 if not fds:
335 fds = []
336
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600337 for function, data in list(self._idlefuns.items()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500338 try:
339 retval = function(self, data, False)
340 if retval is False:
341 del self._idlefuns[function]
342 nextsleep = None
343 elif retval is True:
344 nextsleep = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600345 elif isinstance(retval, float) and nextsleep:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500346 if (retval < nextsleep):
347 nextsleep = retval
348 elif nextsleep is None:
349 continue
350 else:
351 fds = fds + retval
352 except SystemExit:
353 raise
354 except Exception as exc:
355 if not isinstance(exc, bb.BBHandledException):
356 logger.exception('Running idle function')
357 del self._idlefuns[function]
358 self.quit = True
359
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500360 # Create new heartbeat event?
361 now = time.time()
362 if now >= self.next_heartbeat:
363 # We might have missed heartbeats. Just trigger once in
364 # that case and continue after the usual delay.
365 self.next_heartbeat += self.heartbeat_seconds
366 if self.next_heartbeat <= now:
367 self.next_heartbeat = now + self.heartbeat_seconds
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500368 if hasattr(self.cooker, "data"):
369 heartbeat = bb.event.HeartbeatEvent(now)
William A. Kennington IIIac69b482021-06-02 12:28:27 -0700370 try:
371 bb.event.fire(heartbeat, self.cooker.data)
372 except Exception as exc:
373 if not isinstance(exc, bb.BBHandledException):
374 logger.exception('Running heartbeat function')
375 self.quit = True
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500376 if nextsleep and now + nextsleep > self.next_heartbeat:
377 # Shorten timeout so that we we wake up in time for
378 # the heartbeat.
379 nextsleep = self.next_heartbeat - now
380
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500381 if nextsleep is not None:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500382 if self.xmlrpc:
383 nextsleep = self.xmlrpc.get_timeout(nextsleep)
384 try:
385 return select.select(fds,[],[],nextsleep)[0]
386 except InterruptedError:
387 # Ignore EINTR
388 return []
389 else:
390 return select.select(fds,[],[],0)[0]
391
392
393class ServerCommunicator():
394 def __init__(self, connection, recv):
395 self.connection = connection
396 self.recv = recv
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500397
398 def runCommand(self, command):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500399 self.connection.send(command)
400 if not self.recv.poll(30):
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500401 logger.info("No reply from server in 30s")
Andrew Geissler475cb722020-07-10 16:00:51 -0500402 if not self.recv.poll(30):
403 raise ProcessTimeout("Timeout while waiting for a reply from the bitbake server (60s)")
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500404 ret, exc = self.recv.get()
405 # Should probably turn all exceptions in exc back into exceptions?
406 # For now, at least handle BBHandledException
407 if exc and ("BBHandledException" in exc or "SystemExit" in exc):
408 raise bb.BBHandledException()
409 return ret, exc
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500410
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500411 def updateFeatureSet(self, featureset):
412 _, error = self.runCommand(["setFeatures", featureset])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500413 if error:
414 logger.error("Unable to set the cooker to the correct featureset: %s" % error)
415 raise BaseException(error)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500416
417 def getEventHandle(self):
418 handle, error = self.runCommand(["getUIHandlerNum"])
419 if error:
420 logger.error("Unable to get UI Handler Number: %s" % error)
421 raise BaseException(error)
422
423 return handle
424
425 def terminateServer(self):
426 self.connection.send(['terminateServer'])
427 return
428
429class BitBakeProcessServerConnection(object):
430 def __init__(self, ui_channel, recv, eq, sock):
431 self.connection = ServerCommunicator(ui_channel, recv)
432 self.events = eq
433 # Save sock so it doesn't get gc'd for the life of our connection
434 self.socket_connection = sock
435
436 def terminate(self):
437 self.socket_connection.close()
438 self.connection.connection.close()
439 self.connection.recv.close()
440 return
441
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500442start_log_format = '--- Starting bitbake server pid %s at %s ---'
443start_log_datetime_format = '%Y-%m-%d %H:%M:%S.%f'
444
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500445class BitBakeServer(object):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500446
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500447 def __init__(self, lock, sockname, featureset, server_timeout, xmlrpcinterface):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500448
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500449 self.server_timeout = server_timeout
450 self.xmlrpcinterface = xmlrpcinterface
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500451 self.featureset = featureset
452 self.sockname = sockname
453 self.bitbake_lock = lock
454 self.readypipe, self.readypipein = os.pipe()
455
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800456 # Place the log in the builddirectory alongside the lock file
457 logfile = os.path.join(os.path.dirname(self.bitbake_lock.name), "bitbake-cookerdaemon.log")
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500458 self.logfile = logfile
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800459
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500460 startdatetime = datetime.datetime.now()
461 bb.daemonize.createDaemon(self._startServer, logfile)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500462 self.bitbake_lock.close()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800463 os.close(self.readypipein)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500464
465 ready = ConnectionReader(self.readypipe)
Brad Bishopf058f492019-01-28 23:50:33 -0500466 r = ready.poll(5)
467 if not r:
468 bb.note("Bitbake server didn't start within 5 seconds, waiting for 90")
469 r = ready.poll(90)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500470 if r:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800471 try:
472 r = ready.get()
473 except EOFError:
474 # Trap the child exitting/closing the pipe and error out
475 r = None
Brad Bishopf058f492019-01-28 23:50:33 -0500476 if not r or r[0] != "r":
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500477 ready.close()
Brad Bishopf058f492019-01-28 23:50:33 -0500478 bb.error("Unable to start bitbake server (%s)" % str(r))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500479 if os.path.exists(logfile):
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500480 logstart_re = re.compile(start_log_format % ('([0-9]+)', '([0-9-]+ [0-9:.]+)'))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500481 started = False
482 lines = []
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300483 lastlines = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500484 with open(logfile, "r") as f:
485 for line in f:
486 if started:
487 lines.append(line)
488 else:
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300489 lastlines.append(line)
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500490 res = logstart_re.search(line.rstrip())
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500491 if res:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500492 ldatetime = datetime.datetime.strptime(res.group(2), start_log_datetime_format)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500493 if ldatetime >= startdatetime:
494 started = True
495 lines.append(line)
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300496 if len(lastlines) > 60:
497 lastlines = lastlines[-60:]
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500498 if lines:
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300499 if len(lines) > 60:
500 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 -0500501 else:
502 bb.error("Server log for this session (%s):\n%s" % (logfile, "".join(lines)))
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300503 elif lastlines:
504 bb.error("Server didn't start, last 60 loglines (%s):\n%s" % (logfile, "".join(lastlines)))
505 else:
506 bb.error("%s doesn't exist" % logfile)
507
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500508 raise SystemExit(1)
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300509
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500510 ready.close()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500511
512 def _startServer(self):
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500513 os.close(self.readypipe)
514 os.set_inheritable(self.bitbake_lock.fileno(), True)
515 os.set_inheritable(self.readypipein, True)
516 serverscript = os.path.realpath(os.path.dirname(__file__) + "/../../../bin/bitbake-server")
Andrew Geisslerc926e172021-05-07 16:11:35 -0500517 os.execl(sys.executable, "bitbake-server", serverscript, "decafbad", str(self.bitbake_lock.fileno()), str(self.readypipein), self.logfile, self.bitbake_lock.name, self.sockname, str(self.server_timeout or 0), str(self.xmlrpcinterface[0]), str(self.xmlrpcinterface[1]))
Brad Bishope2d5b612018-11-23 10:55:50 +1300518
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500519def execServer(lockfd, readypipeinfd, lockname, sockname, server_timeout, xmlrpcinterface):
520
521 import bb.cookerdata
522 import bb.cooker
523
524 serverlog(start_log_format % (os.getpid(), datetime.datetime.now().strftime(start_log_datetime_format)))
525
526 try:
527 bitbake_lock = os.fdopen(lockfd, "w")
528
529 # Create server control socket
530 if os.path.exists(sockname):
531 os.unlink(sockname)
532
533 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
534 # AF_UNIX has path length issues so chdir here to workaround
535 cwd = os.getcwd()
Brad Bishop08902b02019-08-20 09:16:51 -0400536 try:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500537 os.chdir(os.path.dirname(sockname))
538 sock.bind(os.path.basename(sockname))
Andrew Geissler635e0e42020-08-21 15:58:33 -0500539 finally:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500540 os.chdir(cwd)
541 sock.listen(1)
542
543 server = ProcessServer(bitbake_lock, lockname, sock, sockname, server_timeout, xmlrpcinterface)
544 writer = ConnectionWriter(readypipeinfd)
545 try:
546 featureset = []
547 cooker = bb.cooker.BBCooker(featureset, server.register_idle_function)
548 except bb.BBHandledException:
549 return None
550 writer.send("r")
551 writer.close()
552 server.cooker = cooker
553 serverlog("Started bitbake server pid %d" % os.getpid())
554
555 server.run()
556 finally:
557 # Flush any ,essages/errors to the logfile before exit
558 sys.stdout.flush()
559 sys.stderr.flush()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500560
561def connectProcessServer(sockname, featureset):
562 # Connect to socket
563 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
564 # AF_UNIX has path length issues so chdir here to workaround
565 cwd = os.getcwd()
566
Brad Bishopf058f492019-01-28 23:50:33 -0500567 readfd = writefd = readfd1 = writefd1 = readfd2 = writefd2 = None
568 eq = command_chan_recv = command_chan = None
569
570 sock.settimeout(10)
571
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500572 try:
Brad Bishope2d5b612018-11-23 10:55:50 +1300573 try:
574 os.chdir(os.path.dirname(sockname))
Brad Bishopf058f492019-01-28 23:50:33 -0500575 finished = False
576 while not finished:
577 try:
578 sock.connect(os.path.basename(sockname))
579 finished = True
580 except IOError as e:
581 if e.errno == errno.EWOULDBLOCK:
582 pass
Richard Purdie3da11142019-02-05 21:34:37 +0000583 raise
Brad Bishope2d5b612018-11-23 10:55:50 +1300584 finally:
585 os.chdir(cwd)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500586
587 # Send an fd for the remote to write events to
588 readfd, writefd = os.pipe()
589 eq = BBUIEventQueue(readfd)
590 # Send an fd for the remote to recieve commands from
591 readfd1, writefd1 = os.pipe()
592 command_chan = ConnectionWriter(writefd1)
593 # Send an fd for the remote to write commands results to
594 readfd2, writefd2 = os.pipe()
595 command_chan_recv = ConnectionReader(readfd2)
596
597 sendfds(sock, [writefd, readfd1, writefd2])
598
599 server_connection = BitBakeProcessServerConnection(command_chan, command_chan_recv, eq, sock)
600
601 # Close the ends of the pipes we won't use
602 for i in [writefd, readfd1, writefd2]:
603 os.close(i)
604
605 server_connection.connection.updateFeatureSet(featureset)
606
607 except (Exception, SystemExit) as e:
608 if command_chan_recv:
609 command_chan_recv.close()
610 if command_chan:
611 command_chan.close()
612 for i in [writefd, readfd1, writefd2]:
613 try:
Brad Bishope2d5b612018-11-23 10:55:50 +1300614 if i:
615 os.close(i)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500616 except OSError:
617 pass
618 sock.close()
619 raise
620
621 return server_connection
622
623def sendfds(sock, fds):
624 '''Send an array of fds over an AF_UNIX socket.'''
625 fds = array.array('i', fds)
626 msg = bytes([len(fds) % 256])
627 sock.sendmsg([msg], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)])
628
629def recvfds(sock, size):
630 '''Receive an array of fds over an AF_UNIX socket.'''
631 a = array.array('i')
632 bytes_size = a.itemsize * size
633 msg, ancdata, flags, addr = sock.recvmsg(1, socket.CMSG_LEN(bytes_size))
634 if not msg and not ancdata:
635 raise EOFError
636 try:
637 if len(ancdata) != 1:
638 raise RuntimeError('received %d items of ancdata' %
639 len(ancdata))
640 cmsg_level, cmsg_type, cmsg_data = ancdata[0]
641 if (cmsg_level == socket.SOL_SOCKET and
642 cmsg_type == socket.SCM_RIGHTS):
643 if len(cmsg_data) % a.itemsize != 0:
644 raise ValueError
645 a.frombytes(cmsg_data)
646 assert len(a) % 256 == msg[0]
647 return list(a)
648 except (ValueError, IndexError):
649 pass
650 raise RuntimeError('Invalid data received')
651
652class BBUIEventQueue:
653 def __init__(self, readfd):
654
655 self.eventQueue = []
656 self.eventQueueLock = threading.Lock()
657 self.eventQueueNotify = threading.Event()
658
659 self.reader = ConnectionReader(readfd)
660
661 self.t = threading.Thread()
662 self.t.setDaemon(True)
663 self.t.run = self.startCallbackHandler
664 self.t.start()
665
666 def getEvent(self):
667 self.eventQueueLock.acquire()
668
669 if len(self.eventQueue) == 0:
670 self.eventQueueLock.release()
671 return None
672
673 item = self.eventQueue.pop(0)
674
675 if len(self.eventQueue) == 0:
676 self.eventQueueNotify.clear()
677
678 self.eventQueueLock.release()
679 return item
680
681 def waitEvent(self, delay):
682 self.eventQueueNotify.wait(delay)
683 return self.getEvent()
684
685 def queue_event(self, event):
686 self.eventQueueLock.acquire()
687 self.eventQueue.append(event)
688 self.eventQueueNotify.set()
689 self.eventQueueLock.release()
690
691 def send_event(self, event):
692 self.queue_event(pickle.loads(event))
693
694 def startCallbackHandler(self):
695 bb.utils.set_process_name("UIEventQueue")
696 while True:
697 try:
698 self.reader.wait()
699 event = self.reader.get()
700 self.queue_event(event)
701 except EOFError:
702 # Easiest way to exit is to close the file descriptor to cause an exit
703 break
704 self.reader.close()
705
706class ConnectionReader(object):
707
708 def __init__(self, fd):
709 self.reader = multiprocessing.connection.Connection(fd, writable=False)
710 self.rlock = multiprocessing.Lock()
711
712 def wait(self, timeout=None):
713 return multiprocessing.connection.wait([self.reader], timeout)
714
715 def poll(self, timeout=None):
716 return self.reader.poll(timeout)
717
718 def get(self):
719 with self.rlock:
720 res = self.reader.recv_bytes()
721 return multiprocessing.reduction.ForkingPickler.loads(res)
722
723 def fileno(self):
724 return self.reader.fileno()
725
726 def close(self):
727 return self.reader.close()
728
729
730class ConnectionWriter(object):
731
732 def __init__(self, fd):
733 self.writer = multiprocessing.connection.Connection(fd, readable=False)
734 self.wlock = multiprocessing.Lock()
735 # Why bb.event needs this I have no idea
736 self.event = self
737
738 def send(self, obj):
739 obj = multiprocessing.reduction.ForkingPickler.dumps(obj)
740 with self.wlock:
741 self.writer.send_bytes(obj)
742
743 def fileno(self):
744 return self.writer.fileno()
745
746 def close(self):
747 return self.writer.close()