blob: 3668a32b71b500b672799fc1a903b2c4b2b3757d [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
Patrick Williams213cb262021-08-07 19:21:33 -050029import traceback
Patrick Williamsde0582f2022-04-08 10:23:27 -050030import gc
Patrick Williams6ad2fb62023-06-15 12:50:14 -050031import stat
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
Patrick Williams6ad2fb62023-06-15 12:50:14 -050068 # It is possible the directory may be renamed. Cache the inode of the socket file
69 # so we can tell if things changed.
70 self.sockinode = os.stat(self.sockname)[stat.ST_INO]
Brad Bishopd7bf8c12018-02-25 22:55:05 -050071
Andrew Geissler635e0e42020-08-21 15:58:33 -050072 self.server_timeout = server_timeout
Andrew Geisslerc9f78652020-09-18 14:11:35 -050073 self.timeout = self.server_timeout
Andrew Geissler635e0e42020-08-21 15:58:33 -050074 self.xmlrpcinterface = xmlrpcinterface
75
Brad Bishopd7bf8c12018-02-25 22:55:05 -050076 def register_idle_function(self, function, data):
77 """Register a function to be called while the server is idle"""
78 assert hasattr(function, '__call__')
79 self._idlefuns[function] = data
Patrick Williamsc124f4f2015-09-15 14:41:29 -050080
81 def run(self):
Brad Bishopd7bf8c12018-02-25 22:55:05 -050082
83 if self.xmlrpcinterface[0]:
84 self.xmlrpc = bb.server.xmlrpcserver.BitBakeXMLRPCServer(self.xmlrpcinterface, self.cooker, self)
85
Andrew Geisslerc9f78652020-09-18 14:11:35 -050086 serverlog("Bitbake XMLRPC server address: %s, server port: %s" % (self.xmlrpc.host, self.xmlrpc.port))
Brad Bishopd7bf8c12018-02-25 22:55:05 -050087
88 try:
89 self.bitbake_lock.seek(0)
90 self.bitbake_lock.truncate()
91 if self.xmlrpc:
92 self.bitbake_lock.write("%s %s:%s\n" % (os.getpid(), self.xmlrpc.host, self.xmlrpc.port))
93 else:
94 self.bitbake_lock.write("%s\n" % (os.getpid()))
95 self.bitbake_lock.flush()
96 except Exception as e:
Andrew Geisslerc9f78652020-09-18 14:11:35 -050097 serverlog("Error writing to lock file: %s" % str(e))
Brad Bishopd7bf8c12018-02-25 22:55:05 -050098 pass
99
100 if self.cooker.configuration.profile:
101 try:
102 import cProfile as profile
103 except:
104 import profile
105 prof = profile.Profile()
106
107 ret = profile.Profile.runcall(prof, self.main)
108
109 prof.dump_stats("profile.log")
110 bb.utils.process_profilelog("profile.log")
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500111 serverlog("Raw profiling information saved to profile.log and processed statistics to profile.log.processed")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500112
113 else:
114 ret = self.main()
115
116 return ret
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500117
118 def main(self):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500119 self.cooker.pre_serve()
120
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500121 bb.utils.set_process_name("Cooker")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500122
123 ready = []
Brad Bishopf058f492019-01-28 23:50:33 -0500124 newconnections = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500125
126 self.controllersock = False
127 fds = [self.sock]
128 if self.xmlrpc:
129 fds.append(self.xmlrpc)
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500130 seendata = False
131 serverlog("Entering server connection loop")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500132
133 def disconnect_client(self, fds):
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500134 serverlog("Disconnecting Client")
Brad Bishopf058f492019-01-28 23:50:33 -0500135 if self.controllersock:
136 fds.remove(self.controllersock)
137 self.controllersock.close()
138 self.controllersock = False
139 if self.haveui:
140 fds.remove(self.command_channel)
141 bb.event.unregister_UIHhandler(self.event_handle, True)
142 self.command_channel_reply.writer.close()
143 self.event_writer.writer.close()
144 self.command_channel.close()
145 self.command_channel = False
146 del self.event_writer
147 self.lastui = time.time()
148 self.cooker.clientComplete()
149 self.haveui = False
150 ready = select.select(fds,[],[],0)[0]
151 if newconnections:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500152 serverlog("Starting new client")
Brad Bishopf058f492019-01-28 23:50:33 -0500153 conn = newconnections.pop(-1)
154 fds.append(conn)
155 self.controllersock = conn
Andrew Geissler5f350902021-07-23 13:09:54 -0400156 elif not self.timeout and not ready:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500157 serverlog("No timeout, exiting.")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500158 self.quit = True
159
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500160 self.lastui = time.time()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500161 while not self.quit:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500162 if self.sock in ready:
Brad Bishopf058f492019-01-28 23:50:33 -0500163 while select.select([self.sock],[],[],0)[0]:
164 controllersock, address = self.sock.accept()
165 if self.controllersock:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500166 serverlog("Queuing %s (%s)" % (str(ready), str(newconnections)))
Brad Bishopf058f492019-01-28 23:50:33 -0500167 newconnections.append(controllersock)
168 else:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500169 serverlog("Accepting %s (%s)" % (str(ready), str(newconnections)))
Brad Bishopf058f492019-01-28 23:50:33 -0500170 self.controllersock = controllersock
171 fds.append(controllersock)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500172 if self.controllersock in ready:
173 try:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500174 serverlog("Processing Client")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500175 ui_fds = recvfds(self.controllersock, 3)
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500176 serverlog("Connecting Client")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500177
178 # Where to write events to
179 writer = ConnectionWriter(ui_fds[0])
180 self.event_handle = bb.event.register_UIHhandler(writer, True)
181 self.event_writer = writer
182
183 # Where to read commands from
184 reader = ConnectionReader(ui_fds[1])
185 fds.append(reader)
186 self.command_channel = reader
187
188 # Where to send command return values to
189 writer = ConnectionWriter(ui_fds[2])
190 self.command_channel_reply = writer
191
192 self.haveui = True
Andrew Geissler635e0e42020-08-21 15:58:33 -0500193 self.hadanyui = True
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500194
195 except (EOFError, OSError):
196 disconnect_client(self, fds)
197
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500198 if not self.timeout == -1.0 and not self.haveui and self.timeout and \
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500199 (self.lastui + self.timeout) < time.time():
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500200 serverlog("Server timeout, exiting.")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500201 self.quit = True
202
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500203 # If we don't see a UI connection within maxuiwait, its unlikely we're going to see
204 # one. We have had issue with processes hanging indefinitely so timing out UI-less
205 # servers is useful.
Andrew Geissler635e0e42020-08-21 15:58:33 -0500206 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 -0500207 serverlog("No UI connection within max timeout, exiting to avoid infinite loop.")
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500208 self.quit = True
209
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500210 if self.command_channel in ready:
211 try:
212 command = self.command_channel.get()
213 except EOFError:
214 # Client connection shutting down
215 ready = []
216 disconnect_client(self, fds)
217 continue
218 if command[0] == "terminateServer":
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500219 self.quit = True
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500220 continue
221 try:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500222 serverlog("Running command %s" % command)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500223 self.command_channel_reply.send(self.cooker.command.runCommand(command))
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500224 serverlog("Command Completed")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500225 except Exception as e:
Patrick Williams213cb262021-08-07 19:21:33 -0500226 stack = traceback.format_exc()
227 serverlog('Exception in server main event loop running command %s (%s)' % (command, stack))
228 logger.exception('Exception in server main event loop running command %s (%s)' % (command, stack))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500229
230 if self.xmlrpc in ready:
231 self.xmlrpc.handle_requests()
232
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500233 if not seendata and hasattr(self.cooker, "data"):
234 heartbeat_event = self.cooker.data.getVar('BB_HEARTBEAT_EVENT')
235 if heartbeat_event:
236 try:
237 self.heartbeat_seconds = float(heartbeat_event)
238 except:
239 bb.warn('Ignoring invalid BB_HEARTBEAT_EVENT=%s, must be a float specifying seconds.' % heartbeat_event)
240
241 self.timeout = self.server_timeout or self.cooker.data.getVar('BB_SERVER_TIMEOUT')
242 try:
243 if self.timeout:
244 self.timeout = float(self.timeout)
245 except:
246 bb.warn('Ignoring invalid BB_SERVER_TIMEOUT=%s, must be a float specifying seconds.' % self.timeout)
247 seendata = True
248
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500249 ready = self.idle_commands(.1, fds)
250
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500251 serverlog("Exiting")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500252 # Remove the socket file so we don't get any more connections to avoid races
Patrick Williams6ad2fb62023-06-15 12:50:14 -0500253 # The build directory could have been renamed so if the file isn't the one we created
254 # we shouldn't delete it.
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500255 try:
Patrick Williams6ad2fb62023-06-15 12:50:14 -0500256 sockinode = os.stat(self.sockname)[stat.ST_INO]
257 if sockinode == self.sockinode:
258 os.unlink(self.sockname)
259 else:
260 serverlog("bitbake.sock inode mismatch (%s vs %s), not deleting." % (sockinode, self.sockinode))
261 except Exception as err:
262 serverlog("Removing socket file '%s' failed (%s)" % (self.sockname, err))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500263 self.sock.close()
264
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500265 try:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500266 self.cooker.shutdown(True)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400267 self.cooker.notifier.stop()
268 self.cooker.confignotifier.stop()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500269 except:
270 pass
271
272 self.cooker.post_serve()
273
Andrew Geissler9aee5002022-03-30 16:27:02 +0000274 if len(threading.enumerate()) != 1:
275 serverlog("More than one thread left?: " + str(threading.enumerate()))
276
Andrew Geissler635e0e42020-08-21 15:58:33 -0500277 # Flush logs before we release the lock
278 sys.stdout.flush()
279 sys.stderr.flush()
280
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500281 # Finally release the lockfile but warn about other processes holding it open
282 lock = self.bitbake_lock
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500283 lockfile = self.bitbake_lock_name
284
285 def get_lock_contents(lockfile):
286 try:
287 with open(lockfile, "r") as f:
288 return f.readlines()
289 except FileNotFoundError:
290 return None
291
292 lockcontents = get_lock_contents(lockfile)
293 serverlog("Original lockfile contents: " + str(lockcontents))
294
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500295 lock.close()
296 lock = None
297
298 while not lock:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500299 i = 0
300 lock = None
301 while not lock and i < 30:
302 lock = bb.utils.lockfile(lockfile, shared=False, retry=False, block=False)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500303 if not lock:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500304 newlockcontents = get_lock_contents(lockfile)
305 if newlockcontents != lockcontents:
306 # A new server was started, the lockfile contents changed, we can exit
307 serverlog("Lockfile now contains different contents, exiting: " + str(newlockcontents))
308 return
309 time.sleep(0.1)
310 i += 1
311 if lock:
312 # We hold the lock so we can remove the file (hide stale pid data)
313 # via unlockfile.
314 bb.utils.unlockfile(lock)
315 serverlog("Exiting as we could obtain the lock")
316 return
317
318 if not lock:
319 # Some systems may not have lsof available
320 procs = None
321 try:
322 procs = subprocess.check_output(["lsof", '-w', lockfile], stderr=subprocess.STDOUT)
323 except subprocess.CalledProcessError:
324 # File was deleted?
325 continue
326 except OSError as e:
327 if e.errno != errno.ENOENT:
328 raise
329 if procs is None:
330 # Fall back to fuser if lsof is unavailable
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500331 try:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500332 procs = subprocess.check_output(["fuser", '-v', lockfile], stderr=subprocess.STDOUT)
333 except subprocess.CalledProcessError:
334 # File was deleted?
335 continue
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500336 except OSError as e:
337 if e.errno != errno.ENOENT:
338 raise
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500339
Andrew Geissler595f6302022-01-24 19:11:47 +0000340 msg = ["Delaying shutdown due to active processes which appear to be holding bitbake.lock"]
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500341 if procs:
Andrew Geissler595f6302022-01-24 19:11:47 +0000342 msg.append(":\n%s" % str(procs.decode("utf-8")))
343 serverlog("".join(msg))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500344
345 def idle_commands(self, delay, fds=None):
346 nextsleep = delay
347 if not fds:
348 fds = []
349
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600350 for function, data in list(self._idlefuns.items()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500351 try:
352 retval = function(self, data, False)
353 if retval is False:
354 del self._idlefuns[function]
355 nextsleep = None
356 elif retval is True:
357 nextsleep = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600358 elif isinstance(retval, float) and nextsleep:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500359 if (retval < nextsleep):
360 nextsleep = retval
361 elif nextsleep is None:
362 continue
363 else:
364 fds = fds + retval
365 except SystemExit:
366 raise
367 except Exception as exc:
368 if not isinstance(exc, bb.BBHandledException):
369 logger.exception('Running idle function')
370 del self._idlefuns[function]
371 self.quit = True
372
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500373 # Create new heartbeat event?
374 now = time.time()
375 if now >= self.next_heartbeat:
376 # We might have missed heartbeats. Just trigger once in
377 # that case and continue after the usual delay.
378 self.next_heartbeat += self.heartbeat_seconds
379 if self.next_heartbeat <= now:
380 self.next_heartbeat = now + self.heartbeat_seconds
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500381 if hasattr(self.cooker, "data"):
382 heartbeat = bb.event.HeartbeatEvent(now)
William A. Kennington IIIac69b482021-06-02 12:28:27 -0700383 try:
384 bb.event.fire(heartbeat, self.cooker.data)
385 except Exception as exc:
386 if not isinstance(exc, bb.BBHandledException):
387 logger.exception('Running heartbeat function')
388 self.quit = True
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500389 if nextsleep and now + nextsleep > self.next_heartbeat:
390 # Shorten timeout so that we we wake up in time for
391 # the heartbeat.
392 nextsleep = self.next_heartbeat - now
393
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500394 if nextsleep is not None:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500395 if self.xmlrpc:
396 nextsleep = self.xmlrpc.get_timeout(nextsleep)
397 try:
398 return select.select(fds,[],[],nextsleep)[0]
399 except InterruptedError:
400 # Ignore EINTR
401 return []
402 else:
403 return select.select(fds,[],[],0)[0]
404
405
406class ServerCommunicator():
407 def __init__(self, connection, recv):
408 self.connection = connection
409 self.recv = recv
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500410
411 def runCommand(self, command):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500412 self.connection.send(command)
413 if not self.recv.poll(30):
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500414 logger.info("No reply from server in 30s")
Andrew Geissler475cb722020-07-10 16:00:51 -0500415 if not self.recv.poll(30):
416 raise ProcessTimeout("Timeout while waiting for a reply from the bitbake server (60s)")
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500417 ret, exc = self.recv.get()
418 # Should probably turn all exceptions in exc back into exceptions?
419 # For now, at least handle BBHandledException
420 if exc and ("BBHandledException" in exc or "SystemExit" in exc):
421 raise bb.BBHandledException()
422 return ret, exc
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500423
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500424 def updateFeatureSet(self, featureset):
425 _, error = self.runCommand(["setFeatures", featureset])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500426 if error:
427 logger.error("Unable to set the cooker to the correct featureset: %s" % error)
428 raise BaseException(error)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500429
430 def getEventHandle(self):
431 handle, error = self.runCommand(["getUIHandlerNum"])
432 if error:
433 logger.error("Unable to get UI Handler Number: %s" % error)
434 raise BaseException(error)
435
436 return handle
437
438 def terminateServer(self):
439 self.connection.send(['terminateServer'])
440 return
441
442class BitBakeProcessServerConnection(object):
443 def __init__(self, ui_channel, recv, eq, sock):
444 self.connection = ServerCommunicator(ui_channel, recv)
445 self.events = eq
446 # Save sock so it doesn't get gc'd for the life of our connection
447 self.socket_connection = sock
448
449 def terminate(self):
Andrew Geissler78b72792022-06-14 06:47:25 -0500450 self.events.close()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500451 self.socket_connection.close()
452 self.connection.connection.close()
453 self.connection.recv.close()
454 return
455
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500456start_log_format = '--- Starting bitbake server pid %s at %s ---'
457start_log_datetime_format = '%Y-%m-%d %H:%M:%S.%f'
458
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500459class BitBakeServer(object):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500460
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500461 def __init__(self, lock, sockname, featureset, server_timeout, xmlrpcinterface):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500462
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500463 self.server_timeout = server_timeout
464 self.xmlrpcinterface = xmlrpcinterface
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500465 self.featureset = featureset
466 self.sockname = sockname
467 self.bitbake_lock = lock
468 self.readypipe, self.readypipein = os.pipe()
469
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800470 # Place the log in the builddirectory alongside the lock file
471 logfile = os.path.join(os.path.dirname(self.bitbake_lock.name), "bitbake-cookerdaemon.log")
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500472 self.logfile = logfile
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800473
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500474 startdatetime = datetime.datetime.now()
475 bb.daemonize.createDaemon(self._startServer, logfile)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500476 self.bitbake_lock.close()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800477 os.close(self.readypipein)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500478
479 ready = ConnectionReader(self.readypipe)
Brad Bishopf058f492019-01-28 23:50:33 -0500480 r = ready.poll(5)
481 if not r:
482 bb.note("Bitbake server didn't start within 5 seconds, waiting for 90")
483 r = ready.poll(90)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500484 if r:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800485 try:
486 r = ready.get()
487 except EOFError:
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500488 # Trap the child exiting/closing the pipe and error out
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800489 r = None
Brad Bishopf058f492019-01-28 23:50:33 -0500490 if not r or r[0] != "r":
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500491 ready.close()
Brad Bishopf058f492019-01-28 23:50:33 -0500492 bb.error("Unable to start bitbake server (%s)" % str(r))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500493 if os.path.exists(logfile):
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500494 logstart_re = re.compile(start_log_format % ('([0-9]+)', '([0-9-]+ [0-9:.]+)'))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500495 started = False
496 lines = []
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300497 lastlines = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500498 with open(logfile, "r") as f:
499 for line in f:
500 if started:
501 lines.append(line)
502 else:
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300503 lastlines.append(line)
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500504 res = logstart_re.search(line.rstrip())
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500505 if res:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500506 ldatetime = datetime.datetime.strptime(res.group(2), start_log_datetime_format)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500507 if ldatetime >= startdatetime:
508 started = True
509 lines.append(line)
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300510 if len(lastlines) > 60:
511 lastlines = lastlines[-60:]
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500512 if lines:
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300513 if len(lines) > 60:
514 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 -0500515 else:
516 bb.error("Server log for this session (%s):\n%s" % (logfile, "".join(lines)))
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300517 elif lastlines:
518 bb.error("Server didn't start, last 60 loglines (%s):\n%s" % (logfile, "".join(lastlines)))
519 else:
520 bb.error("%s doesn't exist" % logfile)
521
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500522 raise SystemExit(1)
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300523
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500524 ready.close()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500525
526 def _startServer(self):
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500527 os.close(self.readypipe)
528 os.set_inheritable(self.bitbake_lock.fileno(), True)
529 os.set_inheritable(self.readypipein, True)
530 serverscript = os.path.realpath(os.path.dirname(__file__) + "/../../../bin/bitbake-server")
Andrew Geisslerc926e172021-05-07 16:11:35 -0500531 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 +1300532
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500533def execServer(lockfd, readypipeinfd, lockname, sockname, server_timeout, xmlrpcinterface):
534
535 import bb.cookerdata
536 import bb.cooker
537
538 serverlog(start_log_format % (os.getpid(), datetime.datetime.now().strftime(start_log_datetime_format)))
539
540 try:
541 bitbake_lock = os.fdopen(lockfd, "w")
542
543 # Create server control socket
544 if os.path.exists(sockname):
Patrick Williams6ad2fb62023-06-15 12:50:14 -0500545 serverlog("WARNING: removing existing socket file '%s'" % sockname)
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500546 os.unlink(sockname)
547
548 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
549 # AF_UNIX has path length issues so chdir here to workaround
550 cwd = os.getcwd()
Brad Bishop08902b02019-08-20 09:16:51 -0400551 try:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500552 os.chdir(os.path.dirname(sockname))
553 sock.bind(os.path.basename(sockname))
Andrew Geissler635e0e42020-08-21 15:58:33 -0500554 finally:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500555 os.chdir(cwd)
556 sock.listen(1)
557
558 server = ProcessServer(bitbake_lock, lockname, sock, sockname, server_timeout, xmlrpcinterface)
559 writer = ConnectionWriter(readypipeinfd)
560 try:
561 featureset = []
562 cooker = bb.cooker.BBCooker(featureset, server.register_idle_function)
563 except bb.BBHandledException:
564 return None
565 writer.send("r")
566 writer.close()
567 server.cooker = cooker
568 serverlog("Started bitbake server pid %d" % os.getpid())
569
570 server.run()
571 finally:
Andrew Geissler9aee5002022-03-30 16:27:02 +0000572 # Flush any messages/errors to the logfile before exit
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500573 sys.stdout.flush()
574 sys.stderr.flush()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500575
576def connectProcessServer(sockname, featureset):
577 # Connect to socket
578 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
579 # AF_UNIX has path length issues so chdir here to workaround
580 cwd = os.getcwd()
581
Brad Bishopf058f492019-01-28 23:50:33 -0500582 readfd = writefd = readfd1 = writefd1 = readfd2 = writefd2 = None
583 eq = command_chan_recv = command_chan = None
584
585 sock.settimeout(10)
586
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500587 try:
Brad Bishope2d5b612018-11-23 10:55:50 +1300588 try:
589 os.chdir(os.path.dirname(sockname))
Brad Bishopf058f492019-01-28 23:50:33 -0500590 finished = False
591 while not finished:
592 try:
593 sock.connect(os.path.basename(sockname))
594 finished = True
595 except IOError as e:
596 if e.errno == errno.EWOULDBLOCK:
597 pass
Richard Purdie3da11142019-02-05 21:34:37 +0000598 raise
Brad Bishope2d5b612018-11-23 10:55:50 +1300599 finally:
600 os.chdir(cwd)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500601
602 # Send an fd for the remote to write events to
603 readfd, writefd = os.pipe()
604 eq = BBUIEventQueue(readfd)
605 # Send an fd for the remote to recieve commands from
606 readfd1, writefd1 = os.pipe()
607 command_chan = ConnectionWriter(writefd1)
608 # Send an fd for the remote to write commands results to
609 readfd2, writefd2 = os.pipe()
610 command_chan_recv = ConnectionReader(readfd2)
611
612 sendfds(sock, [writefd, readfd1, writefd2])
613
614 server_connection = BitBakeProcessServerConnection(command_chan, command_chan_recv, eq, sock)
615
616 # Close the ends of the pipes we won't use
617 for i in [writefd, readfd1, writefd2]:
618 os.close(i)
619
620 server_connection.connection.updateFeatureSet(featureset)
621
622 except (Exception, SystemExit) as e:
623 if command_chan_recv:
624 command_chan_recv.close()
625 if command_chan:
626 command_chan.close()
627 for i in [writefd, readfd1, writefd2]:
628 try:
Brad Bishope2d5b612018-11-23 10:55:50 +1300629 if i:
630 os.close(i)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500631 except OSError:
632 pass
633 sock.close()
634 raise
635
636 return server_connection
637
638def sendfds(sock, fds):
639 '''Send an array of fds over an AF_UNIX socket.'''
640 fds = array.array('i', fds)
641 msg = bytes([len(fds) % 256])
642 sock.sendmsg([msg], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)])
643
644def recvfds(sock, size):
645 '''Receive an array of fds over an AF_UNIX socket.'''
646 a = array.array('i')
647 bytes_size = a.itemsize * size
648 msg, ancdata, flags, addr = sock.recvmsg(1, socket.CMSG_LEN(bytes_size))
649 if not msg and not ancdata:
650 raise EOFError
651 try:
652 if len(ancdata) != 1:
653 raise RuntimeError('received %d items of ancdata' %
654 len(ancdata))
655 cmsg_level, cmsg_type, cmsg_data = ancdata[0]
656 if (cmsg_level == socket.SOL_SOCKET and
657 cmsg_type == socket.SCM_RIGHTS):
658 if len(cmsg_data) % a.itemsize != 0:
659 raise ValueError
660 a.frombytes(cmsg_data)
661 assert len(a) % 256 == msg[0]
662 return list(a)
663 except (ValueError, IndexError):
664 pass
665 raise RuntimeError('Invalid data received')
666
667class BBUIEventQueue:
668 def __init__(self, readfd):
669
670 self.eventQueue = []
671 self.eventQueueLock = threading.Lock()
672 self.eventQueueNotify = threading.Event()
673
674 self.reader = ConnectionReader(readfd)
675
676 self.t = threading.Thread()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500677 self.t.run = self.startCallbackHandler
678 self.t.start()
679
680 def getEvent(self):
Andrew Geissler78b72792022-06-14 06:47:25 -0500681 with self.eventQueueLock:
682 if len(self.eventQueue) == 0:
683 return None
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500684
Andrew Geissler78b72792022-06-14 06:47:25 -0500685 item = self.eventQueue.pop(0)
686 if len(self.eventQueue) == 0:
687 self.eventQueueNotify.clear()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500688
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500689 return item
690
691 def waitEvent(self, delay):
692 self.eventQueueNotify.wait(delay)
693 return self.getEvent()
694
695 def queue_event(self, event):
Andrew Geissler78b72792022-06-14 06:47:25 -0500696 with self.eventQueueLock:
697 self.eventQueue.append(event)
698 self.eventQueueNotify.set()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500699
700 def send_event(self, event):
701 self.queue_event(pickle.loads(event))
702
703 def startCallbackHandler(self):
704 bb.utils.set_process_name("UIEventQueue")
705 while True:
706 try:
Andrew Geissler78b72792022-06-14 06:47:25 -0500707 ready = self.reader.wait(0.25)
708 if ready:
709 event = self.reader.get()
710 self.queue_event(event)
711 except (EOFError, OSError, TypeError):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500712 # Easiest way to exit is to close the file descriptor to cause an exit
713 break
Andrew Geissler78b72792022-06-14 06:47:25 -0500714
715 def close(self):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500716 self.reader.close()
Andrew Geissler78b72792022-06-14 06:47:25 -0500717 self.t.join()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500718
719class ConnectionReader(object):
720
721 def __init__(self, fd):
722 self.reader = multiprocessing.connection.Connection(fd, writable=False)
723 self.rlock = multiprocessing.Lock()
724
725 def wait(self, timeout=None):
726 return multiprocessing.connection.wait([self.reader], timeout)
727
728 def poll(self, timeout=None):
729 return self.reader.poll(timeout)
730
731 def get(self):
732 with self.rlock:
733 res = self.reader.recv_bytes()
734 return multiprocessing.reduction.ForkingPickler.loads(res)
735
736 def fileno(self):
737 return self.reader.fileno()
738
739 def close(self):
740 return self.reader.close()
741
742
743class ConnectionWriter(object):
744
745 def __init__(self, fd):
746 self.writer = multiprocessing.connection.Connection(fd, readable=False)
747 self.wlock = multiprocessing.Lock()
748 # Why bb.event needs this I have no idea
749 self.event = self
750
Andrew Geissler9aee5002022-03-30 16:27:02 +0000751 def _send(self, obj):
Patrick Williamsde0582f2022-04-08 10:23:27 -0500752 gc.disable()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500753 with self.wlock:
754 self.writer.send_bytes(obj)
Patrick Williamsde0582f2022-04-08 10:23:27 -0500755 gc.enable()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500756
Andrew Geissler9aee5002022-03-30 16:27:02 +0000757 def send(self, obj):
758 obj = multiprocessing.reduction.ForkingPickler.dumps(obj)
759 # See notes/code in CookerParser
760 # We must not terminate holding this lock else processes will hang.
761 # For SIGTERM, raising afterwards avoids this.
762 # For SIGINT, we don't want to have written partial data to the pipe.
763 # pthread_sigmask block/unblock would be nice but doesn't work, https://bugs.python.org/issue47139
764 process = multiprocessing.current_process()
765 if process and hasattr(process, "queue_signals"):
766 with process.signal_threadlock:
767 process.queue_signals = True
768 self._send(obj)
769 process.queue_signals = False
Andrew Geissler615f2f12022-07-15 14:00:58 -0500770 try:
771 for sig in process.signal_received.pop():
772 process.handle_sig(sig, None)
773 except IndexError:
774 pass
Andrew Geissler9aee5002022-03-30 16:27:02 +0000775 else:
776 self._send(obj)
777
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500778 def fileno(self):
779 return self.writer.fileno()
780
781 def close(self):
782 return self.writer.close()