Skip to content

Conversation

fabbione
Copy link
Contributor

@fabbione fabbione commented Aug 14, 2025

Closes: #1844

Summary by Sourcery

Enable Linux NUMA memory policy support by adding new mempolicy implementation, integrating it into the container startup sequence, updating build dependencies and configurations, and introducing comprehensive tests for various modes, flags, and error scenarios.

New Features:

  • Add libcrun_set_mempolicy to apply OCI memoryPolicy configuration at container startup
  • Introduce configure option to enable/disable NUMA support with checks for numaif.h

Enhancements:

  • Integrate mempolicy sources and headers into autotools (Makefile.am, configure.ac)
  • Invoke set_mempolicy in container initialization path

Build:

  • Add numactl-devel/libnuma-dev requirement across README, Dockerfiles, CI workflows, RPM spec, Nix derivations

Tests:

  • Add C helper and Python test suite for validating NUMA mempolicy modes, flags, nodes, and error handling

Copy link

sourcery-ai bot commented Aug 14, 2025

Reviewer's Guide

This PR introduces optional NUMA memory policy support by extending the configure system for auto-detection, implementing libcrun_set_mempolicy and integrating it into container startup, adding internal headers and helper binaries, new Python and C tests to validate mempolicy, and updating documentation and CI to install libnuma.

Sequence diagram for container startup with NUMA memory policy

sequenceDiagram
    participant Container as "libcrun_container_run_internal()"
    participant Mempolicy as "libcrun_set_mempolicy()"
    participant Kernel as "Linux Kernel (set_mempolicy syscall)"
    Container->>Mempolicy: Call libcrun_set_mempolicy(def, err)
    Mempolicy->>Kernel: syscall(__NR_set_mempolicy, ...)
    Kernel-->>Mempolicy: Return result
    Mempolicy-->>Container: Return status
    Container->>Seccomp as "setup_seccomp()": Continue startup if mempolicy succeeded
Loading

Class diagram for new and modified mempolicy-related types

classDiagram
    class runtime_spec_schema_config_schema {
        +linux : runtime_spec_schema_config_linux*
    }
    class runtime_spec_schema_config_linux {
        +memory_policy : runtime_spec_schema_config_linux_memory_policy*
    }
    class runtime_spec_schema_config_linux_memory_policy {
        +mode : char*
        +flags : char**
        +flags_len : size_t
        +nodes : char*
    }
    class libcrun_error_t
    class libcrun_set_mempolicy {
        +libcrun_set_mempolicy(def : runtime_spec_schema_config_schema*, err : libcrun_error_t*) : int
    }
    class str2int_map_t {
        +name : const char*
        +value : int
    }
    runtime_spec_schema_config_schema --> runtime_spec_schema_config_linux
    runtime_spec_schema_config_linux --> runtime_spec_schema_config_linux_memory_policy
    libcrun_set_mempolicy --> runtime_spec_schema_config_schema
    libcrun_set_mempolicy --> libcrun_error_t
    libcrun_set_mempolicy --> str2int_map_t
Loading

File-Level Changes

Change Details Files
Integrate NUMA support into the build configuration
  • Add --disable-numa flag and auto-detect numaif.h and libnuma in configure.ac
  • Use AC_CHECK_HEADERS to verify numa interface headers
  • Guard code paths with HAVE_NUMAIF_H
configure.ac
Implement mempolicy API and internal mappings
  • Create mempolicy.c with libcrun_set_mempolicy logic and syscall invocation
  • Define str2int maps and policies in mempolicy_internal.h
  • Declare public API in mempolicy.h
src/libcrun/mempolicy.c
src/libcrun/mempolicy_internal.h
src/libcrun/mempolicy.h
Wire mempolicy into container startup
  • Include mempolicy.h in container.c
  • Invoke libcrun_set_mempolicy after umask before seccomp setup
