summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb/cooker.py
diff options
context:
space:
mode:
authorRichard Purdie <richard.purdie@linuxfoundation.org>2023-09-16 18:20:03 +0100
committerRichard Purdie <richard.purdie@linuxfoundation.org>2023-09-18 11:35:05 +0100
commit37c31a5adc26fc947a447ca9eae0983a654d1a33 (patch)
tree4d40adb741fd25fb0f60993e303ad5e9e7d5fa1a /bitbake/lib/bb/cooker.py
parentd1f84db670bd130353ebc5fb8077cf9657fb4e44 (diff)
downloadpoky-37c31a5adc26fc947a447ca9eae0983a654d1a33.tar.gz
bitbake: lib: Drop inotify support and replace with mtime checks
With the flush in serverlog() removed and a memory resident bitbake with a 60s timeout, the following could fail in strange ways: rm bitbake-cookerdaemon.log bitbake-layers add-layer ../meta-virtualization/ bitbake-layers add-layer ../meta-openembedded/meta-oe/ bitbake -m specifically that it might error adding meta-oe with an error related to meta-virt. This clearly shows that whilst bblayers.conf was modified, bitbake was not recognising that. This would fit with the random autobuilder issues seen when the serverlog flush() call was removed. The issue appears to be that you have no way to "sync()" the inotify events with the command stream coming over the socket. There is no way to know if there are changes in the IO queue which bitbake needs to wait for before proceeding with the next command. I did experiment with os.sync() and fsync on the inotify fd, however nothing addressed the issue. Since it is extremely important we have accurate cache data, the only realistic thing to do is to switch to stat() calls and check mtime. For bitbake commands, this is straightforward since we can revalidate the cache upon new connections/commands. For tinfoil this is problematic and we need to introduce and explict command "revalidateCaches" that the code can use to force bitbake to re-check it's cache validity. I've exposed this through tinfoil with a new "modified_files" function. So, this patch: a) drops inotify support within bitbake's cooker/server and switch to using mtime b) requires a new function call in tinfoil when metadata has been modified (Bitbake rev: da3ec3801bdb80180b3f1ac24edb27a698415ff7) Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib/bb/cooker.py')
-rw-r--r--bitbake/lib/bb/cooker.py179
1 files changed, 40 insertions, 139 deletions
diff --git a/bitbake/lib/bb/cooker.py b/bitbake/lib/bb/cooker.py
index 064e3cae6e..4089d003bb 100644
--- a/bitbake/lib/bb/cooker.py
+++ b/bitbake/lib/bb/cooker.py
@@ -22,7 +22,6 @@ from bb import utils, data, parse, event, cache, providers, taskdata, runqueue,
22import queue 22import queue
23import signal 23import signal
24import prserv.serv 24import prserv.serv
25import pyinotify
26import json 25import json
27import pickle 26import pickle
28import codecs 27import codecs
@@ -175,15 +174,8 @@ class BBCooker:
175 bb.debug(1, "BBCooker starting %s" % time.time()) 174 bb.debug(1, "BBCooker starting %s" % time.time())
176 sys.stdout.flush() 175 sys.stdout.flush()
177 176
178 self.configwatcher = None 177 self.configwatched = {}
179 self.confignotifier = None 178 self.parsewatched = {}
180
181 self.watchmask = pyinotify.IN_CLOSE_WRITE | pyinotify.IN_CREATE | pyinotify.IN_DELETE | \
182 pyinotify.IN_DELETE_SELF | pyinotify.IN_MODIFY | pyinotify.IN_MOVE_SELF | \
183 pyinotify.IN_MOVED_FROM | pyinotify.IN_MOVED_TO
184
185 self.watcher = None
186 self.notifier = None
187 179
188 # If being called by something like tinfoil, we need to clean cached data 180 # If being called by something like tinfoil, we need to clean cached data
189 # which may now be invalid 181 # which may now be invalid
@@ -194,8 +186,6 @@ class BBCooker:
194 self.hashserv = None 186 self.hashserv = None
195 self.hashservaddr = None 187 self.hashservaddr = None
196 188
197 self.inotify_modified_files = []
198
199 # TOSTOP must not be set or our children will hang when they output 189 # TOSTOP must not be set or our children will hang when they output
200 try: 190 try:
201 fd = sys.stdout.fileno() 191 fd = sys.stdout.fileno()
@@ -221,8 +211,6 @@ class BBCooker:
221 bb.debug(1, "BBCooker startup complete %s" % time.time()) 211 bb.debug(1, "BBCooker startup complete %s" % time.time())
222 sys.stdout.flush() 212 sys.stdout.flush()
223 213
224 self.inotify_threadlock = threading.Lock()
225
226 def init_configdata(self): 214 def init_configdata(self):
227 if not hasattr(self, "data"): 215 if not hasattr(self, "data"):
228 self.initConfigurationData() 216 self.initConfigurationData()
@@ -230,42 +218,6 @@ class BBCooker:
230 sys.stdout.flush() 218 sys.stdout.flush()
231 self.handlePRServ() 219 self.handlePRServ()
232 220
233 def setupConfigWatcher(self):
234 with bb.utils.lock_timeout(self.inotify_threadlock):
235 if self.configwatcher:
236 self.configwatcher.close()
237 self.confignotifier = None
238 self.configwatcher = None
239 self.configwatcher = pyinotify.WatchManager()
240 self.configwatcher.bbseen = set()
241 self.configwatcher.bbwatchedfiles = set()
242 self.confignotifier = pyinotify.Notifier(self.configwatcher, self.config_notifications)
243
244 def setupParserWatcher(self):
245 with bb.utils.lock_timeout(self.inotify_threadlock):
246 if self.watcher:
247 self.watcher.close()
248 self.notifier = None
249 self.watcher = None
250 self.watcher = pyinotify.WatchManager()
251 self.watcher.bbseen = set()
252 self.watcher.bbwatchedfiles = set()
253 self.notifier = pyinotify.Notifier(self.watcher, self.notifications)
254
255 def process_inotify_updates(self):
256 with bb.utils.lock_timeout(self.inotify_threadlock):
257 for n in [self.confignotifier, self.notifier]:
258 if n and n.check_events(timeout=0):
259 # read notified events and enqueue them
260 n.read_events()
261
262 def process_inotify_updates_apply(self):
263 with bb.utils.lock_timeout(self.inotify_threadlock):
264 for n in [self.confignotifier, self.notifier]:
265 if n and n.check_events(timeout=0):
266 n.read_events()
267 n.process_events()
268
269 def _baseconfig_set(self, value): 221 def _baseconfig_set(self, value):
270 if value and not self.baseconfig_valid: 222 if value and not self.baseconfig_valid:
271 bb.server.process.serverlog("Base config valid") 223 bb.server.process.serverlog("Base config valid")
@@ -280,88 +232,16 @@ class BBCooker:
280 bb.server.process.serverlog("Parse cache invalidated") 232 bb.server.process.serverlog("Parse cache invalidated")
281 self.parsecache_valid = value 233 self.parsecache_valid = value
282 234
283 def config_notifications(self, event): 235 def add_filewatch(self, deps, configwatcher=False):
284 if event.maskname == "IN_Q_OVERFLOW": 236 if configwatcher:
285 bb.warn("inotify event queue overflowed, invalidating caches.") 237 watcher = self.configwatched
286 self._parsecache_set(False) 238 else:
287 self._baseconfig_set(False) 239 watcher = self.parsewatched
288 bb.parse.clear_cache()
289 return
290 if not event.pathname in self.configwatcher.bbwatchedfiles:
291 return
292 if "IN_ISDIR" in event.maskname:
293 if "IN_CREATE" in event.maskname or "IN_DELETE" in event.maskname:
294 if event.pathname in self.configwatcher.bbseen:
295 self.configwatcher.bbseen.remove(event.pathname)
296 # Could remove all entries starting with the directory but for now...
297 bb.parse.clear_cache()
298 if not event.pathname in self.inotify_modified_files:
299 self.inotify_modified_files.append(event.pathname)
300 self._baseconfig_set(False)
301
302 def notifications(self, event):
303 if event.maskname == "IN_Q_OVERFLOW":
304 bb.warn("inotify event queue overflowed, invalidating caches.")
305 self._parsecache_set(False)
306 bb.parse.clear_cache()
307 return
308 if event.pathname.endswith("bitbake-cookerdaemon.log") \
309 or event.pathname.endswith("bitbake.lock"):
310 return
311 if "IN_ISDIR" in event.maskname:
312 if "IN_CREATE" in event.maskname or "IN_DELETE" in event.maskname:
313 if event.pathname in self.watcher.bbseen:
314 self.watcher.bbseen.remove(event.pathname)
315 # Could remove all entries starting with the directory but for now...
316 bb.parse.clear_cache()
317 if not event.pathname in self.inotify_modified_files:
318 self.inotify_modified_files.append(event.pathname)
319 self._parsecache_set(False)
320 240
321 def add_filewatch(self, deps, watcher=None, dirs=False):
322 if not watcher:
323 watcher = self.watcher
324 for i in deps: 241 for i in deps:
325 watcher.bbwatchedfiles.add(i[0]) 242 f = i[0]
326 if dirs: 243 mtime = i[1]
327 f = i[0] 244 watcher[f] = mtime
328 else:
329 f = os.path.dirname(i[0])
330 if f in watcher.bbseen:
331 continue
332 watcher.bbseen.add(f)
333 watchtarget = None
334 while True:
335 # We try and add watches for files that don't exist but if they did, would influence
336 # the parser. The parent directory of these files may not exist, in which case we need
337 # to watch any parent that does exist for changes.
338 try:
339 watcher.add_watch(f, self.watchmask, quiet=False)
340 if watchtarget:
341 watcher.bbwatchedfiles.add(watchtarget)
342 break
343 except pyinotify.WatchManagerError as e:
344 if 'ENOENT' in str(e):
345 watchtarget = f
346 f = os.path.dirname(f)
347 if f in watcher.bbseen:
348 break
349 watcher.bbseen.add(f)
350 continue
351 if 'ENOSPC' in str(e):
352 providerlog.error("No space left on device or exceeds fs.inotify.max_user_watches?")
353 providerlog.error("To check max_user_watches: sysctl -n fs.inotify.max_user_watches.")
354 providerlog.error("To modify max_user_watches: sysctl -n -w fs.inotify.max_user_watches=<value>.")
355 providerlog.error("Root privilege is required to modify max_user_watches.")
356 raise
357
358 def handle_inotify_updates(self):
359 # reload files for which we got notifications
360 for p in self.inotify_modified_files:
361 bb.parse.update_cache(p)
362 if p in bb.parse.BBHandler.cached_statements:
363 del bb.parse.BBHandler.cached_statements[p]
364 self.inotify_modified_files = []
365 245
366 def sigterm_exception(self, signum, stackframe): 246 def sigterm_exception(self, signum, stackframe):
367 if signum == signal.SIGTERM: 247 if signum == signal.SIGTERM:
@@ -392,8 +272,7 @@ class BBCooker:
392 if mod not in self.orig_sysmodules: 272 if mod not in self.orig_sysmodules:
393 del sys.modules[mod] 273 del sys.modules[mod]
394 274
395 self.handle_inotify_updates() 275 self.configwatched = {}
396 self.setupConfigWatcher()
397 276
398 # Need to preserve BB_CONSOLELOG over resets 277 # Need to preserve BB_CONSOLELOG over resets
399 consolelog = None 278 consolelog = None
@@ -436,7 +315,7 @@ class BBCooker:
436 self.disableDataTracking() 315 self.disableDataTracking()
437 316
438 for mc in self.databuilder.mcdata.values(): 317 for mc in self.databuilder.mcdata.values():
439 self.add_filewatch(mc.getVar("__base_depends", False), self.configwatcher) 318 self.add_filewatch(mc.getVar("__base_depends", False), configwatcher=True)
440 319
441 self._baseconfig_set(True) 320 self._baseconfig_set(True)
442 self._parsecache_set(False) 321 self._parsecache_set(False)
@@ -486,6 +365,29 @@ class BBCooker:
486 if hasattr(self, "data"): 365 if hasattr(self, "data"):
487 self.data.disableTracking() 366 self.data.disableTracking()
488 367
368 def revalidateCaches(self):
369 bb.parse.clear_cache()
370
371 clean = True
372 for f in self.configwatched:
373 if not bb.parse.check_mtime(f, self.configwatched[f]):
374 bb.server.process.serverlog("Found %s changed, invalid cache" % f)
375 self._baseconfig_set(False)
376 self._parsecache_set(False)
377 clean = False
378 break
379
380 if clean:
381 for f in self.parsewatched:
382 if not bb.parse.check_mtime(f, self.parsewatched[f]):
383 bb.server.process.serverlog("Found %s changed, invalid cache" % f)
384 self._parsecache_set(False)
385 clean = False
386 break
387
388 if not clean:
389 bb.parse.BBHandler.cached_statements = {}
390
489 def parseConfiguration(self): 391 def parseConfiguration(self):
490 self.updateCacheSync() 392 self.updateCacheSync()
491 393
@@ -566,6 +468,7 @@ class BBCooker:
566 # Now update all the variables not in the datastore to match 468 # Now update all the variables not in the datastore to match
567 self.configuration.env = environment 469 self.configuration.env = environment
568 470
471 self.revalidateCaches()
569 if not clean: 472 if not clean:
570 logger.debug("Base environment change, triggering reparse") 473 logger.debug("Base environment change, triggering reparse")
571 self.reset() 474 self.reset()
@@ -1644,8 +1547,6 @@ class BBCooker:
1644 if self.state == state.running: 1547 if self.state == state.running:
1645 return 1548 return
1646 1549
1647 self.handle_inotify_updates()
1648
1649 if not self.baseconfig_valid: 1550 if not self.baseconfig_valid:
1650 logger.debug("Reloading base configuration data") 1551 logger.debug("Reloading base configuration data")
1651 self.initConfigurationData() 1552 self.initConfigurationData()
@@ -1667,7 +1568,7 @@ class BBCooker:
1667 1568
1668 if self.state != state.parsing and not self.parsecache_valid: 1569 if self.state != state.parsing and not self.parsecache_valid:
1669 bb.server.process.serverlog("Parsing started") 1570 bb.server.process.serverlog("Parsing started")
1670 self.setupParserWatcher() 1571 self.parsewatched = {}
1671 1572
1672 bb.parse.siggen.reset(self.data) 1573 bb.parse.siggen.reset(self.data)
1673 self.parseConfiguration () 1574 self.parseConfiguration ()
@@ -1692,9 +1593,9 @@ class BBCooker:
1692 total_masked += masked 1593 total_masked += masked
1693 searchdirs |= set(search) 1594 searchdirs |= set(search)
1694 1595
1695 # Add inotify watches for directories searched for bb/bbappend files 1596 # Add mtimes for directories searched for bb/bbappend files
1696 for dirent in searchdirs: 1597 for dirent in searchdirs:
1697 self.add_filewatch([[dirent]], dirs=True) 1598 self.add_filewatch([(dirent, bb.parse.cached_mtime_noerror(dirent))])
1698 1599
1699 self.parser = CookerParser(self, mcfilelist, total_masked) 1600 self.parser = CookerParser(self, mcfilelist, total_masked)
1700 self._parsecache_set(True) 1601 self._parsecache_set(True)
@@ -1881,7 +1782,7 @@ class CookerCollectFiles(object):
1881 collectlog.error("no recipe files to build, check your BBPATH and BBFILES?") 1782 collectlog.error("no recipe files to build, check your BBPATH and BBFILES?")
1882 bb.event.fire(CookerExit(), eventdata) 1783 bb.event.fire(CookerExit(), eventdata)
1883 1784
1884 # We need to track where we look so that we can add inotify watches. There 1785 # We need to track where we look so that we can know when the cache is invalid. There
1885 # is no nice way to do this, this is horrid. We intercept the os.listdir() 1786 # is no nice way to do this, this is horrid. We intercept the os.listdir()
1886 # (or os.scandir() for python 3.6+) calls while we run glob(). 1787 # (or os.scandir() for python 3.6+) calls while we run glob().
1887 origlistdir = os.listdir 1788 origlistdir = os.listdir