blob: 19ef83980f5ebc63b768deb819fa9b8790acb784 [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
Andrew Geissler9aee5002022-03-30 16:27:02 +000023import signal
Brad Bishopd7bf8c12018-02-25 22:55:05 -050024import socket
25import subprocess
26import errno
27import re
28import datetime
Andrew Geisslerc9f78652020-09-18 14:11:35 -050029import pickle
Patrick Williams213cb262021-08-07 19:21:33 -050030import traceback
Patrick Williamsde0582f2022-04-08 10:23:27 -050031import gc
Brad Bishopd7bf8c12018-02-25 22:55:05 -050032import bb.server.xmlrpcserver
33from bb import daemonize
34from multiprocessing import queues
Patrick Williamsc124f4f2015-09-15 14:41:29 -050035
36logger = logging.getLogger('BitBake')
37
Brad Bishopd7bf8c12018-02-25 22:55:05 -050038class ProcessTimeout(SystemExit):
39 pass
Patrick Williamsc124f4f2015-09-15 14:41:29 -050040
Andrew Geisslerc9f78652020-09-18 14:11:35 -050041def serverlog(msg):
42 print(str(os.getpid()) + " " + datetime.datetime.now().strftime('%H:%M:%S.%f') + " " + msg)
43 sys.stdout.flush()
44
Andrew Geissler635e0e42020-08-21 15:58:33 -050045class ProcessServer():
Patrick Williamsc124f4f2015-09-15 14:41:29 -050046 profile_filename = "profile.log"
47 profile_processed_filename = "profile.log.processed"
48
Andrew Geisslerc9f78652020-09-18 14:11:35 -050049 def __init__(self, lock, lockname, sock, sockname, server_timeout, xmlrpcinterface):
Brad Bishopd7bf8c12018-02-25 22:55:05 -050050 self.command_channel = False
51 self.command_channel_reply = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -050052 self.quit = False
Brad Bishop6e60e8b2018-02-01 10:27:11 -050053 self.heartbeat_seconds = 1 # default, BB_HEARTBEAT_EVENT will be checked once we have a datastore.
54 self.next_heartbeat = time.time()
Patrick Williamsc124f4f2015-09-15 14:41:29 -050055
Brad Bishopd7bf8c12018-02-25 22:55:05 -050056 self.event_handle = None
Andrew Geissler635e0e42020-08-21 15:58:33 -050057 self.hadanyui = False
Brad Bishopd7bf8c12018-02-25 22:55:05 -050058 self.haveui = False
Andrew Geisslerb7d28612020-07-24 16:15:54 -050059 self.maxuiwait = 30
Brad Bishopd7bf8c12018-02-25 22:55:05 -050060 self.xmlrpc = False
61
62 self._idlefuns = {}
63
64 self.bitbake_lock = lock
Andrew Geisslerc9f78652020-09-18 14:11:35 -050065 self.bitbake_lock_name = lockname
Brad Bishopd7bf8c12018-02-25 22:55:05 -050066 self.sock = sock
67 self.sockname = sockname
68
Andrew Geissler635e0e42020-08-21 15:58:33 -050069 self.server_timeout = server_timeout
Andrew Geisslerc9f78652020-09-18 14:11:35 -050070 self.timeout = self.server_timeout
Andrew Geissler635e0e42020-08-21 15:58:33 -050071 self.xmlrpcinterface = xmlrpcinterface
72
Brad Bishopd7bf8c12018-02-25 22:55:05 -050073 def register_idle_function(self, function, data):
74 """Register a function to be called while the server is idle"""
75 assert hasattr(function, '__call__')
76 self._idlefuns[function] = data
Patrick Williamsc124f4f2015-09-15 14:41:29 -050077
78 def run(self):
Brad Bishopd7bf8c12018-02-25 22:55:05 -050079
80 if self.xmlrpcinterface[0]:
81 self.xmlrpc = bb.server.xmlrpcserver.BitBakeXMLRPCServer(self.xmlrpcinterface, self.cooker, self)
82
Andrew Geisslerc9f78652020-09-18 14:11:35 -050083 serverlog("Bitbake XMLRPC server address: %s, server port: %s" % (self.xmlrpc.host, self.xmlrpc.port))
Brad Bishopd7bf8c12018-02-25 22:55:05 -050084
85 try:
86 self.bitbake_lock.seek(0)
87 self.bitbake_lock.truncate()
88 if self.xmlrpc:
89 self.bitbake_lock.write("%s %s:%s\n" % (os.getpid(), self.xmlrpc.host, self.xmlrpc.port))
90 else:
91 self.bitbake_lock.write("%s\n" % (os.getpid()))
92 self.bitbake_lock.flush()
93 except Exception as e:
Andrew Geisslerc9f78652020-09-18 14:11:35 -050094 serverlog("Error writing to lock file: %s" % str(e))
Brad Bishopd7bf8c12018-02-25 22:55:05 -050095 pass
96
97 if self.cooker.configuration.profile:
98 try:
99 import cProfile as profile
100 except:
101 import profile
102 prof = profile.Profile()
103
104 ret = profile.Profile.runcall(prof, self.main)
105
106 prof.dump_stats("profile.log")
107 bb.utils.process_profilelog("profile.log")
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500108 serverlog("Raw profiling information saved to profile.log and processed statistics to profile.log.processed")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500109
110 else:
111 ret = self.main()
112
113 return ret
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500114
115 def main(self):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500116 self.cooker.pre_serve()
117
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500118 bb.utils.set_process_name("Cooker")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500119
120 ready = []
Brad Bishopf058f492019-01-28 23:50:33 -0500121 newconnections = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500122
123 self.controllersock = False
124 fds = [self.sock]
125 if self.xmlrpc:
126 fds.append(self.xmlrpc)
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500127 seendata = False
128 serverlog("Entering server connection loop")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500129
130 def disconnect_client(self, fds):
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500131 serverlog("Disconnecting Client")
Brad Bishopf058f492019-01-28 23:50:33 -0500132 if self.controllersock:
133 fds.remove(self.controllersock)
134 self.controllersock.close()
135 self.controllersock = False
136 if self.haveui:
137 fds.remove(self.command_channel)
138 bb.event.unregister_UIHhandler(self.event_handle, True)
139 self.command_channel_reply.writer.close()
140 self.event_writer.writer.close()
141 self.command_channel.close()
142 self.command_channel = False
143 del self.event_writer
144 self.lastui = time.time()
145 self.cooker.clientComplete()
146 self.haveui = False
147 ready = select.select(fds,[],[],0)[0]
148 if newconnections:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500149 serverlog("Starting new client")
Brad Bishopf058f492019-01-28 23:50:33 -0500150 conn = newconnections.pop(-1)
151 fds.append(conn)
152 self.controllersock = conn
Andrew Geissler5f350902021-07-23 13:09:54 -0400153 elif not self.timeout and not ready:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500154 serverlog("No timeout, exiting.")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500155 self.quit = True
156
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500157 self.lastui = time.time()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500158 while not self.quit:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500159 if self.sock in ready:
Brad Bishopf058f492019-01-28 23:50:33 -0500160 while select.select([self.sock],[],[],0)[0]:
161 controllersock, address = self.sock.accept()
162 if self.controllersock:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500163 serverlog("Queuing %s (%s)" % (str(ready), str(newconnections)))
Brad Bishopf058f492019-01-28 23:50:33 -0500164 newconnections.append(controllersock)
165 else:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500166 serverlog("Accepting %s (%s)" % (str(ready), str(newconnections)))
Brad Bishopf058f492019-01-28 23:50:33 -0500167 self.controllersock = controllersock
168 fds.append(controllersock)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500169 if self.controllersock in ready:
170 try:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500171 serverlog("Processing Client")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500172 ui_fds = recvfds(self.controllersock, 3)
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500173 serverlog("Connecting Client")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500174
175 # Where to write events to
176 writer = ConnectionWriter(ui_fds[0])
177 self.event_handle = bb.event.register_UIHhandler(writer, True)
178 self.event_writer = writer
179
180 # Where to read commands from
181 reader = ConnectionReader(ui_fds[1])
182 fds.append(reader)
183 self.command_channel = reader
184
185 # Where to send command return values to
186 writer = ConnectionWriter(ui_fds[2])
187 self.command_channel_reply = writer
188
189 self.haveui = True
Andrew Geissler635e0e42020-08-21 15:58:33 -0500190 self.hadanyui = True
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500191
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():
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500197 serverlog("Server timeout, exiting.")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500198 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.
Andrew Geissler635e0e42020-08-21 15:58:33 -0500203 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 -0500204 serverlog("No UI connection within max timeout, exiting to avoid infinite loop.")
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500205 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:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500219 serverlog("Running command %s" % command)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500220 self.command_channel_reply.send(self.cooker.command.runCommand(command))
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500221 serverlog("Command Completed")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500222 except Exception as e:
Patrick Williams213cb262021-08-07 19:21:33 -0500223 stack = traceback.format_exc()
224 serverlog('Exception in server main event loop running command %s (%s)' % (command, stack))
225 logger.exception('Exception in server main event loop running command %s (%s)' % (command, stack))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500226
227 if self.xmlrpc in ready:
228 self.xmlrpc.handle_requests()
229
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500230 if not seendata and hasattr(self.cooker, "data"):
231 heartbeat_event = self.cooker.data.getVar('BB_HEARTBEAT_EVENT')
232 if heartbeat_event:
233 try:
234 self.heartbeat_seconds = float(heartbeat_event)
235 except:
236 bb.warn('Ignoring invalid BB_HEARTBEAT_EVENT=%s, must be a float specifying seconds.' % heartbeat_event)
237
238 self.timeout = self.server_timeout or self.cooker.data.getVar('BB_SERVER_TIMEOUT')
239 try:
240 if self.timeout:
241 self.timeout = float(self.timeout)
242 except:
243 bb.warn('Ignoring invalid BB_SERVER_TIMEOUT=%s, must be a float specifying seconds.' % self.timeout)
244 seendata = True
245
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500246 ready = self.idle_commands(.1, fds)
247
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500248 serverlog("Exiting")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500249 # Remove the socket file so we don't get any more connections to avoid races
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500250 try:
251 os.unlink(self.sockname)
252 except:
253 pass
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500254 self.sock.close()
255
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500256 try:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500257 self.cooker.shutdown(True)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400258 self.cooker.notifier.stop()
259 self.cooker.confignotifier.stop()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500260 except:
261 pass
262
263 self.cooker.post_serve()
264
Andrew Geissler9aee5002022-03-30 16:27:02 +0000265 if len(threading.enumerate()) != 1:
266 serverlog("More than one thread left?: " + str(threading.enumerate()))
267
Andrew Geissler635e0e42020-08-21 15:58:33 -0500268 # Flush logs before we release the lock
269 sys.stdout.flush()
270 sys.stderr.flush()
271
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500272 # Finally release the lockfile but warn about other processes holding it open
273 lock = self.bitbake_lock
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500274 lockfile = self.bitbake_lock_name
275
276 def get_lock_contents(lockfile):
277 try:
278 with open(lockfile, "r") as f:
279 return f.readlines()
280 except FileNotFoundError:
281 return None
282
283 lockcontents = get_lock_contents(lockfile)
284 serverlog("Original lockfile contents: " + str(lockcontents))
285
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500286 lock.close()
287 lock = None
288
289 while not lock:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500290 i = 0
291 lock = None
292 while not lock and i < 30:
293 lock = bb.utils.lockfile(lockfile, shared=False, retry=False, block=False)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500294 if not lock:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500295 newlockcontents = get_lock_contents(lockfile)
296 if newlockcontents != lockcontents:
297 # A new server was started, the lockfile contents changed, we can exit
298 serverlog("Lockfile now contains different contents, exiting: " + str(newlockcontents))
299 return
300 time.sleep(0.1)
301 i += 1
302 if lock:
303 # We hold the lock so we can remove the file (hide stale pid data)
304 # via unlockfile.
305 bb.utils.unlockfile(lock)
306 serverlog("Exiting as we could obtain the lock")
307 return
308
309 if not lock:
310 # Some systems may not have lsof available
311 procs = None
312 try:
313 procs = subprocess.check_output(["lsof", '-w', lockfile], stderr=subprocess.STDOUT)
314 except subprocess.CalledProcessError:
315 # File was deleted?
316 continue
317 except OSError as e:
318 if e.errno != errno.ENOENT:
319 raise
320 if procs is None:
321 # Fall back to fuser if lsof is unavailable
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500322 try:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500323 procs = subprocess.check_output(["fuser", '-v', lockfile], stderr=subprocess.STDOUT)
324 except subprocess.CalledProcessError:
325 # File was deleted?
326 continue
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500327 except OSError as e:
328 if e.errno != errno.ENOENT:
329 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500330
Andrew Geissler595f6302022-01-24 19:11:47 +0000331 msg = ["Delaying shutdown due to active processes which appear to be holding bitbake.lock"]
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500332 if procs:
Andrew Geissler595f6302022-01-24 19:11:47 +0000333 msg.append(":\n%s" % str(procs.decode("utf-8")))
334 serverlog("".join(msg))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500335
336 def idle_commands(self, delay, fds=None):
337 nextsleep = delay
338 if not fds:
339 fds = []
340
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600341 for function, data in list(self._idlefuns.items()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500342 try:
343 retval = function(self, data, False)
344 if retval is False:
345 del self._idlefuns[function]
346 nextsleep = None
347 elif retval is True:
348 nextsleep = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600349 elif isinstance(retval, float) and nextsleep:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500350 if (retval < nextsleep):
351 nextsleep = retval
352 elif nextsleep is None:
353 continue
354 else:
355 fds = fds + retval
356 except SystemExit:
357 raise
358 except Exception as exc:
359 if not isinstance(exc, bb.BBHandledException):
360 logger.exception('Running idle function')
361 del self._idlefuns[function]
362 self.quit = True
363
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500364 # Create new heartbeat event?
365 now = time.time()
366 if now >= self.next_heartbeat:
367 # We might have missed heartbeats. Just trigger once in
368 # that case and continue after the usual delay.
369 self.next_heartbeat += self.heartbeat_seconds
370 if self.next_heartbeat <= now:
371 self.next_heartbeat = now + self.heartbeat_seconds
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500372 if hasattr(self.cooker, "data"):
373 heartbeat = bb.event.HeartbeatEvent(now)
William A. Kennington IIIac69b482021-06-02 12:28:27 -0700374 try:
375 bb.event.fire(heartbeat, self.cooker.data)
376 except Exception as exc:
377 if not isinstance(exc, bb.BBHandledException):
378 logger.exception('Running heartbeat function')
379 self.quit = True
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500380 if nextsleep and now + nextsleep > self.next_heartbeat:
381 # Shorten timeout so that we we wake up in time for
382 # the heartbeat.
383 nextsleep = self.next_heartbeat - now
384
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500385 if nextsleep is not None:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500386 if self.xmlrpc:
387 nextsleep = self.xmlrpc.get_timeout(nextsleep)
388 try:
389 return select.select(fds,[],[],nextsleep)[0]
390 except InterruptedError:
391 # Ignore EINTR
392 return []
393 else:
394 return select.select(fds,[],[],0)[0]
395
396
397class ServerCommunicator():
398 def __init__(self, connection, recv):
399 self.connection = connection
400 self.recv = recv
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500401
402 def runCommand(self, command):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500403 self.connection.send(command)
404 if not self.recv.poll(30):
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500405 logger.info("No reply from server in 30s")
Andrew Geissler475cb722020-07-10 16:00:51 -0500406 if not self.recv.poll(30):
407 raise ProcessTimeout("Timeout while waiting for a reply from the bitbake server (60s)")
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500408 ret, exc = self.recv.get()
409 # Should probably turn all exceptions in exc back into exceptions?
410 # For now, at least handle BBHandledException
411 if exc and ("BBHandledException" in exc or "SystemExit" in exc):
412 raise bb.BBHandledException()
413 return ret, exc
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500414
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500415 def updateFeatureSet(self, featureset):
416 _, error = self.runCommand(["setFeatures", featureset])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500417 if error:
418 logger.error("Unable to set the cooker to the correct featureset: %s" % error)
419 raise BaseException(error)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500420
421 def getEventHandle(self):
422 handle, error = self.runCommand(["getUIHandlerNum"])
423 if error:
424 logger.error("Unable to get UI Handler Number: %s" % error)
425 raise BaseException(error)
426
427 return handle
428
429 def terminateServer(self):
430 self.connection.send(['terminateServer'])
431 return
432
433class BitBakeProcessServerConnection(object):
434 def __init__(self, ui_channel, recv, eq, sock):
435 self.connection = ServerCommunicator(ui_channel, recv)
436 self.events = eq
437 # Save sock so it doesn't get gc'd for the life of our connection
438 self.socket_connection = sock
439
440 def terminate(self):
441 self.socket_connection.close()
442 self.connection.connection.close()
443 self.connection.recv.close()
444 return
445
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500446start_log_format = '--- Starting bitbake server pid %s at %s ---'
447start_log_datetime_format = '%Y-%m-%d %H:%M:%S.%f'
448
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500449class BitBakeServer(object):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500450
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500451 def __init__(self, lock, sockname, featureset, server_timeout, xmlrpcinterface):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500452
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500453 self.server_timeout = server_timeout
454 self.xmlrpcinterface = xmlrpcinterface
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500455 self.featureset = featureset
456 self.sockname = sockname
457 self.bitbake_lock = lock
458 self.readypipe, self.readypipein = os.pipe()
459
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800460 # Place the log in the builddirectory alongside the lock file
461 logfile = os.path.join(os.path.dirname(self.bitbake_lock.name), "bitbake-cookerdaemon.log")
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500462 self.logfile = logfile
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800463
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500464 startdatetime = datetime.datetime.now()
465 bb.daemonize.createDaemon(self._startServer, logfile)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500466 self.bitbake_lock.close()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800467 os.close(self.readypipein)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500468
469 ready = ConnectionReader(self.readypipe)
Brad Bishopf058f492019-01-28 23:50:33 -0500470 r = ready.poll(5)
471 if not r:
472 bb.note("Bitbake server didn't start within 5 seconds, waiting for 90")
473 r = ready.poll(90)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500474 if r:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800475 try:
476 r = ready.get()
477 except EOFError:
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500478 # Trap the child exiting/closing the pipe and error out
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800479 r = None
Brad Bishopf058f492019-01-28 23:50:33 -0500480 if not r or r[0] != "r":
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500481 ready.close()
Brad Bishopf058f492019-01-28 23:50:33 -0500482 bb.error("Unable to start bitbake server (%s)" % str(r))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500483 if os.path.exists(logfile):
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500484 logstart_re = re.compile(start_log_format % ('([0-9]+)', '([0-9-]+ [0-9:.]+)'))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500485 started = False
486 lines = []
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300487 lastlines = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500488 with open(logfile, "r") as f:
489 for line in f:
490 if started:
491 lines.append(line)
492 else:
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300493 lastlines.append(line)
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500494 res = logstart_re.search(line.rstrip())
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500495 if res:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500496 ldatetime = datetime.datetime.strptime(res.group(2), start_log_datetime_format)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500497 if ldatetime >= startdatetime:
498 started = True
499 lines.append(line)
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300500 if len(lastlines) > 60:
501 lastlines = lastlines[-60:]
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500502 if lines:
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300503 if len(lines) > 60:
504 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 -0500505 else:
506 bb.error("Server log for this session (%s):\n%s" % (logfile, "".join(lines)))
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300507 elif lastlines:
508 bb.error("Server didn't start, last 60 loglines (%s):\n%s" % (logfile, "".join(lastlines)))
509 else:
510 bb.error("%s doesn't exist" % logfile)
511
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500512 raise SystemExit(1)
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300513
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500514 ready.close()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500515
516 def _startServer(self):
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500517 os.close(self.readypipe)
518 os.set_inheritable(self.bitbake_lock.fileno(), True)
519 os.set_inheritable(self.readypipein, True)
520 serverscript = os.path.realpath(os.path.dirname(__file__) + "/../../../bin/bitbake-server")
Andrew Geisslerc926e172021-05-07 16:11:35 -0500521 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 +1300522
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500523def execServer(lockfd, readypipeinfd, lockname, sockname, server_timeout, xmlrpcinterface):
524
525 import bb.cookerdata
526 import bb.cooker
527
528 serverlog(start_log_format % (os.getpid(), datetime.datetime.now().strftime(start_log_datetime_format)))
529
530 try:
531 bitbake_lock = os.fdopen(lockfd, "w")
532
533 # Create server control socket
534 if os.path.exists(sockname):
535 os.unlink(sockname)
536
537 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
538 # AF_UNIX has path length issues so chdir here to workaround
539 cwd = os.getcwd()
Brad Bishop08902b02019-08-20 09:16:51 -0400540 try:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500541 os.chdir(os.path.dirname(sockname))
542 sock.bind(os.path.basename(sockname))
Andrew Geissler635e0e42020-08-21 15:58:33 -0500543 finally:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500544 os.chdir(cwd)
545 sock.listen(1)
546
547 server = ProcessServer(bitbake_lock, lockname, sock, sockname, server_timeout, xmlrpcinterface)
548 writer = ConnectionWriter(readypipeinfd)
549 try:
550 featureset = []
551 cooker = bb.cooker.BBCooker(featureset, server.register_idle_function)
552 except bb.BBHandledException:
553 return None
554 writer.send("r")
555 writer.close()
556 server.cooker = cooker
557 serverlog("Started bitbake server pid %d" % os.getpid())
558
559 server.run()
560 finally:
Andrew Geissler9aee5002022-03-30 16:27:02 +0000561 # Flush any messages/errors to the logfile before exit
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500562 sys.stdout.flush()
563 sys.stderr.flush()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500564
565def connectProcessServer(sockname, featureset):
566 # Connect to socket
567 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
568 # AF_UNIX has path length issues so chdir here to workaround
569 cwd = os.getcwd()
570
Brad Bishopf058f492019-01-28 23:50:33 -0500571 readfd = writefd = readfd1 = writefd1 = readfd2 = writefd2 = None
572 eq = command_chan_recv = command_chan = None
573
574 sock.settimeout(10)
575
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500576 try:
Brad Bishope2d5b612018-11-23 10:55:50 +1300577 try:
578 os.chdir(os.path.dirname(sockname))
Brad Bishopf058f492019-01-28 23:50:33 -0500579 finished = False
580 while not finished:
581 try:
582 sock.connect(os.path.basename(sockname))
583 finished = True
584 except IOError as e:
585 if e.errno == errno.EWOULDBLOCK:
586 pass
Richard Purdie3da11142019-02-05 21:34:37 +0000587 raise
Brad Bishope2d5b612018-11-23 10:55:50 +1300588 finally:
589 os.chdir(cwd)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500590
591 # Send an fd for the remote to write events to
592 readfd, writefd = os.pipe()
593 eq = BBUIEventQueue(readfd)
594 # Send an fd for the remote to recieve commands from
595 readfd1, writefd1 = os.pipe()
596 command_chan = ConnectionWriter(writefd1)
597 # Send an fd for the remote to write commands results to
598 readfd2, writefd2 = os.pipe()
599 command_chan_recv = ConnectionReader(readfd2)
600
601 sendfds(sock, [writefd, readfd1, writefd2])
602
603 server_connection = BitBakeProcessServerConnection(command_chan, command_chan_recv, eq, sock)
604
605 # Close the ends of the pipes we won't use
606 for i in [writefd, readfd1, writefd2]:
607 os.close(i)
608
609 server_connection.connection.updateFeatureSet(featureset)
610
611 except (Exception, SystemExit) as e:
612 if command_chan_recv:
613 command_chan_recv.close()
614 if command_chan:
615 command_chan.close()
616 for i in [writefd, readfd1, writefd2]:
617 try:
Brad Bishope2d5b612018-11-23 10:55:50 +1300618 if i:
619 os.close(i)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500620 except OSError:
621 pass
622 sock.close()
623 raise
624
625 return server_connection
626
627def sendfds(sock, fds):
628 '''Send an array of fds over an AF_UNIX socket.'''
629 fds = array.array('i', fds)
630 msg = bytes([len(fds) % 256])
631 sock.sendmsg([msg], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)])
632
633def recvfds(sock, size):
634 '''Receive an array of fds over an AF_UNIX socket.'''
635 a = array.array('i')
636 bytes_size = a.itemsize * size
637 msg, ancdata, flags, addr = sock.recvmsg(1, socket.CMSG_LEN(bytes_size))
638 if not msg and not ancdata:
639 raise EOFError
640 try:
641 if len(ancdata) != 1:
642 raise RuntimeError('received %d items of ancdata' %
643 len(ancdata))
644 cmsg_level, cmsg_type, cmsg_data = ancdata[0]
645 if (cmsg_level == socket.SOL_SOCKET and
646 cmsg_type == socket.SCM_RIGHTS):
647 if len(cmsg_data) % a.itemsize != 0:
648 raise ValueError
649 a.frombytes(cmsg_data)
650 assert len(a) % 256 == msg[0]
651 return list(a)
652 except (ValueError, IndexError):
653 pass
654 raise RuntimeError('Invalid data received')
655
656class BBUIEventQueue:
657 def __init__(self, readfd):
658
659 self.eventQueue = []
660 self.eventQueueLock = threading.Lock()
661 self.eventQueueNotify = threading.Event()
662
663 self.reader = ConnectionReader(readfd)
664
665 self.t = threading.Thread()
Andrew Geissler5199d832021-09-24 16:47:35 -0500666 self.t.daemon = True
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500667 self.t.run = self.startCallbackHandler
668 self.t.start()
669
670 def getEvent(self):
671 self.eventQueueLock.acquire()
672
673 if len(self.eventQueue) == 0:
674 self.eventQueueLock.release()
675 return None
676
677 item = self.eventQueue.pop(0)
678
679 if len(self.eventQueue) == 0:
680 self.eventQueueNotify.clear()
681
682 self.eventQueueLock.release()
683 return item
684
685 def waitEvent(self, delay):
686 self.eventQueueNotify.wait(delay)
687 return self.getEvent()
688
689 def queue_event(self, event):
690 self.eventQueueLock.acquire()
691 self.eventQueue.append(event)
692 self.eventQueueNotify.set()
693 self.eventQueueLock.release()
694
695 def send_event(self, event):
696 self.queue_event(pickle.loads(event))
697
698 def startCallbackHandler(self):
699 bb.utils.set_process_name("UIEventQueue")
700 while True:
701 try:
702 self.reader.wait()
703 event = self.reader.get()
704 self.queue_event(event)
705 except EOFError:
706 # Easiest way to exit is to close the file descriptor to cause an exit
707 break
708 self.reader.close()
709
710class ConnectionReader(object):
711
712 def __init__(self, fd):
713 self.reader = multiprocessing.connection.Connection(fd, writable=False)
714 self.rlock = multiprocessing.Lock()
715
716 def wait(self, timeout=None):
717 return multiprocessing.connection.wait([self.reader], timeout)
718
719 def poll(self, timeout=None):
720 return self.reader.poll(timeout)
721
722 def get(self):
723 with self.rlock:
724 res = self.reader.recv_bytes()
725 return multiprocessing.reduction.ForkingPickler.loads(res)
726
727 def fileno(self):
728 return self.reader.fileno()
729
730 def close(self):
731 return self.reader.close()
732
733
734class ConnectionWriter(object):
735
736 def __init__(self, fd):
737 self.writer = multiprocessing.connection.Connection(fd, readable=False)
738 self.wlock = multiprocessing.Lock()
739 # Why bb.event needs this I have no idea
740 self.event = self
741
Andrew Geissler9aee5002022-03-30 16:27:02 +0000742 def _send(self, obj):
Patrick Williamsde0582f2022-04-08 10:23:27 -0500743 gc.disable()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500744 with self.wlock:
745 self.writer.send_bytes(obj)
Patrick Williamsde0582f2022-04-08 10:23:27 -0500746 gc.enable()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500747
Andrew Geissler9aee5002022-03-30 16:27:02 +0000748 def send(self, obj):
749 obj = multiprocessing.reduction.ForkingPickler.dumps(obj)
750 # See notes/code in CookerParser
751 # We must not terminate holding this lock else processes will hang.
752 # For SIGTERM, raising afterwards avoids this.
753 # For SIGINT, we don't want to have written partial data to the pipe.
754 # pthread_sigmask block/unblock would be nice but doesn't work, https://bugs.python.org/issue47139
755 process = multiprocessing.current_process()
756 if process and hasattr(process, "queue_signals"):
757 with process.signal_threadlock:
758 process.queue_signals = True
759 self._send(obj)
760 process.queue_signals = False
761 for sig in process.signal_received.pop():
762 process.handle_sig(sig, None)
763 else:
764 self._send(obj)
765
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500766 def fileno(self):
767 return self.writer.fileno()
768
769 def close(self):
770 return self.writer.close()