src/libcrun/container.c
Update tests and helpers for mempolicy
  • Add tests_mempolicy_helper.c to print available modes and flags
  • Add comprehensive Python tests in tests/test_mempolicy.py
  • Register new helper and Python tests in Makefile.am
tests/tests_mempolicy_helper.c
tests/test_mempolicy.py
Makefile.am
Revise dependencies and CI for NUMA support
  • Add numactl-devel/libnuma-dev to README dependency lists
  • Update all test Dockerfiles to install numactl-dev
  • Modify GitHub workflows (test.yaml, codeql-analysis.yml, release.yaml) to include libnuma-dev
  • Adjust rpm and Nix packaging to require numactl
README.md
.github/workflows/test.yaml
.github/workflows/codeql-analysis.yml
.github/workflows/release.yaml
tests/fuzzing/Dockerfile
tests/alpine-build/Dockerfile
tests/centos10-build/Dockerfile
tests/centos9-build/Dockerfile
tests/centos8-build/Dockerfile
tests/oci-validation/Dockerfile
tests/wasmedge-build/Dockerfile
tests/clang-check/Dockerfile
tests/clang-format/Dockerfile
tests/containerd/Dockerfile
tests/cri-o/Dockerfile
tests/podman/Dockerfile
rpm/Makefile
rpm/crun.spec
nix/derivation.nix
nix/overlay.nix

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@fabbione fabbione marked this pull request as draft August 14, 2025 04:15
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link

Ephemeral COPR build failed. @containers/packit-build please check.

@fabbione fabbione force-pushed the linux-memory-policy branch 4 times, most recently from bc7f8a5 to 9a21680 Compare August 14, 2025 08:41
@giuseppe
Copy link
Member

I don't think we need another dependency for this feature. We can call directly the set_mempolicy(2) syscall

@fabbione
Copy link
Contributor Author

I don't think we need another dependency for this feature. We can call directly the set_mempolicy(2) syscall

@giuseppe let´s talk when you are back from vacation :) that was one of the many questions I had in the list.

@giuseppe
Copy link
Member

Sure, but if there's anything I can answer now, feel free to ask about it :)

@fabbione
Copy link
Contributor Author

Sure, but if there's anything I can answer now, feel free to ask about it :)

not going to bother you while you are on vacation. Get off the computer :P

@fabbione fabbione force-pushed the linux-memory-policy branch 2 times, most recently from 6f7e65c to ebfeb36 Compare August 20, 2025 09:33
@fabbione
Copy link
Contributor Author

There are 2 commits related to tests: that will be moved on a separate PR. At the moment I am just playing around with different ideas.

@fabbione fabbione force-pushed the linux-memory-policy branch 10 times, most recently from dd89d67 to 13109fa Compare August 21, 2025 05:28
Copy link

TMT tests failed. @containers/packit-build please check.

@fabbione fabbione force-pushed the linux-memory-policy branch 4 times, most recently from 961fc8d to 0855812 Compare August 22, 2025 05:54
@giuseppe
Copy link
Member

giuseppe commented Aug 25, 2025

what I had in mind was something like:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>
#include <string.h>
#include <linux/mempolicy.h>

int main ()
{
  long r = syscall (SYS_set_mempolicy, MPOL_DEFAULT, NULL, 0);
  if (r == 0)
    printf ("set_mempolicy syscall succeeded\n");
  else
    {
      printf ("set_mempolicy syscall failed: %m\n");
      return 1;
    }

  return 0;
}

of course we will need some code to convert from the string representation of nodemask to the bitmask value accepted by set_mempolicy (is it equivalent to the code we already have in cpuset_string_to_bitmask? If we are lucky we can just use this function).

@fabbione fabbione force-pushed the linux-memory-policy branch from 3b55373 to 1bafdcc Compare August 26, 2025 10:05
@giuseppe
Copy link
Member

there is a PR to mark nodes as OPTIONAL in the specs: opencontainers/runtime-spec#1294

