1

I have a fairly robust CI test for a C++ library, these tests (around 50) run over the same docker image but in different machines.

In one machine ("A") all the memcheck (valgrind) tests pass (i.e. no memory leaks). In the other ("B"), all tests produce the same valgrind error below.

51/56 MemCheck #51: combinations.cpp.x ....................***Exception: SegFault  0.14 sec
valgrind: m_libcfile.c:66 (vgPlain_safe_fd): Assertion 'newfd >= VG_(fd_hard_limit)' failed.
Cannot find memory tester output file: /builds/user/boost-multi/build/Testing/Temporary/MemoryChecker.51.log

The machines are very similar, both are intel i7. The only difference I can think of is that one is:

A. Ubuntu 22.10, Linux 5.19.0-29, docker 20.10.16

and the other:

B. Fedora 37, Linux 6.1.7-200.fc37.x86_64, docker 20.10.23

and perhaps some configuration of docker I don't know about.

Is there some configuration of docker that might generate the difference? or of the kernel? or some option in valgrind to workaround this problem?

I know for a fact that in real machines (not docker) valgrind doesn't produce any memory error.

The options I use for valgrind are always -leak-check=yes --num-callers=51 --trace-children=yes --leak-check=full --track-origins=yes --gen-suppressions=all. Valgrind version in the image is 3.19.0-1 from the debian:testing image.

Note that this isn't an error reported by valgrind, it is an error within valgrind. Perhaps after all, the only difference is that Ubuntu version of valgrind is compiled in release mode and the error is just ignored. (<-- this doesn't make sense, valgrind is the same in both cases because the docker image is the same).

I tried removing --num-callers=51 or setting it at 12 (default value), to no avail.

alfC
  • 14,261
  • 4
  • 67
  • 118
  • 2
    Inconsistent results are typically a sign of *undefined behavior* in your code. Try using a sanitizer as well (e.g. build with `-fsanitize=undefined` or `-fsanitize=memory` in different test-runs). – Some programmer dude Jan 31 '23 at 03:31
  • 1
    @Someprogrammerdude, good point, yes, I am doing UBSAN and ASAN all the time. https://gitlab.com/correaa/boost-multi/-/blob/master/.gitlab-ci.yml#L17 – alfC Jan 31 '23 at 03:34
  • valgrind crashing (which is what is happening here) is very suspicious. – pm100 Jan 31 '23 at 04:47
  • @pm100, yes, and it happens in a single host, if I use the gitlab CI runner in my Ubuntu machine it works, if I use it the gitlab shared runners (virtual machines hosted by gitlab) it works. If I run in this particular Fedora machine it fails! The specific failed assertion is reported on the internet, but the references are very old and there is no clear explanation. – alfC Jan 31 '23 at 04:50
  • i would file a bug report to them – pm100 Jan 31 '23 at 04:53
  • 1
    https://bugs.kde.org/buglist.cgi?bug_status=__all__&content=vgPlain_safe_fd&no_redirect=1&order=Importance&product=valgrind&query_format=specific – pm100 Jan 31 '23 at 04:57
  • @pm100, yes, I have been looking there, they are circular conversations, seems to classified sometimes as OSX weirdness (I am not using OSX). – alfC Jan 31 '23 at 05:00
  • @pm100, I found a mention to ulimit in one of the post, that allowed me to device a workaround, see my solution. – alfC Jan 31 '23 at 07:11
  • 1
    What does docker say the valuse is with `prlimit`? – Paul Floyd Jan 31 '23 at 09:01
  • @PaulFloyd, in Ubuntu 22.10 (real machine) it says prlimit says NOFILE 1024, in docker running inside it, says NOFILE 1048576 (both different but both consistent with `ulimit -n`). – alfC Jan 31 '23 at 16:02
  • @PaulFloyd, in Fedora 37 (real machine) it says prlimit NOFILE 1024, in docker running inside it, says NOFILE 1073741816 (respectively consistent with `ulimit -n`) – alfC Jan 31 '23 at 17:21
  • 1
    Could you try my proposed patch here https://bugs.kde.org/show_bug.cgi?id=465435 ? It should make the error message clearer. – Paul Floyd Feb 14 '23 at 06:17
  • @PaulFloyd, thank you for the patch. Do you happen to have instructions on how to compile valgrind from scratch? I am not super trained in compiling software in the wild. – alfC Feb 14 '23 at 08:18
  • Build instructions are here https://valgrind.org/downloads/repository.html you will also need to download and apply the patch from bugs.ked.org – Paul Floyd Feb 14 '23 at 08:24

