From 165e8b563d400bf54c317659a267db85e8c34006 Mon Sep 17 00:00:00 2001 From: Richard Purdie Date: Mon, 9 Jan 2023 23:17:56 +0000 Subject: bitbake: process/cooker/command: Fix currentAsyncCommand locking/races currentAsyncCommand currently doesn't have any locking and we have a conflict in "idle" conditions since the idle functions count needs to be zero *and* there needs to be no active command. Move the changes/checks of currentAsyncCommand to within the lock and then we can add it to the condition for idle, simplifying some of the code. (Bitbake rev: b5215887d2f8ea3f28f1ebda721bd5b8f93ec7f3) Signed-off-by: Richard Purdie --- bitbake/lib/bb/command.py | 26 ++++++++++++-------------- bitbake/lib/bb/cooker.py | 12 ++++++++---- bitbake/lib/bb/server/process.py | 30 ++++++++++++++++++++++++------ 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/bitbake/lib/bb/command.py b/bitbake/lib/bb/command.py index a29e41afda..9e2cdc5c75 100644 --- a/bitbake/lib/bb/command.py +++ b/bitbake/lib/bb/command.py @@ -51,13 +51,14 @@ class Command: """ A queue of asynchronous commands for bitbake """ - def __init__(self, cooker): + def __init__(self, cooker, process_server): self.cooker = cooker self.cmds_sync = CommandsSync() self.cmds_async = CommandsAsync() self.remotedatastores = None - # FIXME Add lock for this + self.process_server = process_server + # Access with locking using process_server.{get/set/clear}_async_cmd() self.currentAsyncCommand = None def runCommand(self, commandline, process_server, ro_only=False): @@ -99,18 +100,14 @@ class Command: return None, traceback.format_exc() else: return result, None - if self.currentAsyncCommand is not None: - # Wait for the idle loop to have cleared (30s max) - process_server.wait_for_idle(timeout=30) - if self.currentAsyncCommand is not None: - return None, "Busy (%s in progress)" % self.currentAsyncCommand[0] if command not in CommandsAsync.__dict__: return None, "No such command" - self.currentAsyncCommand = (command, commandline) - self.cooker.idleCallBackRegister(self.runAsyncCommand, None) + if not process_server.set_async_cmd((command, commandline)): + return None, "Busy (%s in progress)" % self.process_server.get_async_cmd()[0] + self.cooker.idleCallBackRegister(self.runAsyncCommand, process_server) return True, None - def runAsyncCommand(self, _, _2, halt): + def runAsyncCommand(self, _, process_server, halt): try: self.cooker.process_inotify_updates_apply() if self.cooker.state in (bb.cooker.state.error, bb.cooker.state.shutdown, bb.cooker.state.forceshutdown): @@ -118,8 +115,9 @@ class Command: # and then raise BBHandledException triggering an exit self.cooker.updateCache() return bb.server.process.idleFinish("Cooker in error state") - if self.currentAsyncCommand is not None: - (command, options) = self.currentAsyncCommand + cmd = process_server.get_async_cmd() + if cmd is not None: + (command, options) = cmd commandmethod = getattr(CommandsAsync, command) needcache = getattr( commandmethod, "needcache" ) if needcache and self.cooker.state != bb.cooker.state.running: @@ -153,7 +151,7 @@ class Command: else: bb.event.fire(CommandCompleted(), self.cooker.data) self.cooker.finishcommand() - self.currentAsyncCommand = None + self.process_server.clear_async_cmd() def reset(self): if self.remotedatastores: @@ -746,7 +744,7 @@ class CommandsAsync: """ event = params[0] bb.event.fire(eval(event), command.cooker.data) - command.currentAsyncCommand = None + process_server.clear_async_cmd() triggerEvent.needcache = False def resetCooker(self, command, params): diff --git a/bitbake/lib/bb/cooker.py b/bitbake/lib/bb/cooker.py index 13d6e9d847..5a0e675b44 100644 --- a/bitbake/lib/bb/cooker.py +++ b/bitbake/lib/bb/cooker.py @@ -149,7 +149,7 @@ class BBCooker: Manages one bitbake build run """ - def __init__(self, featureSet=None, idleCallBackRegister=None, waitIdle=None): + def __init__(self, featureSet=None, server=None): self.recipecaches = None self.eventlog = None self.skiplist = {} @@ -163,8 +163,12 @@ class BBCooker: self.configuration = bb.cookerdata.CookerConfiguration() - self.idleCallBackRegister = idleCallBackRegister - self.waitIdle = waitIdle + self.process_server = server + self.idleCallBackRegister = None + self.waitIdle = None + if server: + self.idleCallBackRegister = server.register_idle_function + self.waitIdle = server.wait_for_idle bb.debug(1, "BBCooker starting %s" % time.time()) sys.stdout.flush() @@ -203,7 +207,7 @@ class BBCooker: except UnsupportedOperation: pass - self.command = bb.command.Command(self) + self.command = bb.command.Command(self, self.process_server) self.state = state.initial self.parser = None diff --git a/bitbake/lib/bb/server/process.py b/bitbake/lib/bb/server/process.py index 81e5229c9d..a25f04d148 100644 --- a/bitbake/lib/bb/server/process.py +++ b/bitbake/lib/bb/server/process.py @@ -154,10 +154,30 @@ class ProcessServer(): return ret + def _idle_check(self): + return len(self._idlefuns) == 0 and self.cooker.command.currentAsyncCommand is None + def wait_for_idle(self, timeout=30): # Wait for the idle loop to have cleared - with self.idle_cond: - self.idle_cond.wait_for(lambda: len(self._idlefuns) == 0, timeout) + with bb.utils.lock_timeout(self._idlefuncsLock): + return self.idle_cond.wait_for(self._idle_check, timeout) is not False + + def set_async_cmd(self, cmd): + with bb.utils.lock_timeout(self._idlefuncsLock): + ret = self.idle_cond.wait_for(self._idle_check, 30) + if ret is False: + return False + self.cooker.command.currentAsyncCommand = cmd + return True + + def clear_async_cmd(self): + with bb.utils.lock_timeout(self._idlefuncsLock): + self.cooker.command.currentAsyncCommand = None + self.idle_cond.notify_all() + + def get_async_cmd(self): + with bb.utils.lock_timeout(self._idlefuncsLock): + return self.cooker.command.currentAsyncCommand def main(self): self.cooker.pre_serve() @@ -183,11 +203,9 @@ class ProcessServer(): self.controllersock = False if self.haveui: # Wait for the idle loop to have cleared (30s max) - self.wait_for_idle(30) - if self.cooker.command.currentAsyncCommand is not None: + if not self.wait_for_idle(30): serverlog("Idle loop didn't finish queued commands after 30s, exiting.") self.quit = True - fds.remove(self.command_channel) bb.event.unregister_UIHhandler(self.event_handle, True) self.command_channel_reply.writer.close() @@ -624,7 +642,7 @@ def execServer(lockfd, readypipeinfd, lockname, sockname, server_timeout, xmlrpc writer = ConnectionWriter(readypipeinfd) try: featureset = [] - cooker = bb.cooker.BBCooker(featureset, server.register_idle_function, server.wait_for_idle) + cooker = bb.cooker.BBCooker(featureset, server) cooker.configuration.profile = profile except bb.BBHandledException: return None -- cgit v1.2.3-54-g00ecf