@fabbione fabbione force-pushed the linux-memory-policy branch 2 times, most recently from faf9160 to e5881ed Compare September 8, 2025 05:20
Copy link

TMT tests failed. @containers/packit-build please check.

@fabbione fabbione force-pushed the linux-memory-policy branch from e5881ed to cc42a2f Compare September 8, 2025 05:35
@fabbione fabbione marked this pull request as ready for review September 8, 2025 05:53
@fabbione
Copy link
Contributor Author

fabbione commented Sep 8, 2025

@giuseppe the current CI failures are unrelated to this PR.

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and found some issues that need to be addressed.

Blocking issues:

  • Potential buffer overflow risk in memcpy to nmask_final. (link)
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `src/libcrun/mempolicy.c:113` </location>
<code_context>
+          memcpy (nmask_final, bitmask, bitmask_size);
+        }
+
+      r = syscall (__NR_set_mempolicy, mpol_mode, nmask_final, MAX_NUMA_NODES - 1);
+      if (r < 0)
+        {
</code_context>

<issue_to_address>
MAX_NUMA_NODES - 1 may not be correct for set_mempolicy.

Please verify the kernel API documentation for set_mempolicy, as passing MAX_NUMA_NODES - 1 may result in off-by-one errors or nodes being ignored.
</issue_to_address>

### Comment 2
<location> `src/libcrun/mempolicy.c:110` </location>
<code_context>
+
+          nmask_final = nmask;
+          memset (nmask_final, 0, sizeof (nmask));
+          memcpy (nmask_final, bitmask, bitmask_size);
+        }
+
</code_context>

<issue_to_address>
Potential buffer overflow risk in memcpy to nmask_final.

Please verify that bitmask_size does not exceed sizeof(nmask) before calling memcpy to prevent buffer overflow.
</issue_to_address>

### Comment 3
<location> `src/libcrun/mempolicy.c:60` </location>
<code_context>
+  size_t i = 0;
+  int ret = 0;
+  long r = 0;
+  long unsigned int nmask[MAX_NUMA_NODES / (sizeof (unsigned long) * 8)];
+  long unsigned int *nmask_final = NULL;
+
</code_context>

<issue_to_address>
Consider using static_assert or runtime check for nmask sizing.

The current sizing may lead to incorrect allocation if MAX_NUMA_NODES is not divisible by (sizeof(unsigned long) * 8). Adding a static_assert or runtime check will help prevent allocation errors.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
  long unsigned int nmask[MAX_NUMA_NODES / (sizeof (unsigned long) * 8)];
  long unsigned int *nmask_final = NULL;
=======
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
  static_assert(MAX_NUMA_NODES % (sizeof(unsigned long) * 8) == 0, "MAX_NUMA_NODES must be divisible by (sizeof(unsigned long) * 8)");
#endif
  if (MAX_NUMA_NODES % (sizeof(unsigned long) * 8) != 0) {
    libcrun_error_set (err, "MAX_NUMA_NODES (%lu) is not divisible by (sizeof(unsigned long) * 8) (%lu)", (unsigned long)MAX_NUMA_NODES, (unsigned long)(sizeof(unsigned long) * 8));
    return -1;
  }
  long unsigned int nmask[MAX_NUMA_NODES / (sizeof (unsigned long) * 8)];
  long unsigned int *nmask_final = NULL;
>>>>>>> REPLACE

</suggested_fix>

