blob: 40cb99bc97cf707c97e97f045647ab4952960945 [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
Andrew Geissler517393d2023-01-13 08:55:19 -060031import 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 Geissler5082cc72023-09-11 08:41:39 -040041def currenttime():
42 return datetime.datetime.now().strftime('%H:%M:%S.%f')
43
Andrew Geisslerc9f78652020-09-18 14:11:35 -050044def serverlog(msg):
Andrew Geissler5082cc72023-09-11 08:41:39 -040045 print(str(os.getpid()) + " " + currenttime() + " " + msg)
Andrew Geisslerc9f78652020-09-18 14:11:35 -050046 sys.stdout.flush()
47
Andrew Geissler517393d2023-01-13 08:55:19 -060048#
49# When we have lockfile issues, try and find infomation about which process is
50# using the lockfile
51#
52def get_lockfile_process_msg(lockfile):
53 # Some systems may not have lsof available
54 procs = None
55 try:
56 procs = subprocess.check_output(["lsof", '-w', lockfile], stderr=subprocess.STDOUT)
57 except subprocess.CalledProcessError:
58 # File was deleted?
59 pass
60 except OSError as e:
61 if e.errno != errno.ENOENT:
62 raise
63 if procs is None:
64 # Fall back to fuser if lsof is unavailable
65 try:
66 procs = subprocess.check_output(["fuser", '-v', lockfile], stderr=subprocess.STDOUT)
67 except subprocess.CalledProcessError:
68 # File was deleted?
69 pass
70 except OSError as e:
71 if e.errno != errno.ENOENT:
72 raise
73 if procs:
74 return procs.decode("utf-8")
75 return None
76
77class idleFinish():
78 def __init__(self, msg):
79 self.msg = msg
80
Andrew Geissler635e0e42020-08-21 15:58:33 -050081class ProcessServer():
Patrick Williamsc124f4f2015-09-15 14:41:29 -050082 profile_filename = "profile.log"
83 profile_processed_filename = "profile.log.processed"
84
Andrew Geisslerc9f78652020-09-18 14:11:35 -050085 def __init__(self, lock, lockname, sock, sockname, server_timeout, xmlrpcinterface):
Brad Bishopd7bf8c12018-02-25 22:55:05 -050086 self.command_channel = False
87 self.command_channel_reply = False
Patrick Williamsc124f4f2015-09-15 14:41:29 -050088 self.quit = False
Brad Bishop6e60e8b2018-02-01 10:27:11 -050089 self.heartbeat_seconds = 1 # default, BB_HEARTBEAT_EVENT will be checked once we have a datastore.
90 self.next_heartbeat = time.time()
Patrick Williamsc124f4f2015-09-15 14:41:29 -050091
Brad Bishopd7bf8c12018-02-25 22:55:05 -050092 self.event_handle = None
Andrew Geissler635e0e42020-08-21 15:58:33 -050093 self.hadanyui = False
Brad Bishopd7bf8c12018-02-25 22:55:05 -050094 self.haveui = False
Andrew Geisslerb7d28612020-07-24 16:15:54 -050095 self.maxuiwait = 30
Brad Bishopd7bf8c12018-02-25 22:55:05 -050096 self.xmlrpc = False
97
Andrew Geissler517393d2023-01-13 08:55:19 -060098 self.idle = None
99 # Need a lock for _idlefuns changes
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500100 self._idlefuns = {}
Andrew Geissler517393d2023-01-13 08:55:19 -0600101 self._idlefuncsLock = threading.Lock()
102 self.idle_cond = threading.Condition(self._idlefuncsLock)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500103
104 self.bitbake_lock = lock
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500105 self.bitbake_lock_name = lockname
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500106 self.sock = sock
107 self.sockname = sockname
Andrew Geissler517393d2023-01-13 08:55:19 -0600108 # It is possible the directory may be renamed. Cache the inode of the socket file
109 # so we can tell if things changed.
110 self.sockinode = os.stat(self.sockname)[stat.ST_INO]
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500111
Andrew Geissler635e0e42020-08-21 15:58:33 -0500112 self.server_timeout = server_timeout
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500113 self.timeout = self.server_timeout
Andrew Geissler635e0e42020-08-21 15:58:33 -0500114 self.xmlrpcinterface = xmlrpcinterface
115
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500116 def register_idle_function(self, function, data):
117 """Register a function to be called while the server is idle"""
118 assert hasattr(function, '__call__')
Andrew Geissler517393d2023-01-13 08:55:19 -0600119 with bb.utils.lock_timeout(self._idlefuncsLock):
120 self._idlefuns[function] = data
121 serverlog("Registering idle function %s" % str(function))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500122
123 def run(self):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500124
125 if self.xmlrpcinterface[0]:
126 self.xmlrpc = bb.server.xmlrpcserver.BitBakeXMLRPCServer(self.xmlrpcinterface, self.cooker, self)
127
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500128 serverlog("Bitbake XMLRPC server address: %s, server port: %s" % (self.xmlrpc.host, self.xmlrpc.port))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500129
130 try:
131 self.bitbake_lock.seek(0)
132 self.bitbake_lock.truncate()
133 if self.xmlrpc:
134 self.bitbake_lock.write("%s %s:%s\n" % (os.getpid(), self.xmlrpc.host, self.xmlrpc.port))
135 else:
136 self.bitbake_lock.write("%s\n" % (os.getpid()))
137 self.bitbake_lock.flush()
138 except Exception as e:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500139 serverlog("Error writing to lock file: %s" % str(e))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500140 pass
141
142 if self.cooker.configuration.profile:
143 try:
144 import cProfile as profile
145 except:
146 import profile
147 prof = profile.Profile()
148
149 ret = profile.Profile.runcall(prof, self.main)
150
151 prof.dump_stats("profile.log")
152 bb.utils.process_profilelog("profile.log")
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500153 serverlog("Raw profiling information saved to profile.log and processed statistics to profile.log.processed")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500154
155 else:
156 ret = self.main()
157
158 return ret
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500159
Andrew Geissler517393d2023-01-13 08:55:19 -0600160 def _idle_check(self):
161 return len(self._idlefuns) == 0 and self.cooker.command.currentAsyncCommand is None
162
163 def wait_for_idle(self, timeout=30):
164 # Wait for the idle loop to have cleared
165 with bb.utils.lock_timeout(self._idlefuncsLock):
166 return self.idle_cond.wait_for(self._idle_check, timeout) is not False
167
168 def set_async_cmd(self, cmd):
169 with bb.utils.lock_timeout(self._idlefuncsLock):
170 ret = self.idle_cond.wait_for(self._idle_check, 30)
171 if ret is False:
172 return False
173 self.cooker.command.currentAsyncCommand = cmd
174 return True
175
176 def clear_async_cmd(self):
177 with bb.utils.lock_timeout(self._idlefuncsLock):
178 self.cooker.command.currentAsyncCommand = None
179 self.idle_cond.notify_all()
180
181 def get_async_cmd(self):
182 with bb.utils.lock_timeout(self._idlefuncsLock):
183 return self.cooker.command.currentAsyncCommand
184
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500185 def main(self):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500186 self.cooker.pre_serve()
187
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500188 bb.utils.set_process_name("Cooker")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500189
190 ready = []
Brad Bishopf058f492019-01-28 23:50:33 -0500191 newconnections = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500192
193 self.controllersock = False
194 fds = [self.sock]
195 if self.xmlrpc:
196 fds.append(self.xmlrpc)
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500197 seendata = False
198 serverlog("Entering server connection loop")
Andrew Geissler517393d2023-01-13 08:55:19 -0600199 serverlog("Lockfile is: %s\nSocket is %s (%s)" % (self.bitbake_lock_name, self.sockname, os.path.exists(self.sockname)))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500200
201 def disconnect_client(self, fds):
Andrew Geissler517393d2023-01-13 08:55:19 -0600202 serverlog("Disconnecting Client (socket: %s)" % os.path.exists(self.sockname))
Brad Bishopf058f492019-01-28 23:50:33 -0500203 if self.controllersock:
204 fds.remove(self.controllersock)
205 self.controllersock.close()
206 self.controllersock = False
207 if self.haveui:
Andrew Geissler517393d2023-01-13 08:55:19 -0600208 # Wait for the idle loop to have cleared (30s max)
209 if not self.wait_for_idle(30):
210 serverlog("Idle loop didn't finish queued commands after 30s, exiting.")
211 self.quit = True
Brad Bishopf058f492019-01-28 23:50:33 -0500212 fds.remove(self.command_channel)
213 bb.event.unregister_UIHhandler(self.event_handle, True)
214 self.command_channel_reply.writer.close()
215 self.event_writer.writer.close()
216 self.command_channel.close()
217 self.command_channel = False
218 del self.event_writer
219 self.lastui = time.time()
220 self.cooker.clientComplete()
221 self.haveui = False
222 ready = select.select(fds,[],[],0)[0]
Andrew Geissler517393d2023-01-13 08:55:19 -0600223 if newconnections and not self.quit:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500224 serverlog("Starting new client")
Brad Bishopf058f492019-01-28 23:50:33 -0500225 conn = newconnections.pop(-1)
226 fds.append(conn)
227 self.controllersock = conn
Andrew Geissler5f350902021-07-23 13:09:54 -0400228 elif not self.timeout and not ready:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500229 serverlog("No timeout, exiting.")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500230 self.quit = True
231
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500232 self.lastui = time.time()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500233 while not self.quit:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500234 if self.sock in ready:
Brad Bishopf058f492019-01-28 23:50:33 -0500235 while select.select([self.sock],[],[],0)[0]:
236 controllersock, address = self.sock.accept()
237 if self.controllersock:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500238 serverlog("Queuing %s (%s)" % (str(ready), str(newconnections)))
Brad Bishopf058f492019-01-28 23:50:33 -0500239 newconnections.append(controllersock)
240 else:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500241 serverlog("Accepting %s (%s)" % (str(ready), str(newconnections)))
Brad Bishopf058f492019-01-28 23:50:33 -0500242 self.controllersock = controllersock
243 fds.append(controllersock)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500244 if self.controllersock in ready:
245 try:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500246 serverlog("Processing Client")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500247 ui_fds = recvfds(self.controllersock, 3)
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500248 serverlog("Connecting Client")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500249
250 # Where to write events to
251 writer = ConnectionWriter(ui_fds[0])
252 self.event_handle = bb.event.register_UIHhandler(writer, True)
253 self.event_writer = writer
254
255 # Where to read commands from
256 reader = ConnectionReader(ui_fds[1])
257 fds.append(reader)
258 self.command_channel = reader
259
260 # Where to send command return values to
261 writer = ConnectionWriter(ui_fds[2])
262 self.command_channel_reply = writer
263
264 self.haveui = True
Andrew Geissler635e0e42020-08-21 15:58:33 -0500265 self.hadanyui = True
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500266
267 except (EOFError, OSError):
268 disconnect_client(self, fds)
269
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500270 if not self.timeout == -1.0 and not self.haveui and self.timeout and \
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500271 (self.lastui + self.timeout) < time.time():
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500272 serverlog("Server timeout, exiting.")
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500273 self.quit = True
274
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500275 # If we don't see a UI connection within maxuiwait, its unlikely we're going to see
276 # one. We have had issue with processes hanging indefinitely so timing out UI-less
277 # servers is useful.
Andrew Geissler635e0e42020-08-21 15:58:33 -0500278 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 -0500279 serverlog("No UI connection within max timeout, exiting to avoid infinite loop.")
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500280 self.quit = True
281
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500282 if self.command_channel in ready:
283 try:
284 command = self.command_channel.get()
285 except EOFError:
286 # Client connection shutting down
287 ready = []
288 disconnect_client(self, fds)
289 continue
290 if command[0] == "terminateServer":
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500291 self.quit = True
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500292 continue
293 try:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500294 serverlog("Running command %s" % command)
Andrew Geissler5082cc72023-09-11 08:41:39 -0400295 reply = self.cooker.command.runCommand(command, self)
296 serverlog("Sending reply %s" % repr(reply))
297 self.command_channel_reply.send(reply)
Andrew Geissler517393d2023-01-13 08:55:19 -0600298 serverlog("Command Completed (socket: %s)" % os.path.exists(self.sockname))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500299 except Exception as e:
Patrick Williams213cb262021-08-07 19:21:33 -0500300 stack = traceback.format_exc()
301 serverlog('Exception in server main event loop running command %s (%s)' % (command, stack))
302 logger.exception('Exception in server main event loop running command %s (%s)' % (command, stack))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500303
304 if self.xmlrpc in ready:
305 self.xmlrpc.handle_requests()
306
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500307 if not seendata and hasattr(self.cooker, "data"):
308 heartbeat_event = self.cooker.data.getVar('BB_HEARTBEAT_EVENT')
309 if heartbeat_event:
310 try:
311 self.heartbeat_seconds = float(heartbeat_event)
312 except:
313 bb.warn('Ignoring invalid BB_HEARTBEAT_EVENT=%s, must be a float specifying seconds.' % heartbeat_event)
314
315 self.timeout = self.server_timeout or self.cooker.data.getVar('BB_SERVER_TIMEOUT')
316 try:
317 if self.timeout:
318 self.timeout = float(self.timeout)
319 except:
320 bb.warn('Ignoring invalid BB_SERVER_TIMEOUT=%s, must be a float specifying seconds.' % self.timeout)
321 seendata = True
322
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500323 ready = self.idle_commands(.1, fds)
324
Andrew Geissler517393d2023-01-13 08:55:19 -0600325 if self.idle:
326 self.idle.join()
327
328 serverlog("Exiting (socket: %s)" % os.path.exists(self.sockname))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500329 # Remove the socket file so we don't get any more connections to avoid races
Andrew Geissler517393d2023-01-13 08:55:19 -0600330 # The build directory could have been renamed so if the file isn't the one we created
331 # we shouldn't delete it.
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500332 try:
Andrew Geissler517393d2023-01-13 08:55:19 -0600333 sockinode = os.stat(self.sockname)[stat.ST_INO]
334 if sockinode == self.sockinode:
335 os.unlink(self.sockname)
336 else:
337 serverlog("bitbake.sock inode mismatch (%s vs %s), not deleting." % (sockinode, self.sockinode))
338 except Exception as err:
339 serverlog("Removing socket file '%s' failed (%s)" % (self.sockname, err))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500340 self.sock.close()
341
Andrew Geisslerb7d28612020-07-24 16:15:54 -0500342 try:
Andrew Geissler517393d2023-01-13 08:55:19 -0600343 self.cooker.shutdown(True, idle=False)
Brad Bishop316dfdd2018-06-25 12:45:53 -0400344 self.cooker.notifier.stop()
345 self.cooker.confignotifier.stop()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500346 except:
347 pass
348
349 self.cooker.post_serve()
350
Andrew Geissler9aee5002022-03-30 16:27:02 +0000351 if len(threading.enumerate()) != 1:
352 serverlog("More than one thread left?: " + str(threading.enumerate()))
353
Andrew Geissler635e0e42020-08-21 15:58:33 -0500354 # Flush logs before we release the lock
355 sys.stdout.flush()
356 sys.stderr.flush()
357
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500358 # Finally release the lockfile but warn about other processes holding it open
359 lock = self.bitbake_lock
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500360 lockfile = self.bitbake_lock_name
361
362 def get_lock_contents(lockfile):
363 try:
364 with open(lockfile, "r") as f:
365 return f.readlines()
366 except FileNotFoundError:
367 return None
368
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500369 lock.close()
370 lock = None
371
372 while not lock:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500373 i = 0
374 lock = None
Andrew Geisslerc5535c92023-01-27 16:10:19 -0600375 if not os.path.exists(os.path.basename(lockfile)):
376 serverlog("Lockfile directory gone, exiting.")
377 return
378
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500379 while not lock and i < 30:
380 lock = bb.utils.lockfile(lockfile, shared=False, retry=False, block=False)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500381 if not lock:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500382 newlockcontents = get_lock_contents(lockfile)
Patrick Williamsb542dec2023-06-09 01:26:37 -0500383 if not newlockcontents[0].startswith([f"{os.getpid()}\n", f"{os.getpid()} "]):
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500384 # A new server was started, the lockfile contents changed, we can exit
385 serverlog("Lockfile now contains different contents, exiting: " + str(newlockcontents))
386 return
387 time.sleep(0.1)
388 i += 1
389 if lock:
390 # We hold the lock so we can remove the file (hide stale pid data)
391 # via unlockfile.
392 bb.utils.unlockfile(lock)
393 serverlog("Exiting as we could obtain the lock")
394 return
395
396 if not lock:
Andrew Geissler517393d2023-01-13 08:55:19 -0600397 procs = get_lockfile_process_msg(lockfile)
Andrew Geissler595f6302022-01-24 19:11:47 +0000398 msg = ["Delaying shutdown due to active processes which appear to be holding bitbake.lock"]
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500399 if procs:
Andrew Geissler517393d2023-01-13 08:55:19 -0600400 msg.append(":\n%s" % procs)
Andrew Geissler595f6302022-01-24 19:11:47 +0000401 serverlog("".join(msg))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500402
Andrew Geissler517393d2023-01-13 08:55:19 -0600403 def idle_thread(self):
404 def remove_idle_func(function):
405 with bb.utils.lock_timeout(self._idlefuncsLock):
406 del self._idlefuns[function]
407 self.idle_cond.notify_all()
408
409 while not self.quit:
410 nextsleep = 0.1
411 fds = []
412
Andrew Geissler6aa7eec2023-03-03 12:41:14 -0600413 try:
414 self.cooker.process_inotify_updates()
415 except Exception as exc:
416 serverlog("Exception %s in inofify updates broke the idle_thread, exiting" % traceback.format_exc())
417 self.quit = True
Andrew Geissler517393d2023-01-13 08:55:19 -0600418
419 with bb.utils.lock_timeout(self._idlefuncsLock):
420 items = list(self._idlefuns.items())
421
422 for function, data in items:
423 try:
424 retval = function(self, data, False)
425 if isinstance(retval, idleFinish):
426 serverlog("Removing idle function %s at idleFinish" % str(function))
427 remove_idle_func(function)
428 self.cooker.command.finishAsyncCommand(retval.msg)
429 nextsleep = None
430 elif retval is False:
431 serverlog("Removing idle function %s" % str(function))
432 remove_idle_func(function)
433 nextsleep = None
434 elif retval is True:
435 nextsleep = None
436 elif isinstance(retval, float) and nextsleep:
437 if (retval < nextsleep):
438 nextsleep = retval
439 elif nextsleep is None:
440 continue
441 else:
442 fds = fds + retval
443 except SystemExit:
444 raise
445 except Exception as exc:
446 if not isinstance(exc, bb.BBHandledException):
447 logger.exception('Running idle function')
448 remove_idle_func(function)
449 serverlog("Exception %s broke the idle_thread, exiting" % traceback.format_exc())
450 self.quit = True
451
452 # Create new heartbeat event?
453 now = time.time()
454 if bb.event._heartbeat_enabled and now >= self.next_heartbeat:
455 # We might have missed heartbeats. Just trigger once in
456 # that case and continue after the usual delay.
457 self.next_heartbeat += self.heartbeat_seconds
458 if self.next_heartbeat <= now:
459 self.next_heartbeat = now + self.heartbeat_seconds
460 if hasattr(self.cooker, "data"):
461 heartbeat = bb.event.HeartbeatEvent(now)
462 try:
463 bb.event.fire(heartbeat, self.cooker.data)
464 except Exception as exc:
465 if not isinstance(exc, bb.BBHandledException):
466 logger.exception('Running heartbeat function')
467 serverlog("Exception %s broke in idle_thread, exiting" % traceback.format_exc())
468 self.quit = True
469 if nextsleep and bb.event._heartbeat_enabled and now + nextsleep > self.next_heartbeat:
470 # Shorten timeout so that we we wake up in time for
471 # the heartbeat.
472 nextsleep = self.next_heartbeat - now
473
474 if nextsleep is not None:
475 select.select(fds,[],[],nextsleep)[0]
476
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500477 def idle_commands(self, delay, fds=None):
478 nextsleep = delay
479 if not fds:
480 fds = []
481
Andrew Geissler517393d2023-01-13 08:55:19 -0600482 if not self.idle:
483 self.idle = threading.Thread(target=self.idle_thread)
484 self.idle.start()
Andrew Geissler6aa7eec2023-03-03 12:41:14 -0600485 elif self.idle and not self.idle.is_alive():
486 serverlog("Idle thread terminated, main thread exiting too")
487 bb.error("Idle thread terminated, main thread exiting too")
488 self.quit = True
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500489
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500490 if nextsleep is not None:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500491 if self.xmlrpc:
492 nextsleep = self.xmlrpc.get_timeout(nextsleep)
493 try:
494 return select.select(fds,[],[],nextsleep)[0]
495 except InterruptedError:
496 # Ignore EINTR
497 return []
498 else:
499 return select.select(fds,[],[],0)[0]
500
501
502class ServerCommunicator():
503 def __init__(self, connection, recv):
504 self.connection = connection
505 self.recv = recv
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500506
507 def runCommand(self, command):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500508 self.connection.send(command)
509 if not self.recv.poll(30):
Andrew Geissler5082cc72023-09-11 08:41:39 -0400510 logger.info("No reply from server in 30s (for command %s at %s)" % (command[0], currenttime()))
Andrew Geissler475cb722020-07-10 16:00:51 -0500511 if not self.recv.poll(30):
Andrew Geissler5082cc72023-09-11 08:41:39 -0400512 raise ProcessTimeout("Timeout while waiting for a reply from the bitbake server (60s at %s)" % currenttime())
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500513 ret, exc = self.recv.get()
514 # Should probably turn all exceptions in exc back into exceptions?
515 # For now, at least handle BBHandledException
516 if exc and ("BBHandledException" in exc or "SystemExit" in exc):
517 raise bb.BBHandledException()
518 return ret, exc
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500519
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500520 def updateFeatureSet(self, featureset):
521 _, error = self.runCommand(["setFeatures", featureset])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500522 if error:
523 logger.error("Unable to set the cooker to the correct featureset: %s" % error)
524 raise BaseException(error)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500525
526 def getEventHandle(self):
527 handle, error = self.runCommand(["getUIHandlerNum"])
528 if error:
529 logger.error("Unable to get UI Handler Number: %s" % error)
530 raise BaseException(error)
531
532 return handle
533
534 def terminateServer(self):
535 self.connection.send(['terminateServer'])
536 return
537
538class BitBakeProcessServerConnection(object):
539 def __init__(self, ui_channel, recv, eq, sock):
540 self.connection = ServerCommunicator(ui_channel, recv)
541 self.events = eq
542 # Save sock so it doesn't get gc'd for the life of our connection
543 self.socket_connection = sock
544
545 def terminate(self):
Andrew Geissler78b72792022-06-14 06:47:25 -0500546 self.events.close()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500547 self.socket_connection.close()
548 self.connection.connection.close()
549 self.connection.recv.close()
550 return
551
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500552start_log_format = '--- Starting bitbake server pid %s at %s ---'
553start_log_datetime_format = '%Y-%m-%d %H:%M:%S.%f'
554
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500555class BitBakeServer(object):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500556
Andrew Geissler517393d2023-01-13 08:55:19 -0600557 def __init__(self, lock, sockname, featureset, server_timeout, xmlrpcinterface, profile):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500558
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500559 self.server_timeout = server_timeout
560 self.xmlrpcinterface = xmlrpcinterface
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500561 self.featureset = featureset
562 self.sockname = sockname
563 self.bitbake_lock = lock
Andrew Geissler517393d2023-01-13 08:55:19 -0600564 self.profile = profile
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500565 self.readypipe, self.readypipein = os.pipe()
566
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800567 # Place the log in the builddirectory alongside the lock file
568 logfile = os.path.join(os.path.dirname(self.bitbake_lock.name), "bitbake-cookerdaemon.log")
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500569 self.logfile = logfile
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800570
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500571 startdatetime = datetime.datetime.now()
572 bb.daemonize.createDaemon(self._startServer, logfile)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500573 self.bitbake_lock.close()
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800574 os.close(self.readypipein)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500575
576 ready = ConnectionReader(self.readypipe)
Brad Bishopf058f492019-01-28 23:50:33 -0500577 r = ready.poll(5)
578 if not r:
579 bb.note("Bitbake server didn't start within 5 seconds, waiting for 90")
580 r = ready.poll(90)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500581 if r:
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800582 try:
583 r = ready.get()
584 except EOFError:
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500585 # Trap the child exiting/closing the pipe and error out
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800586 r = None
Brad Bishopf058f492019-01-28 23:50:33 -0500587 if not r or r[0] != "r":
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500588 ready.close()
Brad Bishopf058f492019-01-28 23:50:33 -0500589 bb.error("Unable to start bitbake server (%s)" % str(r))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500590 if os.path.exists(logfile):
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500591 logstart_re = re.compile(start_log_format % ('([0-9]+)', '([0-9-]+ [0-9:.]+)'))
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500592 started = False
593 lines = []
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300594 lastlines = []
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500595 with open(logfile, "r") as f:
596 for line in f:
597 if started:
598 lines.append(line)
599 else:
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300600 lastlines.append(line)
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500601 res = logstart_re.search(line.rstrip())
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500602 if res:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500603 ldatetime = datetime.datetime.strptime(res.group(2), start_log_datetime_format)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500604 if ldatetime >= startdatetime:
605 started = True
606 lines.append(line)
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300607 if len(lastlines) > 60:
608 lastlines = lastlines[-60:]
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500609 if lines:
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300610 if len(lines) > 60:
611 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 -0500612 else:
613 bb.error("Server log for this session (%s):\n%s" % (logfile, "".join(lines)))
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300614 elif lastlines:
615 bb.error("Server didn't start, last 60 loglines (%s):\n%s" % (logfile, "".join(lastlines)))
616 else:
617 bb.error("%s doesn't exist" % logfile)
618
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500619 raise SystemExit(1)
Brad Bishopa5c52ff2018-11-23 10:55:50 +1300620
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500621 ready.close()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500622
623 def _startServer(self):
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500624 os.close(self.readypipe)
625 os.set_inheritable(self.bitbake_lock.fileno(), True)
626 os.set_inheritable(self.readypipein, True)
627 serverscript = os.path.realpath(os.path.dirname(__file__) + "/../../../bin/bitbake-server")
Andrew Geissler517393d2023-01-13 08:55:19 -0600628 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(int(self.profile)), str(self.xmlrpcinterface[0]), str(self.xmlrpcinterface[1]))
Brad Bishope2d5b612018-11-23 10:55:50 +1300629
Andrew Geissler517393d2023-01-13 08:55:19 -0600630def execServer(lockfd, readypipeinfd, lockname, sockname, server_timeout, xmlrpcinterface, profile):
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500631
632 import bb.cookerdata
633 import bb.cooker
634
635 serverlog(start_log_format % (os.getpid(), datetime.datetime.now().strftime(start_log_datetime_format)))
636
637 try:
638 bitbake_lock = os.fdopen(lockfd, "w")
639
640 # Create server control socket
641 if os.path.exists(sockname):
Andrew Geissler517393d2023-01-13 08:55:19 -0600642 serverlog("WARNING: removing existing socket file '%s'" % sockname)
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500643 os.unlink(sockname)
644
645 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
646 # AF_UNIX has path length issues so chdir here to workaround
647 cwd = os.getcwd()
Brad Bishop08902b02019-08-20 09:16:51 -0400648 try:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500649 os.chdir(os.path.dirname(sockname))
650 sock.bind(os.path.basename(sockname))
Andrew Geissler635e0e42020-08-21 15:58:33 -0500651 finally:
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500652 os.chdir(cwd)
653 sock.listen(1)
654
655 server = ProcessServer(bitbake_lock, lockname, sock, sockname, server_timeout, xmlrpcinterface)
656 writer = ConnectionWriter(readypipeinfd)
657 try:
658 featureset = []
Andrew Geissler517393d2023-01-13 08:55:19 -0600659 cooker = bb.cooker.BBCooker(featureset, server)
660 cooker.configuration.profile = profile
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500661 except bb.BBHandledException:
662 return None
663 writer.send("r")
664 writer.close()
665 server.cooker = cooker
666 serverlog("Started bitbake server pid %d" % os.getpid())
667
668 server.run()
669 finally:
Andrew Geissler9aee5002022-03-30 16:27:02 +0000670 # Flush any messages/errors to the logfile before exit
Andrew Geisslerc9f78652020-09-18 14:11:35 -0500671 sys.stdout.flush()
672 sys.stderr.flush()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500673
674def connectProcessServer(sockname, featureset):
675 # Connect to socket
676 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
677 # AF_UNIX has path length issues so chdir here to workaround
678 cwd = os.getcwd()
679
Brad Bishopf058f492019-01-28 23:50:33 -0500680 readfd = writefd = readfd1 = writefd1 = readfd2 = writefd2 = None
681 eq = command_chan_recv = command_chan = None
682
683 sock.settimeout(10)
684
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500685 try:
Brad Bishope2d5b612018-11-23 10:55:50 +1300686 try:
687 os.chdir(os.path.dirname(sockname))
Brad Bishopf058f492019-01-28 23:50:33 -0500688 finished = False
689 while not finished:
690 try:
691 sock.connect(os.path.basename(sockname))
692 finished = True
693 except IOError as e:
694 if e.errno == errno.EWOULDBLOCK:
695 pass
Richard Purdie3da11142019-02-05 21:34:37 +0000696 raise
Brad Bishope2d5b612018-11-23 10:55:50 +1300697 finally:
698 os.chdir(cwd)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500699
700 # Send an fd for the remote to write events to
701 readfd, writefd = os.pipe()
702 eq = BBUIEventQueue(readfd)
703 # Send an fd for the remote to recieve commands from
704 readfd1, writefd1 = os.pipe()
705 command_chan = ConnectionWriter(writefd1)
706 # Send an fd for the remote to write commands results to
707 readfd2, writefd2 = os.pipe()
708 command_chan_recv = ConnectionReader(readfd2)
709
710 sendfds(sock, [writefd, readfd1, writefd2])
711
712 server_connection = BitBakeProcessServerConnection(command_chan, command_chan_recv, eq, sock)
713
714 # Close the ends of the pipes we won't use
715 for i in [writefd, readfd1, writefd2]:
716 os.close(i)
717
718 server_connection.connection.updateFeatureSet(featureset)
719
720 except (Exception, SystemExit) as e:
721 if command_chan_recv:
722 command_chan_recv.close()
723 if command_chan:
724 command_chan.close()
725 for i in [writefd, readfd1, writefd2]:
726 try:
Brad Bishope2d5b612018-11-23 10:55:50 +1300727 if i:
728 os.close(i)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500729 except OSError:
730 pass
731 sock.close()
732 raise
733
734 return server_connection
735
736def sendfds(sock, fds):
737 '''Send an array of fds over an AF_UNIX socket.'''
738 fds = array.array('i', fds)
739 msg = bytes([len(fds) % 256])
740 sock.sendmsg([msg], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fds)])
741
742def recvfds(sock, size):
743 '''Receive an array of fds over an AF_UNIX socket.'''
744 a = array.array('i')
745 bytes_size = a.itemsize * size
746 msg, ancdata, flags, addr = sock.recvmsg(1, socket.CMSG_LEN(bytes_size))
747 if not msg and not ancdata:
748 raise EOFError
749 try:
750 if len(ancdata) != 1:
751 raise RuntimeError('received %d items of ancdata' %
752 len(ancdata))
753 cmsg_level, cmsg_type, cmsg_data = ancdata[0]
754 if (cmsg_level == socket.SOL_SOCKET and
755 cmsg_type == socket.SCM_RIGHTS):
756 if len(cmsg_data) % a.itemsize != 0:
757 raise ValueError
758 a.frombytes(cmsg_data)
759 assert len(a) % 256 == msg[0]
760 return list(a)
761 except (ValueError, IndexError):
762 pass
763 raise RuntimeError('Invalid data received')
764
765class BBUIEventQueue:
766 def __init__(self, readfd):
767
768 self.eventQueue = []
769 self.eventQueueLock = threading.Lock()
770 self.eventQueueNotify = threading.Event()
771
772 self.reader = ConnectionReader(readfd)
773
774 self.t = threading.Thread()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500775 self.t.run = self.startCallbackHandler
776 self.t.start()
777
778 def getEvent(self):
Andrew Geissler517393d2023-01-13 08:55:19 -0600779 with bb.utils.lock_timeout(self.eventQueueLock):
Andrew Geissler78b72792022-06-14 06:47:25 -0500780 if len(self.eventQueue) == 0:
781 return None
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500782
Andrew Geissler78b72792022-06-14 06:47:25 -0500783 item = self.eventQueue.pop(0)
784 if len(self.eventQueue) == 0:
785 self.eventQueueNotify.clear()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500786
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500787 return item
788
789 def waitEvent(self, delay):
790 self.eventQueueNotify.wait(delay)
791 return self.getEvent()
792
793 def queue_event(self, event):
Andrew Geissler517393d2023-01-13 08:55:19 -0600794 with bb.utils.lock_timeout(self.eventQueueLock):
Andrew Geissler78b72792022-06-14 06:47:25 -0500795 self.eventQueue.append(event)
796 self.eventQueueNotify.set()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500797
798 def send_event(self, event):
799 self.queue_event(pickle.loads(event))
800
801 def startCallbackHandler(self):
802 bb.utils.set_process_name("UIEventQueue")
803 while True:
804 try:
Andrew Geissler78b72792022-06-14 06:47:25 -0500805 ready = self.reader.wait(0.25)
806 if ready:
807 event = self.reader.get()
808 self.queue_event(event)
809 except (EOFError, OSError, TypeError):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500810 # Easiest way to exit is to close the file descriptor to cause an exit
811 break
Andrew Geissler78b72792022-06-14 06:47:25 -0500812
813 def close(self):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500814 self.reader.close()
Andrew Geissler78b72792022-06-14 06:47:25 -0500815 self.t.join()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500816
817class ConnectionReader(object):
818
819 def __init__(self, fd):
820 self.reader = multiprocessing.connection.Connection(fd, writable=False)
821 self.rlock = multiprocessing.Lock()
822
823 def wait(self, timeout=None):
824 return multiprocessing.connection.wait([self.reader], timeout)
825
826 def poll(self, timeout=None):
827 return self.reader.poll(timeout)
828
829 def get(self):
Andrew Geissler517393d2023-01-13 08:55:19 -0600830 with bb.utils.lock_timeout(self.rlock):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500831 res = self.reader.recv_bytes()
832 return multiprocessing.reduction.ForkingPickler.loads(res)
833
834 def fileno(self):
835 return self.reader.fileno()
836
837 def close(self):
838 return self.reader.close()
839
840
841class ConnectionWriter(object):
842
843 def __init__(self, fd):
844 self.writer = multiprocessing.connection.Connection(fd, readable=False)
845 self.wlock = multiprocessing.Lock()
846 # Why bb.event needs this I have no idea
847 self.event = self
848
Andrew Geissler9aee5002022-03-30 16:27:02 +0000849 def _send(self, obj):
Patrick Williamsde0582f2022-04-08 10:23:27 -0500850 gc.disable()
Andrew Geissler517393d2023-01-13 08:55:19 -0600851 with bb.utils.lock_timeout(self.wlock):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500852 self.writer.send_bytes(obj)
Patrick Williamsde0582f2022-04-08 10:23:27 -0500853 gc.enable()
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500854
Andrew Geissler9aee5002022-03-30 16:27:02 +0000855 def send(self, obj):
856 obj = multiprocessing.reduction.ForkingPickler.dumps(obj)
857 # See notes/code in CookerParser
858 # We must not terminate holding this lock else processes will hang.
859 # For SIGTERM, raising afterwards avoids this.
860 # For SIGINT, we don't want to have written partial data to the pipe.
861 # pthread_sigmask block/unblock would be nice but doesn't work, https://bugs.python.org/issue47139
862 process = multiprocessing.current_process()
863 if process and hasattr(process, "queue_signals"):
Andrew Geissler517393d2023-01-13 08:55:19 -0600864 with bb.utils.lock_timeout(process.signal_threadlock):
Andrew Geissler9aee5002022-03-30 16:27:02 +0000865 process.queue_signals = True
866 self._send(obj)
867 process.queue_signals = False
Patrick Williams2a254922023-08-11 09:48:11 -0500868
869 while len(process.signal_received) > 0:
870 sig = process.signal_received.pop()
871 process.handle_sig(sig, None)
Andrew Geissler9aee5002022-03-30 16:27:02 +0000872 else:
873 self._send(obj)
874
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500875 def fileno(self):
876 return self.writer.fileno()
877
878 def close(self):
879 return self.writer.close()