From: Adrian Freihofer <[email protected]> The hostfwd=tcp: entries in QB_SLIRP_OPT are only a wishlist for the debug server ports: runqemu moves forward to the next free host port whenever the wished-for one is already taken, e.g. by a parallel oe-selftest worker's qemu. Only the ssh port can be recovered afterwards. The debug server ones silently pointed the debugger at nothing instead. lldb-server's spawned gdbserver cannot cope with a moved port at all: its client is told the target port to reconnect to and there is no hook to tell it about a remapped host one.
If the test allocates a pool of host ports that is free right beforehand and holds it for the debug servers, runqemu is expected to grant every wish in it 1:1, since nothing else can take one of them in between. Reserve such a block and pass it to the first devtool ide-sdk invocation, which bakes the ports into QB_SLIRP_OPT before the image is built. The debug server ports are therefore already final at that point, with no remapping left to resolve. devtool ide-sdk still has to be re-run once qemu is up regardless, and needs the same block passed again: not because of these ports, but because it is the only invocation that knows the live qemu IP and the ssh port, which is not covered by the reservation and can still be moved by runqemu's own port picking, e.g. against a second worker's default 2222. The reservation keeps its own lock directory rather than runqemu's, since holding runqemu's locks is exactly what would make it consider the reserved ports taken and move the forwards away again. Having the Linux kernel itself arbitrate the ports, by requesting them from qemu dynamically via QMP once it is running, would be a more robust fix and remove the need for this pool altogether, but that is a bigger refactoring of the runqemu/qemu integration than warranted here. Signed-off-by: Adrian Freihofer <[email protected]> --- meta/lib/oeqa/selftest/cases/devtool.py | 64 +++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index 490a1d0bf0..5bb191ff47 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -5,9 +5,11 @@ # import errno +import fcntl import os import re import shutil +import socket import subprocess import tempfile import threading @@ -3066,6 +3068,12 @@ class RunCmdBackground: class DevtoolIdeSdkTests(DevtoolBase): MAGIC_STRING_ORIG = "Magic: 123456789" + SLIRP_PORT_BLOCK_SIZE = 32 + # runqemu locks /tmp/qemu-port-locks itself while picking the slirp host + # ports, so the reservation below needs a lock directory of its own: + # holding runqemu's locks would make it consider the reserved ports taken + # and move the forwards away again. + SLIRP_PORT_LOCK_DIR = '/tmp/oe-selftest-ide-sdk-port-locks' def setUp(self): super().setUp() @@ -3520,6 +3528,50 @@ class DevtoolIdeSdkTests(DevtoolBase): self.assertIn('hostfwd=tcp:127.0.0.1:2222-:22', bbappend_content, 'SSH slirp port forward missing from QB_SLIRP_OPT') + def _reserve_slirp_port_block(self): + """Reserve a block of host ports for this test's debug servers and return its first port. + + runqemu moves a QB_SLIRP_OPT forward to the next host port whenever the + wished-for one is already taken, and lldb-server's spawned gdbserver + has no way to tell its client about a remapped host port, so the wishes + have to be granted 1:1. Every parallel oe-selftest worker otherwise + starts from the same default port, so claim a block that is free right + now and hold it for the rest of the test. + """ + os.makedirs(self.SLIRP_PORT_LOCK_DIR, exist_ok=True) + for start in range(1234, 20000, self.SLIRP_PORT_BLOCK_SIZE): + block = range(start, start + self.SLIRP_PORT_BLOCK_SIZE) + # runqemu forwards these for ssh/telnet, handing one of them to a + # debug server as well would collide. + if 2222 in block or 2323 in block: + continue + locks = [] + for port in block: + lock = open(os.path.join(self.SLIRP_PORT_LOCK_DIR, '%d.lock' % port), 'w') + try: + fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB) + except OSError: + lock.close() + break + locks.append(lock) + # The lock only coordinates with other ide-sdk selftests, so + # confirm nothing unrelated is holding the port either. + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe: + try: + probe.bind(('127.0.0.1', port)) + except OSError: + break + else: + for lock in locks: + self.addCleanup(lock.close) + self.logger.debug('Reserved slirp host ports %d-%d', + start, start + self.SLIRP_PORT_BLOCK_SIZE - 1) + return start + for lock in locks: + lock.close() + self.fail('Could not reserve %d consecutive free host ports' + % self.SLIRP_PORT_BLOCK_SIZE) + def _verify_nfs_debug_rootfs(self, testimage, nfs): """Verify the NFS debug rootfs was extracted and its runqemu launch helper generated.""" nfs_rootfs = os.path.join(self.workspacedir, 'nfs-exports', testimage, nfs) @@ -3607,7 +3659,13 @@ class DevtoolIdeSdkTests(DevtoolBase): self._meson_recipe_name, "meson.build", testimage) package_opts = self._ide_sdk_package_opts() nfs_opts = ' --nfs=%s' % nfs_export if nfs else '' - runCmd('devtool ide-sdk %s -c --ide=code --ide=none %s%s' % (testimage, package_opts, nfs_opts), + # runqemu only grants a QB_SLIRP_OPT wish while the host port is free, + # and lldb-server's spawned gdbserver needs host == target, so give the + # debug servers a block no parallel worker uses (see + # _reserve_slirp_port_block); both invocations must agree on it. + port_opts = ' -G %d' % self._reserve_slirp_port_block() if slirp else '' + runCmd('devtool ide-sdk %s -c --ide=code --ide=none %s%s%s' % ( + testimage, package_opts, nfs_opts, port_opts), output_log=self._cmd_logger) if slirp: @@ -3651,8 +3709,8 @@ class DevtoolIdeSdkTests(DevtoolBase): # not known at the time of the initial ide-sdk invocation. # --skip-bitbake also skips the NFS rootfs (re-)extraction, which # would otherwise wipe the directory the target has mounted. - bitbake_sdk_cmd = 'devtool ide-sdk %s %s --skip-bitbake --ide=code --ide=none %s%s' % ( - testimage, target_options, package_opts, nfs_opts) + bitbake_sdk_cmd = 'devtool ide-sdk %s %s --skip-bitbake --ide=code --ide=none %s%s%s' % ( + testimage, target_options, package_opts, nfs_opts, port_opts) runCmd(bitbake_sdk_cmd, output_log=self._cmd_logger) # Verify the debugger is available and functional on host -- 2.55.0
-=-=-=-=-=-=-=-=-=-=-=- Links: You receive all messages sent to this group. View/Reply Online (#246087): https://lists.openembedded.org/g/openembedded-core/message/246087 Mute This Topic: https://lists.openembedded.org/mt/121302030/21656 Group Owner: [email protected] Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [[email protected]] -=-=-=-=-=-=-=-=-=-=-=-