### Comment 4
<location> `src/libcrun/mempolicy_internal.h:51` </location>
<code_context>
+ * the warn in mempolicy.c will indicate that an update is required.
+ * MPOL_WEIGHTED_INTERLEAVE has been introduced in MPOL_MAX 7 (kernel 6.9+)
+ * and some distros still has older kernel interfaces */
+str2int_map_t mpol_mode_map[] = {
+  { "MPOL_DEFAULT", MPOL_DEFAULT },
+  { "MPOL_PREFERRED", MPOL_PREFERRED },
</code_context>

<issue_to_address>
Global variable definition in header may cause multiple definition errors.

Mark these variables as static or move them to a source file to prevent multiple definition errors.
</issue_to_address>

### Comment 5
<location> `src/libcrun/mempolicy_internal.h:41` </location>
<code_context>
+#  define MPOL_F_STATIC_NODES (1 << 15)
+#endif
+
+typedef struct
+{
+  const char *name;
</code_context>

<issue_to_address>
Consider moving the typedef and mapping arrays from the header to the source file and marking them static.

```suggestion
mempolicy_internal.h  // slimmed down to only the macros you need
#ifndef MEMPOLICY_INTERNAL_H
#define MEMPOLICY_INTERNAL_H

#ifdef USE_NUMA_IF
# include <numaif.h>
#else
/* follow runc go implementation and redefine everything here */
/* Policies */
# define MPOL_DEFAULT              0
# define MPOL_PREFERRED            1
# define MPOL_BIND                 2
# define MPOL_INTERLEAVE           3
# define MPOL_LOCAL                4
# define MPOL_PREFERRED_MANY       5
# define MPOL_WEIGHTED_INTERLEAVE  6

/* Flags for set_mempolicy, specified in mode */
# define MPOL_F_NUMA_BALANCING     (1 << 13)
# define MPOL_F_RELATIVE_NODES     (1 << 14)
# define MPOL_F_STATIC_NODES       (1 << 15)
#endif

#endif /* MEMPOLICY_INTERNAL_H */
```

```suggestion
mempolicy.c  // move all maps and the typedef here as static symbols
#include "mempolicy_internal.h"

typedef struct {
  const char *name;
  int         value;
} str2int_map_t;

static str2int_map_t mpol_mode_map[] = {
  { "MPOL_DEFAULT",             MPOL_DEFAULT },
  { "MPOL_PREFERRED",           MPOL_PREFERRED },
  { "MPOL_BIND",                MPOL_BIND },
  { "MPOL_INTERLEAVE",          MPOL_INTERLEAVE },
  { "MPOL_LOCAL",               MPOL_LOCAL },
  { "MPOL_PREFERRED_MANY",      MPOL_PREFERRED_MANY },
#ifdef MPOL_WEIGHTED_INTERLEAVE
  { "MPOL_WEIGHTED_INTERLEAVE", MPOL_WEIGHTED_INTERLEAVE },
#endif
  { NULL,                       -1 }
};

static str2int_map_t mpol_flag_map[] = {
#ifdef MPOL_F_NUMA_BALANCING
  { "MPOL_F_NUMA_BALANCING",    MPOL_F_NUMA_BALANCING },
#endif
#ifdef MPOL_F_RELATIVE_NODES
  { "MPOL_F_RELATIVE_NODES",    MPOL_F_RELATIVE_NODES },
#endif
#ifdef MPOL_F_STATIC_NODES
  { "MPOL_F_STATIC_NODES",      MPOL_F_STATIC_NODES },
#endif
  { NULL,                       -1 }
};
```

Removing the typedef and arrays from the header and marking them `static` in the `.c` file keeps all functionality intact, cuts down your public API, and simplifies dependency management.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

memcpy (nmask_final, bitmask, bitmask_size);
}

r = syscall (__NR_set_mempolicy, mpol_mode, nmask_final, MAX_NUMA_NODES - 1);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): MAX_NUMA_NODES - 1 may not be correct for set_mempolicy.

Please verify the kernel API documentation for set_mempolicy, as passing MAX_NUMA_NODES - 1 may result in off-by-one errors or nodes being ignored.


nmask_final = nmask;
memset (nmask_final, 0, sizeof (nmask));
memcpy (nmask_final, bitmask, bitmask_size);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Potential buffer overflow risk in memcpy to nmask_final.

Please verify that bitmask_size does not exceed sizeof(nmask) before calling memcpy to prevent buffer overflow.