2 Answers2

3

I found a difference between the images and the real machine and a workaround. It has to do with the number of file descriptors. (This was pointed out briefly in one of the threads on valgind bug issues on Mac OS https://bugs.kde.org/show_bug.cgi?id=381815#c0)

Inside the docker image running in Ubuntu 22.10:

ulimit -n
1048576

Inside the docker image running in Fedora 37:

ulimit -n
1073741816

(which looks like a ridiculous number or an overflow)

In the Fedora 37 and the Ubuntu 22.10 real machines:

ulimit -n
1024

So, doing this in the CI recipe, "solved" the problem:

- ulimit -n       # reports current value
- ulimit -n 1024  # workaround neededed by valgrind in docker running in Fedora 37
- ctest ... (with memcheck)

I have no idea why this workaround works.

For reference:

$ ulimit --help
...
      -n    the maximum number of open file descriptors
alfC
  • 14,261
  • 4
  • 67
  • 118
2

First off, "you are doing it wrong" with your Valgrind arguments. For CI I recommend a two stage approach. Use as many default arguments as possible for the CI run (--trace-children=yes may well be necessary but not the others). If your codebase is leaky then you may need to check for leaks, but if you can maintain a zero leak policy (or only suppressed leaks) then you can tell if there are new leaks from the summary. After your CI detects an issue you can run again with the kitchen sink options to get full information. Your runs will be significantly faster without all those options.

Back to the question.

Valgrind is trying to dup() some file (the guest exe, a tempfile or something like that). The system call to do this is failing.

A billion files is ridiculous.

Valgrind will try to call prlimit RLIMIT_NOFILE, with a fallback call to rlimit, and a second fallback to setting the limit to 1024.

To realy see what is going on you need to modify the Valgrind source (m_main.c, setup_file_descriptors, set local show to True). With this change I see

fd limits: host, before: cur 65535 max 65535
fd limits: host,  after: cur 65535 max 65535
fd limits: guest       : cur 65523 max 65523

Otherwise with strace I see

2049  prlimit64(0, RLIMIT_NOFILE, NULL, {rlim_cur=65535, rlim_max=65535}) = 0
2049  prlimit64(0, RLIMIT_NOFILE, {rlim_cur=65535, rlim_max=65535}, NULL) = 0

(all the above on RHEL 7.6 amd64)

EDIT: Note that the above show Valgrind querying and setting the resource limit. If you use ulimit to lower the limit before running Valgrind, then Valgrind will try to honour that limit. Also note that Valgrind reserves a small number (8) of files for its own use.

EDIT 2: I've made a change to Valgrind so that when it fails to make a file descriptor in its reserved range it prints a message suggesting that you set the descriptor limit in your shell before running Valgrind. Since this is a bug in Docker it is not possible to fix it in Valgrind (we can't tell that the bogus Docker descriptor resource limit really is bogus and even if we could what would we do - a binary search to try to find the real descriptor resource limit?).

Paul Floyd
  • 5,530
  • 5
  • 29
  • 43
  • I do have a zero leak policy and I am also paranoid wrt to them. So I activated all the options. (Memcheck is not the bottleneck in CI but the compilation). So, do you recommend doing a test with no option and create a report with the full options `valgrind test.x || valgrind -leak-check=yes --num-callers=51 --trace-children=yes --leak-check=full --track-origins=yes --gen-suppressions=all`? Or do you mean both chained (replace `||` by `&&`) ? – alfC Jan 31 '23 at 16:06
  • I would just run `valgrind --trace-children=yes` in CI. Then whenever there are any regressions start a debug session in a terminal and use all the options (and maybe vgdb). However if your tests run quickly enough under Valgrind then leave the options on. – Paul Floyd Jan 31 '23 at 16:52
  • Thank you for the explanation, at the end it seems that valgrind is tripped by excessive number of `nofile` or `nlimit -n` even if it doesn't need to open that many files. In any case, is there a workaround? maybe valgrind can be tell to ignore the reported value by the OS. If you ask me, I think this is a bug in docker or in the kernel used by Fedora. – alfC Jan 31 '23 at 17:24
  • 1
    Using ulimit as you do is OK. – Paul Floyd Jan 31 '23 at 17:27