# 2.6.4. `canStartBuild` Functions

Sometimes, you cannot know in advance what workers to assign to a `BuilderConfig`. For example, you might need to check for the existence of a file on a worker before running a build on it. It is possible to do that by setting the `canStartBuild` callback.

Here is an example that checks if there is a `vm` property set for the build request. If it is set, it checks if a file named after it exists in the `/opt/vm` folder. If the file does not exist on the given worker, refuse to run the build to force the master to select another worker.

    @defer.inlineCallbacks
    def canStartBuild(builder, wfb, request):

        vm = request.properties.get('vm', builder.config.properties.get('vm'))
        if vm:
            args = {'file': os.path.join('/opt/vm', vm)}
            cmd = RemoteCommand('stat', args, stdioLogName=None)
            cmd.worker = wfb.worker
            res = yield cmd.run(None, wfb.worker.conn, builder.name)
            if res.rc != 0:
                return False

        return True

Here is a more complete example that checks if a worker is fit to start a build. If the load average is higher than the number of CPU cores or if there is less than 2GB of free memory, refuse to run the build on that worker. Also, put that worker in quarantine to make sure no other builds are scheduled on it for a while. Otherwise, let the build start on that worker.

    class FakeBuild(object):
        properties = Properties()

    class FakeStep(object):
        build = FakeBuild()

    @defer.inlineCallbacks
    def shell(command, worker, builder):
        args = {
            'command': command,
            'logEnviron': False,
            'workdir': worker.worker_basedir,
            'want_stdout': False,
            'want_stderr': False,
        }
        cmd = RemoteCommand('shell', args, stdioLogName=None)
        cmd.worker = worker
        yield cmd.run(FakeStep(), worker.conn, builder.name)
        return cmd.rc

    @defer.inlineCallbacks
    def canStartBuild(builder, wfb, request):
        # check that load is not too high
        rc = yield shell(
            'test "$(cut -d. -f1 /proc/loadavg)" -le "$(nproc)"',
            wfb.worker, builder)
        if rc != 0:
            log.msg('loadavg is too high to take new builds',
                    system=repr(wfb.worker))
            wfb.worker.putInQuarantine()
            return False

        # check there is enough free memory
        sed_expr = r's/^MemAvailable:[[:space:]]+([0-9]+)[[:space:]]+kB$/\1/p'
        rc = yield shell(
            'test "$(sed -nre \'%s\' /proc/meminfo)" -gt 2000000' % sed_expr,
            wfb.worker, builder)
        if rc != 0:
            log.msg('not enough free memory to take new builds',
                    system=repr(wfb.worker))
            wfb.worker.putInQuarantine()
            return False

        # The build may now proceed.
        #
        # Prevent this worker from taking any other build while this one is
        # starting for 2 min. This leaves time for the build to start consuming
        # resources (disk, memory, cpu). When the quarantine is over, if the
        # same worker is subject to start another build, the above checks will
        # better reflect the actual state of the worker.
        wfb.worker.quarantine_timeout = 120
        wfb.worker.putInQuarantine()

        # This does not take the worker out of quarantine, it only resets the
        # timeout value to default.
        wfb.worker.resetQuarantine()

        return True

You can extend these examples using any remote command described in the Master-Worker API.