Comment on lines 60 to 99
long unsigned int nmask[MAX_NUMA_NODES / (sizeof (unsigned long) * 8)];
long unsigned int *nmask_final = NULL;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Consider using static_assert or runtime check for nmask sizing.

The current sizing may lead to incorrect allocation if MAX_NUMA_NODES is not divisible by (sizeof(unsigned long) * 8). Adding a static_assert or runtime check will help prevent allocation errors.

Suggested change
long unsigned int nmask[MAX_NUMA_NODES / (sizeof (unsigned long) * 8)];
long unsigned int *nmask_final = NULL;
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
static_assert(MAX_NUMA_NODES % (sizeof(unsigned long) * 8) == 0, "MAX_NUMA_NODES must be divisible by (sizeof(unsigned long) * 8)");
#endif
if (MAX_NUMA_NODES % (sizeof(unsigned long) * 8) != 0) {
libcrun_error_set (err, "MAX_NUMA_NODES (%lu) is not divisible by (sizeof(unsigned long) * 8) (%lu)", (unsigned long)MAX_NUMA_NODES, (unsigned long)(sizeof(unsigned long) * 8));
return -1;
}
long unsigned int nmask[MAX_NUMA_NODES / (sizeof (unsigned long) * 8)];
long unsigned int *nmask_final = NULL;

