summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRichard Purdie <richard.purdie@linuxfoundation.org>2023-01-09 23:17:56 +0000
committerRichard Purdie <richard.purdie@linuxfoundation.org>2023-01-11 10:58:36 +0000
commit165e8b563d400bf54c317659a267db85e8c34006 (patch)
tree7e4d05e7d3440b6f95c029afabf1822d3f7b2733
parent4e4f040a73ca81f830cf90af05b75e7c06f91d8d (diff)
downloadpoky-165e8b563d400bf54c317659a267db85e8c34006.tar.gz
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 <richard.purdie@linuxfoundation.org>
-rw-r--r--bitbake/lib/bb/command.py26
-rw-r--r--bitbake/lib/bb/cooker.py12
-rw-r--r--bitbake/lib/bb/server/process.py30
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:
51 """ 51 """
52 A queue of asynchronous commands for bitbake 52 A queue of asynchronous commands for bitbake
53 """ 53 """
54 def __init__(self, cooker): 54 def __init__(self, cooker, process_server):
55 self.cooker = cooker 55 self.cooker = cooker
56 self.cmds_sync = CommandsSync() 56 self.cmds_sync = CommandsSync()
57 self.cmds_async = CommandsAsync() 57 self.cmds_async = CommandsAsync()
58 self.remotedatastores = None 58 self.remotedatastores = None
59 59
60 # FIXME Add lock for this 60 self.process_server = process_server
61 # Access with locking using process_server.{get/set/clear}_async_cmd()
61 self.currentAsyncCommand = None 62 self.currentAsyncCommand = None
62 63
63 def runCommand(self, commandline, process_server, ro_only=False): 64 def runCommand(self, commandline, process_server, ro_only=False):
@@ -99,18 +100,14 @@ class Command:
99 return None, traceback.format_exc() 100 return None, traceback.format_exc()
100 else: 101 else:
101 return result, None 102 return result, None
102 if self.currentAsyncCommand is not None:
103 # Wait for the idle loop to have cleared (30s max)
104 process_server.wait_for_idle(timeout=30)
105 if self.currentAsyncCommand is not None:
106 return None, "Busy (%s in progress)" % self.currentAsyncCommand[0]
107 if command not in CommandsAsync.__dict__: 103 if command not in CommandsAsync.__dict__:
108 return None, "No such command" 104 return None, "No such command"
109 self.currentAsyncCommand = (command, commandline) 105 if not process_server.set_async_cmd((command, commandline)):
110 self.cooker.idleCallBackRegister(self.runAsyncCommand, None) 106 return None, "Busy (%s in progress)" % self.process_server.get_async_cmd()[0]
107 self.cooker.idleCallBackRegister(self.runAsyncCommand, process_server)
111 return True, None 108 return True, None
112 109
113 def runAsyncCommand(self, _, _2, halt): 110 def runAsyncCommand(self, _, process_server, halt):
114 try: 111 try:
115 self.cooker.process_inotify_updates_apply() 112 self.cooker.process_inotify_updates_apply()
116 if self.cooker.state in (bb.cooker.state.error, bb.cooker.state.shutdown, bb.cooker.state.forceshutdown): 113 if self.cooker.state in (bb.cooker.state.error, bb.cooker.state.shutdown, bb.cooker.state.forceshutdown):
@@ -118,8 +115,9 @@ class Command:
118 # and then raise BBHandledException triggering an exit 115 # and then raise BBHandledException triggering an exit
119 self.cooker.updateCache() 116 self.cooker.updateCache()
120 return bb.server.process.idleFinish("Cooker in error state") 117 return bb.server.process.idleFinish("Cooker in error state")
121 if self.currentAsyncCommand is not None: 118 cmd = process_server.get_async_cmd()
122 (command, options) = self.currentAsyncCommand 119 if cmd is not None:
120 (command, options) = cmd
123 commandmethod = getattr(CommandsAsync, command) 121 commandmethod = getattr(CommandsAsync, command)
124 needcache = getattr( commandmethod, "needcache" ) 122 needcache = getattr( commandmethod, "needcache" )
125 if needcache and self.cooker.state != bb.cooker.state.running: 123 if needcache and self.cooker.state != bb.cooker.state.running:
@@ -153,7 +151,7 @@ class Command:
153 else: 151 else:
154 bb.event.fire(CommandCompleted(), self.cooker.data) 152 bb.event.fire(CommandCompleted(), self.cooker.data)
155 self.cooker.finishcommand() 153 self.cooker.finishcommand()
156 self.currentAsyncCommand = None 154 self.process_server.clear_async_cmd()
157 155
158 def reset(self): 156 def reset(self):
159 if self.remotedatastores: 157 if self.remotedatastores:
@@ -746,7 +744,7 @@ class CommandsAsync:
746 """ 744 """
747 event = params[0] 745 event = params[0]
748 bb.event.fire(eval(event), command.cooker.data) 746 bb.event.fire(eval(event), command.cooker.data)
749 command.currentAsyncCommand = None 747 process_server.clear_async_cmd()
750 triggerEvent.needcache = False 748 triggerEvent.needcache = False
751 749
752 def resetCooker(self, command, params): 750 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:
149 Manages one bitbake build run 149 Manages one bitbake build run
150 """ 150 """
151 151
152 def __init__(self, featureSet=None, idleCallBackRegister=None, waitIdle=None): 152 def __init__(self, featureSet=None, server=None):
153 self.recipecaches = None 153 self.recipecaches = None
154 self.eventlog = None 154 self.eventlog = None
155 self.skiplist = {} 155 self.skiplist = {}
@@ -163,8 +163,12 @@ class BBCooker:
163 163
164 self.configuration = bb.cookerdata.CookerConfiguration() 164 self.configuration = bb.cookerdata.CookerConfiguration()
165 165
166 self.idleCallBackRegister = idleCallBackRegister 166 self.process_server = server
167 self.waitIdle = waitIdle 167 self.idleCallBackRegister = None
168 self.waitIdle = None
169 if server:
170 self.idleCallBackRegister = server.register_idle_function
171 self.waitIdle = server.wait_for_idle
168 172
169 bb.debug(1, "BBCooker starting %s" % time.time()) 173 bb.debug(1, "BBCooker starting %s" % time.time())
170 sys.stdout.flush() 174 sys.stdout.flush()
@@ -203,7 +207,7 @@ class BBCooker:
203 except UnsupportedOperation: 207 except UnsupportedOperation:
204 pass 208 pass
205 209
206 self.command = bb.command.Command(self) 210 self.command = bb.command.Command(self, self.process_server)
207 self.state = state.initial 211 self.state = state.initial
208 212
209 self.parser = None 213 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():
154 154
155 return ret 155 return ret
156 156
157 def _idle_check(self):
158 return len(self._idlefuns) == 0 and self.cooker.command.currentAsyncCommand is None
159
157 def wait_for_idle(self, timeout=30): 160 def wait_for_idle(self, timeout=30):
158 # Wait for the idle loop to have cleared 161 # Wait for the idle loop to have cleared
159 with self.idle_cond: 162 with bb.utils.lock_timeout(self._idlefuncsLock):
160 self.idle_cond.wait_for(lambda: len(self._idlefuns) == 0, timeout) 163 return self.idle_cond.wait_for(self._idle_check, timeout) is not False
164
165 def set_async_cmd(self, cmd):
166 with bb.utils.lock_timeout(self._idlefuncsLock):
167 ret = self.idle_cond.wait_for(self._idle_check, 30)
168 if ret is False:
169 return False
170 self.cooker.command.currentAsyncCommand = cmd
171 return True
172
173 def clear_async_cmd(self):
174 with bb.utils.lock_timeout(self._idlefuncsLock):
175 self.cooker.command.currentAsyncCommand = None
176 self.idle_cond.notify_all()
177
178 def get_async_cmd(self):
179 with bb.utils.lock_timeout(self._idlefuncsLock):
180 return self.cooker.command.currentAsyncCommand
161 181
162 def main(self): 182 def main(self):
163 self.cooker.pre_serve() 183 self.cooker.pre_serve()
@@ -183,11 +203,9 @@ class ProcessServer():
183 self.controllersock = False 203 self.controllersock = False
184 if self.haveui: 204 if self.haveui:
185 # Wait for the idle loop to have cleared (30s max) 205 # Wait for the idle loop to have cleared (30s max)
186 self.wait_for_idle(30) 206 if not self.wait_for_idle(30):
187 if self.cooker.command.currentAsyncCommand is not None:
188 serverlog("Idle loop didn't finish queued commands after 30s, exiting.") 207 serverlog("Idle loop didn't finish queued commands after 30s, exiting.")
189 self.quit = True 208 self.quit = True
190
191 fds.remove(self.command_channel) 209 fds.remove(self.command_channel)
192 bb.event.unregister_UIHhandler(self.event_handle, True) 210 bb.event.unregister_UIHhandler(self.event_handle, True)
193 self.command_channel_reply.writer.close() 211 self.command_channel_reply.writer.close()
@@ -624,7 +642,7 @@ def execServer(lockfd, readypipeinfd, lockname, sockname, server_timeout, xmlrpc
624 writer = ConnectionWriter(readypipeinfd) 642 writer = ConnectionWriter(readypipeinfd)
625 try: 643 try:
626 featureset = [] 644 featureset = []
627 cooker = bb.cooker.BBCooker(featureset, server.register_idle_function, server.wait_for_idle) 645 cooker = bb.cooker.BBCooker(featureset, server)
628 cooker.configuration.profile = profile 646 cooker.configuration.profile = profile
629 except bb.BBHandledException: 647 except bb.BBHandledException:
630 return None 648 return None