LCOV - code coverage report
Current view: top level - app/shared/commands/configure - kworkers.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 84 0.0 %
Date: 2026-10-22 18:10:43 Functions: 0 8 0.0 %

          Line data    Source code
       1             : /* The kworkers stage removes Firedancer tile CPUs from the cpumask of
       2             :    unbound kernel workqueues, so that deferred kernel work (writeback,
       3             :    vmstat refresh, various driver bottom halves, and notably the
       4             :    nohz_full remote tick offload) runs on housekeeping CPUs instead of
       5             :    stealing time from spinning tiles.  The idle hyperthread siblings
       6             :    the cpuset stage isolates are excluded too.
       7             : 
       8             :    The kernel exposes this as a global hex mask at
       9             :    /sys/devices/virtual/workqueue/cpumask.  Per-CPU (bound) kworkers
      10             :    are unaffected: they are idle unless work is queued on their CPU,
      11             :    which the irq-affinity and cpuset stages minimize.
      12             : 
      13             :    The kernel rejects a mask with no online CPU, so like irq-affinity
      14             :    this stage refuses to run if tiles cover every host CPU.
      15             : 
      16             :    fini restores the mask to all host CPUs (the kernel default),
      17             :    which may over-restore if the operator had their own narrower mask
      18             :    before init; the previous mask is not persisted anywhere. */
      19             : 
      20             : #include "configure.h"
      21             : #include "fd_cpu_isolation.h"
      22             : 
      23             : #include <errno.h>
      24             : #include <fcntl.h>
      25             : #include <unistd.h>
      26             : 
      27           0 : #define NAME "kworkers"
      28             : 
      29           0 : #define WQ_CPUMASK_PATH "/sys/devices/virtual/workqueue/cpumask"
      30             : 
      31             : /* read_wq_cpumask reads and parses the workqueue cpumask.  Fails fast
      32             :    on any error except the file being absent (which enabled() already
      33             :    gates on, but guard against removal races anyway): this is a
      34             :    kernel-provided file with a fixed format, so parse or IO failures
      35             :    mean something is wrong that we should not paper over. */
      36             : 
      37             : static void
      38           0 : read_wq_cpumask( fd_cpuset_t cpuset[ static fd_cpuset_word_cnt ] ) {
      39           0 :   int fd = open( WQ_CPUMASK_PATH, O_RDONLY );
      40           0 :   if( FD_UNLIKELY( fd<0 ) ) FD_LOG_ERR(( "open(" WQ_CPUMASK_PATH ") failed (%i-%s)", errno, fd_io_strerror( errno ) ));
      41             : 
      42           0 :   char mask[ FD_CPU_ISOLATION_MASK_MAX+64UL ];
      43           0 :   long mask_len = read( fd, mask, sizeof(mask)-1UL );
      44           0 :   if( FD_UNLIKELY( mask_len<=0L ) ) FD_LOG_ERR(( "read(" WQ_CPUMASK_PATH ") failed (%i-%s)", errno, fd_io_strerror( errno ) ));
      45           0 :   if( FD_UNLIKELY( close( fd ) ) ) FD_LOG_ERR(( "close(" WQ_CPUMASK_PATH ") failed (%i-%s)", errno, fd_io_strerror( errno ) ));
      46           0 :   mask[ mask_len ] = '\0';
      47             : 
      48           0 :   if( FD_UNLIKELY( !fd_cpu_isolation_parse_mask( cpuset, mask ) ) )
      49           0 :     FD_LOG_ERR(( "failed to parse `" WQ_CPUMASK_PATH "` (\"%s\")", mask ));
      50           0 : }
      51             : 
      52             : static void
      53           0 : write_wq_cpumask( fd_cpuset_t const * cpuset ) {
      54           0 :   char mask[ FD_CPU_ISOLATION_MASK_MAX ];
      55           0 :   fd_cpu_isolation_format_mask( mask, sizeof(mask), cpuset );
      56           0 :   ulong mask_len = strlen( mask );
      57             : 
      58           0 :   FD_LOG_NOTICE(( "%sRUN: `echo \"%s\" > " WQ_CPUMASK_PATH "`%s", fd_log_style_dim(), mask, fd_log_style_normal() ));
      59             : 
      60           0 :   int fd = open( WQ_CPUMASK_PATH, O_WRONLY );
      61           0 :   if( FD_UNLIKELY( fd<0 ) ) FD_LOG_ERR(( "open(" WQ_CPUMASK_PATH ") failed (%i-%s)", errno, fd_io_strerror( errno ) ));
      62           0 :   FD_CPUSET_DECL( before );
      63           0 :   int have_before = fd_cpu_isolation_read_wq_mask( before );
      64           0 :   if( FD_UNLIKELY( write( fd, mask, mask_len )!=(long)mask_len ) )
      65           0 :     FD_LOG_ERR(( "write(" WQ_CPUMASK_PATH ") failed (%i-%s)", errno, fd_io_strerror( errno ) ));
      66           0 :   if( FD_UNLIKELY( close( fd ) ) ) FD_LOG_ERR(( "close(" WQ_CPUMASK_PATH ") failed (%i-%s)", errno, fd_io_strerror( errno ) ));
      67           0 :   if( have_before ) fd_cpu_isolation_warn_wq_change( WQ_CPUMASK_PATH, before );
      68           0 : }
      69             : 
      70             : static int
      71           0 : enabled( config_t const * config ) {
      72           0 :   (void)config;
      73             :   /* Not all kernels expose the unbound workqueue mask (CONFIG_SYSFS,
      74             :      ancient kernels).  If absent there is nothing to configure. */
      75           0 :   return 0==access( WQ_CPUMASK_PATH, F_OK );
      76           0 : }
      77             : 
      78             : static void
      79             : init_perm( fd_cap_chk_t *   chk,
      80           0 :            config_t const * config FD_PARAM_UNUSED ) {
      81           0 :   fd_cap_chk_root( chk, NAME, "modify `" WQ_CPUMASK_PATH "`" );
      82           0 : }
      83             : 
      84             : static void
      85             : fini_perm( fd_cap_chk_t *   chk,
      86           0 :            config_t const * config FD_PARAM_UNUSED ) {
      87           0 :   fd_cap_chk_root( chk, NAME, "modify `" WQ_CPUMASK_PATH "`" );
      88           0 : }
      89             : 
      90             : static void
      91           0 : init( config_t const * config ) {
      92           0 :   FD_CPUSET_DECL( tile_cpus );
      93           0 :   fd_cpu_isolation_banned_cpus( tile_cpus, &config->topo );
      94             : 
      95           0 :   FD_CPUSET_DECL( allowed );
      96           0 :   fd_cpu_isolation_online_cpus( allowed );
      97           0 :   fd_cpuset_subtract( allowed, allowed, tile_cpus );
      98             : 
      99           0 :   if( FD_UNLIKELY( !fd_cpuset_cnt( allowed ) ) )
     100           0 :     FD_LOG_ERR(( "all host CPUs are assigned to Firedancer tiles; cannot reserve any CPU for kernel workqueues" ));
     101             : 
     102           0 :   write_wq_cpumask( allowed );
     103           0 : }
     104             : 
     105             : static int
     106             : fini( config_t const * config,
     107           0 :       int              pre_init ) {
     108           0 :   (void)config; (void)pre_init;
     109             : 
     110           0 :   FD_CPUSET_DECL( all );
     111           0 :   ulong cpu_cnt = fd_shmem_cpu_cnt();
     112           0 :   for( ulong cpu=0UL; cpu<cpu_cnt; cpu++ ) fd_cpuset_insert( all, cpu );
     113             : 
     114           0 :   FD_CPUSET_DECL( excluded );
     115           0 :   read_wq_cpumask( excluded );
     116           0 :   fd_cpuset_subtract( excluded, all, excluded );
     117             : 
     118           0 :   write_wq_cpumask( all );
     119           0 :   return !fd_cpuset_is_null( excluded );
     120           0 : }
     121             : 
     122             : static configure_result_t
     123             : check( config_t const * config,
     124           0 :        int              check_type ) {
     125           0 :   (void)check_type;
     126             : 
     127           0 :   FD_CPUSET_DECL( current );
     128           0 :   read_wq_cpumask( current );
     129             : 
     130           0 :   FD_CPUSET_DECL( tile_cpus );
     131           0 :   fd_cpu_isolation_banned_cpus( tile_cpus, &config->topo );
     132             : 
     133           0 :   if( FD_UNLIKELY( fd_cpuset_is_null( tile_cpus ) ) ) {
     134           0 :     FD_CPUSET_DECL( missing );
     135           0 :     fd_cpu_isolation_online_cpus( missing );
     136           0 :     fd_cpuset_subtract( missing, missing, current );
     137           0 :     if( FD_UNLIKELY( !fd_cpuset_is_null( missing ) ) ) {
     138           0 :       char list[ FD_CPU_ISOLATION_LIST_MAX ];
     139           0 :       fd_cpu_isolation_format_list( list, sizeof(list), missing );
     140           0 :       NOT_CONFIGURED( "kernel workqueue cpumask excludes CPUs %s but no tile CPUs are pinned", list );
     141           0 :     }
     142           0 :     CONFIGURE_OK();
     143           0 :   }
     144             : 
     145           0 :   FD_CPUSET_DECL( overlap );
     146           0 :   fd_cpuset_intersect( overlap, current, tile_cpus );
     147           0 :   if( FD_UNLIKELY( !fd_cpuset_is_null( overlap ) ) ) {
     148           0 :     char list[ FD_CPU_ISOLATION_LIST_MAX ];
     149           0 :     fd_cpu_isolation_format_list( list, sizeof(list), overlap );
     150           0 :     NOT_CONFIGURED( "kernel workqueue cpumask includes Firedancer tile CPUs or their isolated siblings %s", list );
     151           0 :   }
     152             : 
     153           0 :   CONFIGURE_OK();
     154           0 : }
     155             : 
     156             : configure_stage_t fd_cfg_stage_kworkers = {
     157             :   .name            = NAME,
     158             :   .always_recreate = 0,
     159             :   .enabled         = enabled,
     160             :   .init_perm       = init_perm,
     161             :   .fini_perm       = fini_perm,
     162             :   .init            = init,
     163             :   .fini            = fini,
     164             :   .check           = check,
     165             : };
     166             : 
     167             : #undef NAME
     168             : #undef WQ_CPUMASK_PATH

Generated by: LCOV version 1.14