* the warn in mempolicy.c will indicate that an update is required.
* MPOL_WEIGHTED_INTERLEAVE has been introduced in MPOL_MAX 7 (kernel 6.9+)
* and some distros still has older kernel interfaces */
str2int_map_t mpol_mode_map[] = {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Global variable definition in header may cause multiple definition errors.

Mark these variables as static or move them to a source file to prevent multiple definition errors.

# define MPOL_F_STATIC_NODES (1 << 15)
#endif

typedef struct
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider moving the typedef and mapping arrays from the header to the source file and marking them static.

Suggested change
typedef struct
mempolicy_internal.h // slimmed down to only the macros you need
#ifndef MEMPOLICY_INTERNAL_H
#define MEMPOLICY_INTERNAL_H
#ifdef USE_NUMA_IF
# include <numaif.h>
#else
/* follow runc go implementation and redefine everything here */
/* Policies */
# define MPOL_DEFAULT 0
# define MPOL_PREFERRED 1
# define MPOL_BIND 2
# define MPOL_INTERLEAVE 3
# define MPOL_LOCAL 4
# define MPOL_PREFERRED_MANY 5
# define MPOL_WEIGHTED_INTERLEAVE 6
/* Flags for set_mempolicy, specified in mode */
# define MPOL_F_NUMA_BALANCING (1 << 13)
# define MPOL_F_RELATIVE_NODES (1 << 14)
# define MPOL_F_STATIC_NODES (1 << 15)
#endif
#endif /* MEMPOLICY_INTERNAL_H */
Suggested change
typedef struct
mempolicy.c // move all maps and the typedef here as static symbols
#include "mempolicy_internal.h"
typedef struct {
const char *name;
int value;
} str2int_map_t;
static str2int_map_t mpol_mode_map[] = {
{ "MPOL_DEFAULT", MPOL_DEFAULT },
{ "MPOL_PREFERRED", MPOL_PREFERRED },
{ "MPOL_BIND", MPOL_BIND },
{ "MPOL_INTERLEAVE", MPOL_INTERLEAVE },
{ "MPOL_LOCAL", MPOL_LOCAL },
{ "MPOL_PREFERRED_MANY", MPOL_PREFERRED_MANY },
#ifdef MPOL_WEIGHTED_INTERLEAVE
{ "MPOL_WEIGHTED_INTERLEAVE", MPOL_WEIGHTED_INTERLEAVE },
#endif
{ NULL, -1 }
};
static str2int_map_t mpol_flag_map[] = {
#ifdef MPOL_F_NUMA_BALANCING
{ "MPOL_F_NUMA_BALANCING", MPOL_F_NUMA_BALANCING },
#endif
#ifdef MPOL_F_RELATIVE_NODES
{ "MPOL_F_RELATIVE_NODES", MPOL_F_RELATIVE_NODES },
#endif
#ifdef MPOL_F_STATIC_NODES
{ "MPOL_F_STATIC_NODES", MPOL_F_STATIC_NODES },
#endif
{ NULL, -1 }
};

Removing the typedef and arrays from the header and marking them static in the .c file keeps all functionality intact, cuts down your public API, and simplifies dependency management.

Comment on lines +247 to +245
if cid is not None:
run_crun_command(["delete", "-f", cid])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid conditionals in tests. (no-conditionals-in-tests)

ExplanationAvoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

Comment on lines +254 to +252
if check_mempolicy_prerequisites():
return 77
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid conditionals in tests. (no-conditionals-in-tests)

ExplanationAvoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

if " local " not in out.splitlines()[1]:
sys.stderr.write("# Unable to find ' local ' in /proc/self/numa_maps\n")
sys.stderr.write(out)
return -1
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid conditionals in tests. (no-conditionals-in-tests)

ExplanationAvoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

Comment on lines +273 to +271
if cid is not None:
run_crun_command(["delete", "-f", cid])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid conditionals in tests. (no-conditionals-in-tests)

ExplanationAvoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

Comment on lines +280 to +278
if check_mempolicy_prerequisites():
return 77
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid conditionals in tests. (no-conditionals-in-tests)

ExplanationAvoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

configure.ac Outdated
Comment on lines 176 to 182
dnl numa
AC_ARG_ENABLE([numa],
AS_HELP_STRING([--disable-numa], [Ignore numa and disable support]))
AS_IF([test "x$enable_numa" != "xno"], [
AC_CHECK_HEADERS([numaif.h], [], [AC_MSG_ERROR([*** Missing numa interface headers])])
])

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can check for linux/mempolicy.h and have a hard fail on it (if any issue comes from that, we can re-evaluate making it configurable).


#ifdef USE_NUMA_IF
/* use system header for testing / CI */
# include <numaif.h>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the defines below are defined in the linux/mempolicy.h file.

We can drop the libnuma dependency completely from the PR and always include linux/mempolicy.h so we don't have to redefine these symbols

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could switch to linux/mempolicy, but we need 2 versions of it.

One that matches CI build OS runtime for testing (we need to know the current supported features) and one that has all the features enabled to match what runc does.

The former covers CI, the latter adds the ability to build crun and run on any kernel installed on the system, regardless of the build environment. I think we discussed it briefly already. Either way, the redefine is to mimic runc implementation 1:1.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could #ifdef these symbols. I don't see the point in having two versions if anyway we need to keep the list updated to do what the runtime-specs define.

In the CI we we build on some old systems (Centos 8), so we can easily see what symbols are not not defined there and ifdef them or define them if necessary.

We don't care about building on an old system and expecting it to get all the features you have in a newer version of the libraries. In any case we are not testing what is supported by the underlying kernel at runtime (it is build system VS latest version of the header files).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, then I need to drastically reduce the test matrix as well. Right now I am covering everything that is supposed to be supported by the underlying kernel.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don´t think that will work. linux/mempolicy.h uses enum to define modes, and we can´t #ifdef on enum. If we build using older kernels, then we miss features.

@giuseppe
Copy link
Member

giuseppe commented Sep 8, 2025

thanks for the new version! Left some inline comments, and also, could you hook crun features to show the numa support?

As reference, this is what I get with runc:

{
    "ociVersionMin": "1.0.0",
    "ociVersionMax": "1.2.1+dev",
    "hooks": [
        "prestart",
        "createRuntime",
        "createContainer",
        "startContainer",
        "poststart",
        "poststop"
    ],
    "mountOptions": [
        "async",
        "atime",
        "bind",
        "defaults",
        "dev",
        "diratime",
        "dirsync",
        "exec",
        "iversion",
        "lazytime",
        "loud",
        "mand",
        "noatime",
        "nodev",
        "nodiratime",
        "noexec",
        "noiversion",
        "nolazytime",
        "nomand",
        "norelatime",
        "nostrictatime",
        "nosuid",
        "nosymfollow",
        "private",
        "ratime",
        "rbind",
        "rdev",
        "rdiratime",
        "relatime",
        "remount",
        "rexec",
        "rnoatime",
        "rnodev",
        "rnodiratime",
        "rnoexec",
        "rnorelatime",
        "rnostrictatime",
        "rnosuid",
        "rnosymfollow",
        "ro",
        "rprivate",
        "rrelatime",
        "rro",
        "rrw",
        "rshared",
        "rslave",
        "rstrictatime",
        "rsuid",
        "rsymfollow",
        "runbindable",
        "rw",
        "shared",
        "silent",
        "slave",
        "strictatime",
        "suid",
        "symfollow",
        "sync",
        "tmpcopyup",
        "unbindable"
    ],
    "linux": {
        "namespaces": [
            "cgroup",
            "ipc",
            "mount",
            "network",
            "pid",
            "time",
            "user",
            "uts"
        ],
        "capabilities": [
            "CAP_CHOWN",
            "CAP_DAC_OVERRIDE",
            "CAP_DAC_READ_SEARCH",
            "CAP_FOWNER",
            "CAP_FSETID",
            "CAP_KILL",
            "CAP_SETGID",
            "CAP_SETUID",
            "CAP_SETPCAP",
            "CAP_LINUX_IMMUTABLE",
            "CAP_NET_BIND_SERVICE",
            "CAP_NET_BROADCAST",
            "CAP_NET_ADMIN",
            "CAP_NET_RAW",
            "CAP_IPC_LOCK",
            "CAP_IPC_OWNER",
            "CAP_SYS_MODULE",
            "CAP_SYS_RAWIO",
            "CAP_SYS_CHROOT",
            "CAP_SYS_PTRACE",
            "CAP_SYS_PACCT",
            "CAP_SYS_ADMIN",
            "CAP_SYS_BOOT",
            "CAP_SYS_NICE",
            "CAP_SYS_RESOURCE",
            "CAP_SYS_TIME",
            "CAP_SYS_TTY_CONFIG",
            "CAP_MKNOD",
            "CAP_LEASE",
            "CAP_AUDIT_WRITE",
            "CAP_AUDIT_CONTROL",
            "CAP_SETFCAP",
            "CAP_MAC_OVERRIDE",
            "CAP_MAC_ADMIN",
            "CAP_SYSLOG",
            "CAP_WAKE_ALARM",
            "CAP_BLOCK_SUSPEND",
            "CAP_AUDIT_READ",
            "CAP_PERFMON",
            "CAP_BPF",
            "CAP_CHECKPOINT_RESTORE"
        ],
        "cgroup": {
            "v1": true,
            "v2": true,
            "systemd": true,
            "systemdUser": true,
            "rdma": true
        },
        "seccomp": {
            "enabled": true,
            "actions": [
                "SCMP_ACT_ALLOW",
                "SCMP_ACT_ERRNO",
                "SCMP_ACT_KILL",
                "SCMP_ACT_KILL_PROCESS",
                "SCMP_ACT_KILL_THREAD",
                "SCMP_ACT_LOG",
                "SCMP_ACT_NOTIFY",
                "SCMP_ACT_TRACE",
                "SCMP_ACT_TRAP"
            ],
            "operators": [
                "SCMP_CMP_EQ",
                "SCMP_CMP_GE",
                "SCMP_CMP_GT",
                "SCMP_CMP_LE",
                "SCMP_CMP_LT",
                "SCMP_CMP_MASKED_EQ",
                "SCMP_CMP_NE"
            ],
            "archs": [
                "SCMP_ARCH_AARCH64",
                "SCMP_ARCH_ARM",
                "SCMP_ARCH_MIPS",
                "SCMP_ARCH_MIPS64",
                "SCMP_ARCH_MIPS64N32",
                "SCMP_ARCH_MIPSEL",
                "SCMP_ARCH_MIPSEL64",
                "SCMP_ARCH_MIPSEL64N32",
                "SCMP_ARCH_PPC",
                "SCMP_ARCH_PPC64",
                "SCMP_ARCH_PPC64LE",
                "SCMP_ARCH_RISCV64",
                "SCMP_ARCH_S390",
                "SCMP_ARCH_S390X",
                "SCMP_ARCH_X32",
                "SCMP_ARCH_X86",
                "SCMP_ARCH_X86_64"
            ],
            "knownFlags": [
                "SECCOMP_FILTER_FLAG_TSYNC",
                "SECCOMP_FILTER_FLAG_SPEC_ALLOW",
                "SECCOMP_FILTER_FLAG_LOG"
            ],
            "supportedFlags": [
                "SECCOMP_FILTER_FLAG_TSYNC",
                "SECCOMP_FILTER_FLAG_SPEC_ALLOW",
                "SECCOMP_FILTER_FLAG_LOG"
            ]
        },
        "apparmor": {
            "enabled": true
        },
        "selinux": {
            "enabled": true
        },
        "intelRdt": {
            "enabled": true
        },
        "memoryPolicy": {
            "modes": [
                "MPOL_BIND",
                "MPOL_DEFAULT",
                "MPOL_INTERLEAVE",
                "MPOL_LOCAL",
                "MPOL_PREFERRED",
                "MPOL_PREFERRED_MANY",
                "MPOL_WEIGHTED_INTERLEAVE"
            ],
            "flags": [
                "MPOL_F_NUMA_BALANCING",
                "MPOL_F_RELATIVE_NODES",
                "MPOL_F_STATIC_NODES"
            ]
        },
        "mountExtensions": {
            "idmap": {
                "enabled": true
            }
        },
        "netDevices": {
            "enabled": true
        }
    },
    "annotations": {
        "io.github.seccomp.libseccomp.version": "2.5.5",
        "org.opencontainers.runc.checkpoint.enabled": "true",
        "org.opencontainers.runc.commit": "v1.3.0-rc.1-204-ge5dca5d7",
        "org.opencontainers.runc.version": "1.3.0-rc.1+dev\n"
    },
    "potentiallyUnsafeConfigAnnotations": [
        "bundle",
        "org.systemd.property.",
        "org.criu.config"
    ]
}

@fabbione fabbione force-pushed the linux-memory-policy branch 2 times, most recently from 0803fd3 to 55d9f2a Compare September 8, 2025 08:23
Copy link

Ephemeral COPR build failed. @containers/packit-build please check.

Copy link

TMT tests failed. @containers/packit-build please check.

@fabbione fabbione force-pushed the linux-memory-policy branch 2 times, most recently from 1f1031f to ad1e4f8 Compare September 9, 2025 06:49
Copy link
Member

@giuseppe giuseppe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

few inline comments and I think it is ready to go. Could you please change the commit message to not list the changelog?

@fabbione fabbione force-pushed the linux-memory-policy branch from ad1e4f8 to 10d400f Compare September 9, 2025 09:08
Closes: containers#1844

Signed-off-by: Fabio M. Di Nitto <fdinitto@redhat.com>
@fabbione fabbione force-pushed the linux-memory-policy branch from 10d400f to 6ead513 Compare September 9, 2025 09:16
Copy link

Ephemeral COPR build failed. @containers/packit-build please check.

Copy link
Member

@giuseppe giuseppe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!

LGTM

Copy link

TMT tests failed. @containers/packit-build please check.

@giuseppe giuseppe merged commit ddc0953 into containers:main Sep 9, 2025
52 of 64 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

implement Linux memory policy

3 participants