LCOV - code coverage report
Current view: top level - app/shared/commands/configure - cpuset.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 132 0.0 %
Date: 2026-08-06 05:23:51 Functions: 0 8 0.0 %

          Line data    Source code
       1             : /* The cpuset stage creates a cgroup v2 "isolated" cpuset partition
       2             :    over the Firedancer tile CPUs, at /sys/fs/cgroup/<name> where <name>
       3             :    is the instance [name] from the configuration (default "fd1"),
       4             :    allowing multiple Firedancer instances on one host to own distinct
       5             :    partitions.
       6             : 
       7             :    An isolated partition removes its CPUs from the scheduler domains of
       8             :    the rest of the system: no load balancing onto them, and processes
       9             :    outside the cgroup cannot be scheduled (or set affinity) onto them
      10             :    at all.  This is the runtime-configurable equivalent of the
      11             :    `isolcpus=` boot parameter, and the strongest tool available for
      12             :    keeping foreign tasks off tile CPUs.
      13             : 
      14             :    IMPORTANT INTERACTION: once the partition exists, ONLY processes
      15             :    inside the cgroup may run on tile CPUs.  The tile launcher
      16             :    (execve_tile in run.c) joins the cgroup before setting a fixed
      17             :    tile's affinity.  Setting affinity to a partitioned CPU from
      18             :    outside the cgroup fails with EINVAL; run `configure fini cpuset`
      19             :    to remove the partition if that happens with tooling that predates
      20             :    this stage.
      21             : 
      22             :    Notes:
      23             :    - Requires cgroup v2 (unified hierarchy) with the cpuset controller,
      24             :      and kernel support for the "isolated" partition type (5.15+ for
      25             :      root-adjacent partitions; probed at init by the partition write).
      26             :    - The cgroup directory is created directly under the cgroup root.
      27             :      systemd tolerates foreign cgroups there, but external cleanup may
      28             :      remove it; the check stage detects this and reports unconfigured.
      29             :    - Sibling exclusivity: the partition's CPUs must not appear in any
      30             :      sibling cgroup's explicit cpuset.cpus.  Typical systemd slices
      31             :      leave cpuset.cpus empty (meaning "parent's effective"), which
      32             :      does not conflict.  If an operator has explicitly assigned tile
      33             :      CPUs to another cgroup, the partition write fails and init
      34             :      reports it. */
      35             : 
      36             : #include "configure.h"
      37             : #include "fd_cpu_isolation.h"
      38             : 
      39             : #include <errno.h>
      40             : #include <fcntl.h>
      41             : #include <unistd.h>
      42             : #include <sys/stat.h>
      43             : 
      44           0 : #define NAME "cpuset"
      45             : 
      46             : #define CGROUP_ROOT     "/sys/fs/cgroup"
      47             : #define SUBTREE_CONTROL CGROUP_ROOT "/cgroup.subtree_control"
      48             : 
      49             : /* cgroup_path formats the per-instance cgroup path or one of its files
      50             :    into buf.  file may be NULL for the cgroup directory itself. */
      51             : 
      52             : static char const *
      53             : cgroup_path( char             buf[ static PATH_MAX ],
      54             :              config_t const * config,
      55           0 :              char const *     file ) {
      56           0 :   FD_TEST( fd_cstr_printf_check( buf, PATH_MAX, NULL, CGROUP_ROOT "/%s%s%s",
      57           0 :                                  config->name, file ? "/" : "", file ? file : "" ) );
      58           0 :   return buf;
      59           0 : }
      60             : 
      61             : /* write_cstr writes val to the file at path.  Returns 1 on success,
      62             :    0 on open/write failure with errno set; callers decide severity
      63             :    (typically FD_LOG_ERR with call-site context). */
      64             : 
      65             : static int
      66             : write_cstr( char const * path,
      67           0 :             char const * val ) {
      68           0 :   int fd = open( path, O_WRONLY );
      69           0 :   if( FD_UNLIKELY( fd<0 ) ) return 0;
      70           0 :   ulong val_len = strlen( val );
      71           0 :   int err = 0;
      72           0 :   if( FD_UNLIKELY( write( fd, val, val_len )!=(long)val_len ) ) err = errno;
      73           0 :   if( FD_UNLIKELY( close( fd ) ) ) FD_LOG_ERR(( "close(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
      74           0 :   if( FD_UNLIKELY( err ) ) {
      75           0 :     errno = err; /* preserve write() errno across close() */
      76           0 :     return 0;
      77           0 :   }
      78           0 :   return 1;
      79           0 : }
      80             : 
      81             : /* read_cstr reads the first line of path into buf.  Returns 1 on
      82             :    success, 0 if the file does not exist (ENOENT).  Any other error is
      83             :    fatal: these are kernel-provided files, and an unexpected failure
      84             :    reading them means something is wrong that we should not paper
      85             :    over. */
      86             : 
      87             : static int
      88             : read_cstr( char const * path,
      89             :            char *       buf,
      90           0 :            ulong        buf_sz ) {
      91           0 :   int fd = open( path, O_RDONLY );
      92           0 :   if( FD_UNLIKELY( fd<0 ) ) {
      93           0 :     if( FD_LIKELY( errno==ENOENT ) ) return 0;
      94           0 :     FD_LOG_ERR(( "open(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
      95           0 :   }
      96           0 :   long n = read( fd, buf, buf_sz-1UL );
      97           0 :   if( FD_UNLIKELY( n<0L ) ) FD_LOG_ERR(( "read(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
      98           0 :   if( FD_UNLIKELY( close( fd ) ) ) FD_LOG_ERR(( "close(%s) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
      99           0 :   buf[ n ] = '\0';
     100           0 :   char * nl = strchr( buf, '\n' );
     101           0 :   if( FD_LIKELY( nl ) ) *nl = '\0';
     102           0 :   return 1;
     103           0 : }
     104             : 
     105             : static int
     106           0 : enabled( config_t const * config ) {
     107           0 :   (void)config;
     108             :   /* cgroup v2 unified hierarchy with the cpuset controller.  On v1 or
     109             :      hybrid systems the unified root has no cgroup.controllers file (or
     110             :      no cpuset in it) and this stage cannot work.  Only genuine absence
     111             :      disables the stage; any other failure reading the file is fatal
     112             :      (in read_cstr). */
     113           0 :   char controllers[ 256 ];
     114           0 :   if( FD_UNLIKELY( !read_cstr( CGROUP_ROOT "/cgroup.controllers", controllers, sizeof(controllers) ) ) ) return 0;
     115           0 :   return NULL!=strstr( controllers, "cpuset" );
     116           0 : }
     117             : 
     118             : static void
     119             : init_perm( fd_cap_chk_t *   chk,
     120           0 :            config_t const * config ) {
     121           0 :   char path[ PATH_MAX ];
     122           0 :   static char reason[ PATH_MAX+64UL ];
     123           0 :   FD_TEST( fd_cstr_printf_check( reason, sizeof(reason), NULL, "create and configure the cgroup `%s`",
     124           0 :                                  cgroup_path( path, config, NULL ) ) );
     125           0 :   fd_cap_chk_root( chk, NAME, reason );
     126           0 : }
     127             : 
     128             : static void
     129           0 : init( config_t const * config ) {
     130           0 :   FD_CPUSET_DECL( part_cpus );
     131           0 :   fd_cpu_isolation_partition_cpus( part_cpus, &config->topo );
     132             : 
     133           0 :   char cgroup[ PATH_MAX ]; cgroup_path( cgroup, config, NULL );
     134             : 
     135           0 :   if( FD_UNLIKELY( !fd_cpuset_cnt( part_cpus ) ) ) {
     136           0 :     FD_LOG_WARNING(( "no fixed tile CPUs in topology; not creating `%s`", cgroup ));
     137           0 :     return;
     138           0 :   }
     139             : 
     140           0 :   FD_CPUSET_DECL( host );
     141           0 :   fd_cpu_isolation_host_cpus( host );
     142           0 :   FD_CPUSET_DECL( housekeeping );
     143           0 :   fd_cpuset_subtract( housekeeping, host, part_cpus );
     144           0 :   if( FD_UNLIKELY( !fd_cpuset_cnt( housekeeping ) ) )
     145           0 :     FD_LOG_ERR(( "all host CPUs are assigned to Firedancer tiles; cannot isolate them all (the kernel needs at "
     146           0 :                  "least one housekeeping CPU)" ));
     147             : 
     148           0 :   char list[ FD_CPU_ISOLATION_LIST_MAX ];
     149           0 :   fd_cpu_isolation_format_list( list, sizeof(list), part_cpus );
     150             : 
     151             :   /* The parent must delegate the cpuset controller before a child can
     152             :      use it.  "+cpuset" is idempotent. */
     153           0 :   FD_LOG_NOTICE(( "%sRUN: `echo \"+cpuset\" > " SUBTREE_CONTROL "`%s", fd_log_style_dim(), fd_log_style_normal() ));
     154           0 :   if( FD_UNLIKELY( !write_cstr( SUBTREE_CONTROL, "+cpuset" ) ) )
     155           0 :     FD_LOG_ERR(( "could not enable the cpuset controller in `" SUBTREE_CONTROL "` (%i-%s)", errno, fd_io_strerror( errno ) ));
     156             : 
     157           0 :   FD_LOG_NOTICE(( "%sRUN: `mkdir %s`%s", fd_log_style_dim(), cgroup , fd_log_style_normal() ));
     158           0 :   if( FD_UNLIKELY( mkdir( cgroup, 0755 ) && errno!=EEXIST ) )
     159           0 :     FD_LOG_ERR(( "mkdir(%s) failed (%i-%s)", cgroup, errno, fd_io_strerror( errno ) ));
     160             : 
     161           0 :   char path[ PATH_MAX ];
     162           0 :   cgroup_path( path, config, "cpuset.cpus" );
     163           0 :   FD_LOG_NOTICE(( "%sRUN: `echo \"%s\" > %s`%s", fd_log_style_dim(), list, path , fd_log_style_normal() ));
     164           0 :   if( FD_UNLIKELY( !write_cstr( path, list ) ) )
     165           0 :     FD_LOG_ERR(( "write(%s,\"%s\") failed (%i-%s)", path, list, errno, fd_io_strerror( errno ) ));
     166             : 
     167           0 :   cgroup_path( path, config, "cpuset.cpus.partition" );
     168           0 :   FD_LOG_NOTICE(( "%sRUN: `echo \"isolated\" > %s`%s", fd_log_style_dim(), path , fd_log_style_normal() ));
     169           0 :   if( FD_UNLIKELY( !write_cstr( path, "isolated" ) ) )
     170           0 :     FD_LOG_ERR(( "write(%s,\"isolated\") failed (%i-%s). The kernel may be too old for isolated cpuset "
     171           0 :                  "partitions, or a sibling cgroup may have explicitly claimed one of the CPUs `%s`",
     172           0 :                  path, errno, fd_io_strerror( errno ), list ));
     173           0 : }
     174             : 
     175             : static int
     176             : fini( config_t const * config,
     177           0 :       int              pre_init ) {
     178           0 :   (void)pre_init;
     179             : 
     180           0 :   char cgroup[ PATH_MAX ]; cgroup_path( cgroup, config, NULL );
     181           0 :   if( FD_UNLIKELY( access( cgroup, F_OK ) ) ) return 0;
     182             : 
     183             :   /* Downgrade to a regular member cgroup first so the CPUs return to
     184             :      the system scheduler domains even if rmdir fails (e.g. because
     185             :      Firedancer is still running in it).  ENOENT is tolerated here and
     186             :      below: the access() check above races with concurrent removal (a
     187             :      second `configure fini`, or systemd cleaning up foreign cgroups),
     188             :      and someone else deleting the cgroup is this stage's goal state,
     189             :      not an error. */
     190           0 :   char path[ PATH_MAX ];
     191           0 :   cgroup_path( path, config, "cpuset.cpus.partition" );
     192           0 :   if( FD_UNLIKELY( !write_cstr( path, "member" ) && errno!=ENOENT ) )
     193           0 :     FD_LOG_ERR(( "write(%s,\"member\") failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
     194             : 
     195           0 :   FD_LOG_NOTICE(( "%sRUN: `rmdir %s`%s", fd_log_style_dim(), cgroup , fd_log_style_normal() ));
     196           0 :   if( FD_UNLIKELY( rmdir( cgroup ) && errno!=ENOENT ) ) {
     197           0 :     if( FD_LIKELY( errno==EBUSY ) ) {
     198           0 :       FD_LOG_ERR(( "Removal of the CPU isolation cgroup `%s` failed because processes are still inside it, "
     199           0 :                    "likely because Firedancer is still running. The partition was downgraded so its CPUs are "
     200           0 :                    "returned to the system, but the cgroup itself could not be removed. Stop the validator and "
     201           0 :                    "run `%s configure fini cpuset` again, or remove it manually with `rmdir %s`",
     202           0 :                    cgroup, FD_BINARY_NAME, cgroup ));
     203           0 :     } else {
     204           0 :       FD_LOG_ERR(( "rmdir(%s) failed (%i-%s)", cgroup, errno, fd_io_strerror( errno ) ));
     205           0 :     }
     206           0 :   }
     207           0 :   return 1;
     208           0 : }
     209             : 
     210             : static configure_result_t
     211             : check( config_t const * config,
     212           0 :        int              check_type ) {
     213           0 :   (void)check_type;
     214             : 
     215           0 :   FD_CPUSET_DECL( part_cpus );
     216           0 :   fd_cpu_isolation_partition_cpus( part_cpus, &config->topo );
     217           0 :   if( FD_UNLIKELY( !fd_cpuset_cnt( part_cpus ) ) ) CONFIGURE_OK();
     218             : 
     219           0 :   char cgroup[ PATH_MAX ]; cgroup_path( cgroup, config, NULL );
     220           0 :   if( FD_UNLIKELY( access( cgroup, F_OK ) ) )
     221           0 :     NOT_CONFIGURED( "`%s` does not exist", cgroup );
     222             : 
     223           0 :   char path[ PATH_MAX ];
     224           0 :   cgroup_path( path, config, "cpuset.cpus" );
     225           0 :   char cpus[ FD_CPU_ISOLATION_LIST_MAX ];
     226           0 :   if( FD_UNLIKELY( !read_cstr( path, cpus, sizeof(cpus) ) ) )
     227           0 :     NOT_CONFIGURED( "`%s` does not exist", path ); /* cgroup removed concurrently */
     228             : 
     229           0 :   FD_CPUSET_DECL( current );
     230           0 :   if( FD_UNLIKELY( !fd_cpu_isolation_parse_list( current, cpus ) ) )
     231           0 :     FD_LOG_ERR(( "failed to parse `%s` (\"%s\")", path, cpus ));
     232             : 
     233           0 :   if( FD_UNLIKELY( !fd_cpuset_eq( current, part_cpus ) ) ) {
     234           0 :     char expected[ FD_CPU_ISOLATION_LIST_MAX ];
     235           0 :     fd_cpu_isolation_format_list( expected, sizeof(expected), part_cpus );
     236           0 :     PARTIALLY_CONFIGURED( "`%s` is \"%s\", expected \"%s\"", path, cpus, expected );
     237           0 :   }
     238             : 
     239           0 :   cgroup_path( path, config, "cpuset.cpus.partition" );
     240           0 :   char partition[ 64 ];
     241           0 :   if( FD_UNLIKELY( !read_cstr( path, partition, sizeof(partition) ) ) )
     242           0 :     NOT_CONFIGURED( "`%s` does not exist", path ); /* cgroup removed concurrently */
     243             : 
     244             :   /* An invalid partition reads as e.g. "isolated invalid (...)". */
     245           0 :   if( FD_UNLIKELY( strcmp( partition, "isolated" ) ) )
     246           0 :     PARTIALLY_CONFIGURED( "`%s` is \"%s\", expected \"isolated\"", path, partition );
     247             : 
     248           0 :   CONFIGURE_OK();
     249           0 : }
     250             : 
     251             : configure_stage_t fd_cfg_stage_cpuset = {
     252             :   .name            = NAME,
     253             :   .always_recreate = 0,
     254             :   .enabled         = enabled,
     255             :   .init_perm       = init_perm,
     256             :   .fini_perm       = init_perm,
     257             :   .init            = init,
     258             :   .fini            = fini,
     259             :   .check           = check,
     260             : };
     261             : 
     262             : #undef NAME
     263             : #undef CGROUP_ROOT
     264             : #undef SUBTREE_CONTROL

Generated by: LCOV version 1.14