1fce6df7eSMauro Carvalho Chehab#!/usr/bin/env python3 2fce6df7eSMauro Carvalho Chehab# SPDX-License-Identifier: GPL-2.0+ 3fce6df7eSMauro Carvalho Chehab# 4fce6df7eSMauro Carvalho Chehab# pylint: disable=C0103,C0209 5fce6df7eSMauro Carvalho Chehab# 6fce6df7eSMauro Carvalho Chehab# 7fce6df7eSMauro Carvalho Chehab 8fce6df7eSMauro Carvalho Chehab""" 9fce6df7eSMauro Carvalho ChehabInteracts with the POSIX jobserver during the Kernel build time. 10fce6df7eSMauro Carvalho Chehab 11fce6df7eSMauro Carvalho ChehabA "normal" jobserver task, like the one initiated by a make subrocess would do: 12fce6df7eSMauro Carvalho Chehab 13fce6df7eSMauro Carvalho Chehab - open read/write file descriptors to communicate with the job server; 14*8b85f614SMauro Carvalho Chehab - ask for one slot by calling:: 15*8b85f614SMauro Carvalho Chehab 16fce6df7eSMauro Carvalho Chehab claim = os.read(reader, 1) 17*8b85f614SMauro Carvalho Chehab 18*8b85f614SMauro Carvalho Chehab - when the job finshes, call:: 19*8b85f614SMauro Carvalho Chehab 20fce6df7eSMauro Carvalho Chehab os.write(writer, b"+") # os.write(writer, claim) 21fce6df7eSMauro Carvalho Chehab 22fce6df7eSMauro Carvalho ChehabHere, the goal is different: This script aims to get the remaining number 23fce6df7eSMauro Carvalho Chehabof slots available, using all of them to run a command which handle tasks in 24fce6df7eSMauro Carvalho Chehabparallel. To to that, it has a loop that ends only after there are no 25fce6df7eSMauro Carvalho Chehabslots left. It then increments the number by one, in order to allow a 26*8b85f614SMauro Carvalho Chehabcall equivalent to ``make -j$((claim+1))``, e.g. having a parent make creating 27fce6df7eSMauro Carvalho Chehab$claim child to do the actual work. 28fce6df7eSMauro Carvalho Chehab 29fce6df7eSMauro Carvalho ChehabThe end goal here is to keep the total number of build tasks under the 30*8b85f614SMauro Carvalho Chehablimit established by the initial ``make -j$n_proc`` call. 31fce6df7eSMauro Carvalho Chehab 32fce6df7eSMauro Carvalho ChehabSee: 33fce6df7eSMauro Carvalho Chehab https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html#POSIX-Jobserver 34fce6df7eSMauro Carvalho Chehab""" 35fce6df7eSMauro Carvalho Chehab 36fce6df7eSMauro Carvalho Chehabimport errno 37fce6df7eSMauro Carvalho Chehabimport os 38fce6df7eSMauro Carvalho Chehabimport subprocess 39fce6df7eSMauro Carvalho Chehabimport sys 40fce6df7eSMauro Carvalho Chehab 41b2664a90SJonathan Corbetdef warn(text, *args): 42b2664a90SJonathan Corbet print(f'WARNING: {text}', *args, file = sys.stderr) 43b2664a90SJonathan Corbet 44fce6df7eSMauro Carvalho Chehabclass JobserverExec: 45fce6df7eSMauro Carvalho Chehab """ 46fce6df7eSMauro Carvalho Chehab Claim all slots from make using POSIX Jobserver. 47fce6df7eSMauro Carvalho Chehab 48fce6df7eSMauro Carvalho Chehab The main methods here are: 49*8b85f614SMauro Carvalho Chehab 50fce6df7eSMauro Carvalho Chehab - open(): reserves all slots; 51fce6df7eSMauro Carvalho Chehab - close(): method returns all used slots back to make; 52*8b85f614SMauro Carvalho Chehab - run(): executes a command setting PARALLELISM=<available slots jobs + 1>. 53fce6df7eSMauro Carvalho Chehab """ 54fce6df7eSMauro Carvalho Chehab 55fce6df7eSMauro Carvalho Chehab def __init__(self): 56*8b85f614SMauro Carvalho Chehab """Initialize internal vars.""" 57fce6df7eSMauro Carvalho Chehab self.claim = 0 58fce6df7eSMauro Carvalho Chehab self.jobs = b"" 59fce6df7eSMauro Carvalho Chehab self.reader = None 60fce6df7eSMauro Carvalho Chehab self.writer = None 61fce6df7eSMauro Carvalho Chehab self.is_open = False 62fce6df7eSMauro Carvalho Chehab 63fce6df7eSMauro Carvalho Chehab def open(self): 64*8b85f614SMauro Carvalho Chehab """Reserve all available slots to be claimed later on.""" 65fce6df7eSMauro Carvalho Chehab 66fce6df7eSMauro Carvalho Chehab if self.is_open: 67fce6df7eSMauro Carvalho Chehab return 68b2664a90SJonathan Corbet self.is_open = True # We only try once 69b2664a90SJonathan Corbet self.claim = None 70b2664a90SJonathan Corbet # 71b2664a90SJonathan Corbet # Check the make flags for "--jobserver=R,W" 72fce6df7eSMauro Carvalho Chehab # Note that GNU Make has used --jobserver-fds and --jobserver-auth 73fce6df7eSMauro Carvalho Chehab # so this handles all of them. 74b2664a90SJonathan Corbet # 75b2664a90SJonathan Corbet flags = os.environ.get('MAKEFLAGS', '') 76fce6df7eSMauro Carvalho Chehab opts = [x for x in flags.split(" ") if x.startswith("--jobserver")] 77b2664a90SJonathan Corbet if not opts: 78b2664a90SJonathan Corbet return 79b2664a90SJonathan Corbet # 80b2664a90SJonathan Corbet # Separate out the provided file descriptors 81b2664a90SJonathan Corbet # 82b2664a90SJonathan Corbet split_opt = opts[-1].split('=', 1) 83b2664a90SJonathan Corbet if len(split_opt) != 2: 84b2664a90SJonathan Corbet warn('unparseable option:', opts[-1]) 85b2664a90SJonathan Corbet return 86b2664a90SJonathan Corbet fds = split_opt[1] 87b2664a90SJonathan Corbet # 88b2664a90SJonathan Corbet # As of GNU Make 4.4, we'll be looking for a named pipe 89b2664a90SJonathan Corbet # identified as fifo:path 90b2664a90SJonathan Corbet # 91b2664a90SJonathan Corbet if fds.startswith('fifo:'): 92b2664a90SJonathan Corbet path = fds[len('fifo:'):] 93b2664a90SJonathan Corbet try: 94fce6df7eSMauro Carvalho Chehab self.reader = os.open(path, os.O_RDONLY | os.O_NONBLOCK) 95fce6df7eSMauro Carvalho Chehab self.writer = os.open(path, os.O_WRONLY) 96b2664a90SJonathan Corbet except (OSError, IOError): 97b2664a90SJonathan Corbet warn('unable to open jobserver pipe', path) 98b2664a90SJonathan Corbet return 99b2664a90SJonathan Corbet # 100b2664a90SJonathan Corbet # Otherwise look for integer file-descriptor numbers. 101b2664a90SJonathan Corbet # 102fce6df7eSMauro Carvalho Chehab else: 103b2664a90SJonathan Corbet split_fds = fds.split(',') 104b2664a90SJonathan Corbet if len(split_fds) != 2: 105b2664a90SJonathan Corbet warn('malformed jobserver file descriptors:', fds) 106b2664a90SJonathan Corbet return 107b2664a90SJonathan Corbet try: 108b2664a90SJonathan Corbet self.reader = int(split_fds[0]) 109b2664a90SJonathan Corbet self.writer = int(split_fds[1]) 110b2664a90SJonathan Corbet except ValueError: 111b2664a90SJonathan Corbet warn('non-integer jobserver file-descriptors:', fds) 112b2664a90SJonathan Corbet return 113b2664a90SJonathan Corbet try: 114b2664a90SJonathan Corbet # 115fce6df7eSMauro Carvalho Chehab # Open a private copy of reader to avoid setting nonblocking 116fce6df7eSMauro Carvalho Chehab # on an unexpecting process with the same reader fd. 117b2664a90SJonathan Corbet # 118b2664a90SJonathan Corbet self.reader = os.open(f"/proc/self/fd/{self.reader}", 119fce6df7eSMauro Carvalho Chehab os.O_RDONLY | os.O_NONBLOCK) 120b2664a90SJonathan Corbet except (IOError, OSError) as e: 121b2664a90SJonathan Corbet warn('Unable to reopen jobserver read-side pipe:', repr(e)) 122b2664a90SJonathan Corbet return 123b2664a90SJonathan Corbet # 124b2664a90SJonathan Corbet # OK, we have the channel to the job server; read out as many jobserver 125b2664a90SJonathan Corbet # slots as possible. 126b2664a90SJonathan Corbet # 127fce6df7eSMauro Carvalho Chehab while True: 128fce6df7eSMauro Carvalho Chehab try: 129fce6df7eSMauro Carvalho Chehab slot = os.read(self.reader, 8) 130bbf8c67aSChangbin Du if not slot: 131b2664a90SJonathan Corbet # 132b2664a90SJonathan Corbet # Something went wrong. Clear self.jobs to avoid writing 133b2664a90SJonathan Corbet # weirdness back to the jobserver and give up. 134bbf8c67aSChangbin Du self.jobs = b"" 135b2664a90SJonathan Corbet warn("unexpected empty token from jobserver;" 136b2664a90SJonathan Corbet " possible invalid '--jobserver-auth=' setting") 137b2664a90SJonathan Corbet self.claim = None 138b2664a90SJonathan Corbet return 139fce6df7eSMauro Carvalho Chehab except (OSError, IOError) as e: 140b2664a90SJonathan Corbet # 141b2664a90SJonathan Corbet # If there is nothing more to read then we are done. 142b2664a90SJonathan Corbet # 143fce6df7eSMauro Carvalho Chehab if e.errno == errno.EWOULDBLOCK: 144fce6df7eSMauro Carvalho Chehab break 145b2664a90SJonathan Corbet # 146b2664a90SJonathan Corbet # Anything else says that something went weird; give back 147b2664a90SJonathan Corbet # the jobs and give up. 148b2664a90SJonathan Corbet # 149fce6df7eSMauro Carvalho Chehab if self.jobs: 150fce6df7eSMauro Carvalho Chehab os.write(self.writer, self.jobs) 151b2664a90SJonathan Corbet self.claim = None 152b2664a90SJonathan Corbet warn('error reading from jobserver pipe', repr(e)) 153b2664a90SJonathan Corbet return 154b2664a90SJonathan Corbet self.jobs += slot 155b2664a90SJonathan Corbet # 156fce6df7eSMauro Carvalho Chehab # Add a bump for our caller's reserveration, since we're just going 157fce6df7eSMauro Carvalho Chehab # to sit here blocked on our child. 158b2664a90SJonathan Corbet # 159fce6df7eSMauro Carvalho Chehab self.claim = len(self.jobs) + 1 160fce6df7eSMauro Carvalho Chehab 161fce6df7eSMauro Carvalho Chehab def close(self): 162*8b85f614SMauro Carvalho Chehab """Return all reserved slots to Jobserver.""" 163fce6df7eSMauro Carvalho Chehab 164fce6df7eSMauro Carvalho Chehab if not self.is_open: 165fce6df7eSMauro Carvalho Chehab return 166fce6df7eSMauro Carvalho Chehab 167fce6df7eSMauro Carvalho Chehab # Return all the reserved slots. 168fce6df7eSMauro Carvalho Chehab if len(self.jobs): 169fce6df7eSMauro Carvalho Chehab os.write(self.writer, self.jobs) 170fce6df7eSMauro Carvalho Chehab 171fce6df7eSMauro Carvalho Chehab self.is_open = False 172fce6df7eSMauro Carvalho Chehab 173fce6df7eSMauro Carvalho Chehab def __enter__(self): 174fce6df7eSMauro Carvalho Chehab self.open() 175fce6df7eSMauro Carvalho Chehab return self 176fce6df7eSMauro Carvalho Chehab 177fce6df7eSMauro Carvalho Chehab def __exit__(self, exc_type, exc_value, exc_traceback): 178fce6df7eSMauro Carvalho Chehab self.close() 179fce6df7eSMauro Carvalho Chehab 180fce6df7eSMauro Carvalho Chehab def run(self, cmd, *args, **pwargs): 181fce6df7eSMauro Carvalho Chehab """ 182fce6df7eSMauro Carvalho Chehab Run a command setting PARALLELISM env variable to the number of 183fce6df7eSMauro Carvalho Chehab available job slots (claim) + 1, e.g. it will reserve claim slots 184fce6df7eSMauro Carvalho Chehab to do the actual build work, plus one to monitor its children. 185fce6df7eSMauro Carvalho Chehab """ 186fce6df7eSMauro Carvalho Chehab self.open() # Ensure that self.claim is set 187fce6df7eSMauro Carvalho Chehab 188fce6df7eSMauro Carvalho Chehab # We can only claim parallelism if there was a jobserver (i.e. a 189fce6df7eSMauro Carvalho Chehab # top-level "-jN" argument) and there were no other failures. Otherwise 190fce6df7eSMauro Carvalho Chehab # leave out the environment variable and let the child figure out what 191fce6df7eSMauro Carvalho Chehab # is best. 192fce6df7eSMauro Carvalho Chehab if self.claim: 193fce6df7eSMauro Carvalho Chehab os.environ["PARALLELISM"] = str(self.claim) 194fce6df7eSMauro Carvalho Chehab 195fce6df7eSMauro Carvalho Chehab return subprocess.call(cmd, *args, **pwargs) 196