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

Generated by: LCOV version 1.14