hubcio commented on code in PR #3615:
URL: https://github.com/apache/iggy/pull/3615#discussion_r3535835633


##########
core/shard_allocator/src/lib.rs:
##########
@@ -287,39 +335,66 @@ impl ShardAllocator {
     pub fn to_shard_assignments(&self) -> Result<Vec<ShardInfo>, 
ShardingError> {
         match &self.allocation {
             CpuAllocation::All => {
+                // `available_parallelism` already accounts for both the
+                // affinity mask and any cgroup CPU quota, so a
+                // quota-restricted process gets proportionally fewer shards.
                 let available_cpus = available_parallelism()
                     .map_err(|err| ShardingError::Other {
                         msg: format!("Failed to get available_parallelism: 
{:?}", err),
                     })?
                     .get();
 
-                let shard_assignments: Vec<_> = (0..available_cpus)
-                    .map(|cpu_id| ShardInfo {
-                        cpu_set: HashSet::from([cpu_id]),
-                        numa_node: None,
-                    })
-                    .collect();
+                if !self.pin_cores {
+                    info!("Using all available CPU cores ({available_cpus} 
shards, unpinned)");
+                    return Ok(unpinned(available_cpus));
+                }
+
+                let allowed = allowed_cpus();
+                let shard_assignments =
+                    pinned_from_allowed(&allowed, 
available_cpus.min(allowed.len()))?;
 
                 info!(
-                    "Using all available CPU cores ({} shards with affinity)",
-                    shard_assignments.len()
+                    "Using all available CPU cores ({} shards pinned within 
allowed set {:?})",
+                    shard_assignments.len(),
+                    allowed
                 );
 
                 Ok(shard_assignments)
             }
             CpuAllocation::Count(count) => {
-                let shard_assignments = (0..*count)
-                    .map(|cpu_id| ShardInfo {
-                        cpu_set: HashSet::from([cpu_id]),
-                        numa_node: None,
-                    })
+                if !self.pin_cores {
+                    info!("Using {count} shard(s), unpinned");
+                    return Ok(unpinned(*count));
+                }
+
+                let allowed = allowed_cpus();
+                let shard_assignments = pinned_from_allowed(&allowed, *count)?;
+                let cores: Vec<usize> = shard_assignments
+                    .iter()
+                    .flat_map(|shard| shard.cpu_set.iter().copied())
                     .collect();
 
-                info!("Using {count} shards with affinity to cores 
0..{count}");
+                info!("Using {count} shard(s) with affinity to cores 
{cores:?}");
 
                 Ok(shard_assignments)
             }
             CpuAllocation::Range(start, end) => {
+                if !self.pin_cores {
+                    info!(
+                        "Using {} shard(s) for range {start}..{end}, unpinned",
+                        end - start
+                    );
+                    return Ok(unpinned(end - start));

Review Comment:
   `unpinned(end - start)` underflows when `start > end` - debug build panics 
on the subtract, release wraps to a huge value and `unpinned()` then tries to 
allocate a vec of ~`usize::MAX` `ShardInfo` and aborts. not reachable in 
production today since both boot paths run `ShardingConfig::validate` first and 
it rejects `start >= end`, but `to_shard_assignments` is a pub fn in an 
internal crate and the server-ng `validate_sharding_runtime_knobs` net doesn't 
cover cpu_allocation, so a direct/test/embedded caller hits it. cheap fix - 
reject `start >= end` in this arm so the allocator self-guards instead of 
trusting the caller.



##########
core/server/src/shard/system/stats.rs:
##########
@@ -88,6 +88,14 @@ impl IggyShard {
                 stats.written_bytes = disk_usage.total_written_bytes.into();
 
                 stats.threads_count = process.tasks().map(|t| t.len() as 
u32).unwrap_or(0);
+
+                if let Some(limits) = process
+                    .cgroup_limits()
+                    .filter(|limits| limits.total_memory < sys.total_memory())
+                {
+                    stats.total_memory = limits.total_memory.into();
+                    stats.available_memory = limits.free_memory.into();

Review Comment:
   under a memory-capped cgroup this sets `available_memory` to `memory.max - 
memory.current` (or lower if a parent cgroup is tighter), and `memory.current` 
charges reclaimable page cache as used. iggy is cache-heavy, so on a warm 
instance `memory.current` climbs toward the cap and `available_memory` trends 
toward ~0 even though the kernel reclaims that cache long before OOM - it 
under-reports real headroom, which is the opposite of what a per-instance stats 
endpoint wants. for an honest number you'd want a cgroup 
MemAvailable-equivalent: `memory.max - (memory.current - reclaimable file 
cache)`, where reclaimable is `inactive_file + active_file` from `memory.stat` 
- sysinfo's `free_memory` doesn't do that, so it needs reading `memory.stat` 
directly. also this override only fires when a memory limit is set (the `total 
< host total` filter just above), so an instance confined by cpuset alone still 
reports host-wide memory - multi-instance setups need a memory cap too. 
re-scoping `total
 _memory` to the cap is correct.



##########
core/configs/src/server_config/validators.rs:
##########
@@ -488,9 +489,27 @@ impl Validatable<ConfigurationError> for ShardingConfig {
                     );
                     return Err(ConfigurationError::InvalidConfigurationValue);
                 }
-                if *end > available_cpus {
+                if !self.pin_cores {
+                    if *end - *start > available_cpus {
+                        eprintln!(
+                            "Invalid sharding configuration: cpu_allocation 
range {start}..{end} yields {} shards, exceeding available CPU cores 
{available_cpus}",
+                            *end - *start
+                        );
+                        return 
Err(ConfigurationError::InvalidConfigurationValue);
+                    }
+                    return Ok(());
+                }
+                if *end - *start > available_cpus {

Review Comment:
   this `*end - *start > available_cpus` check and its `eprintln!` are 
identical to the one in the `!self.pin_cores` branch above. hoist it above the 
`if !self.pin_cores` split so it runs once, and leave only the allowed-set 
membership check gated behind pinning. the `start >= end` guard earlier still 
protects the subtraction. saves ~7 lines.



##########
core/shard_allocator/src/lib.rs:
##########
@@ -287,39 +335,66 @@ impl ShardAllocator {
     pub fn to_shard_assignments(&self) -> Result<Vec<ShardInfo>, 
ShardingError> {
         match &self.allocation {
             CpuAllocation::All => {
+                // `available_parallelism` already accounts for both the
+                // affinity mask and any cgroup CPU quota, so a
+                // quota-restricted process gets proportionally fewer shards.
                 let available_cpus = available_parallelism()
                     .map_err(|err| ShardingError::Other {
                         msg: format!("Failed to get available_parallelism: 
{:?}", err),
                     })?
                     .get();
 
-                let shard_assignments: Vec<_> = (0..available_cpus)
-                    .map(|cpu_id| ShardInfo {
-                        cpu_set: HashSet::from([cpu_id]),
-                        numa_node: None,
-                    })
-                    .collect();
+                if !self.pin_cores {
+                    info!("Using all available CPU cores ({available_cpus} 
shards, unpinned)");
+                    return Ok(unpinned(available_cpus));
+                }
+
+                let allowed = allowed_cpus();
+                let shard_assignments =
+                    pinned_from_allowed(&allowed, 
available_cpus.min(allowed.len()))?;
 
                 info!(
-                    "Using all available CPU cores ({} shards with affinity)",
-                    shard_assignments.len()
+                    "Using all available CPU cores ({} shards pinned within 
allowed set {:?})",
+                    shard_assignments.len(),
+                    allowed
                 );
 
                 Ok(shard_assignments)
             }
             CpuAllocation::Count(count) => {
-                let shard_assignments = (0..*count)
-                    .map(|cpu_id| ShardInfo {
-                        cpu_set: HashSet::from([cpu_id]),
-                        numa_node: None,
-                    })
+                if !self.pin_cores {
+                    info!("Using {count} shard(s), unpinned");
+                    return Ok(unpinned(*count));
+                }
+
+                let allowed = allowed_cpus();
+                let shard_assignments = pinned_from_allowed(&allowed, *count)?;
+                let cores: Vec<usize> = shard_assignments

Review Comment:
   this `cores` vec is only built to log it, and since `pinned_from_allowed` 
takes `allowed.iter().take(count)` in order it's just `&allowed[..*count]`. log 
the slice directly and drop the vec + `flat_map`.



##########
core/shard_allocator/src/lib.rs:
##########
@@ -287,39 +335,66 @@ impl ShardAllocator {
     pub fn to_shard_assignments(&self) -> Result<Vec<ShardInfo>, 
ShardingError> {
         match &self.allocation {
             CpuAllocation::All => {
+                // `available_parallelism` already accounts for both the
+                // affinity mask and any cgroup CPU quota, so a
+                // quota-restricted process gets proportionally fewer shards.
                 let available_cpus = available_parallelism()
                     .map_err(|err| ShardingError::Other {
                         msg: format!("Failed to get available_parallelism: 
{:?}", err),
                     })?
                     .get();
 
-                let shard_assignments: Vec<_> = (0..available_cpus)
-                    .map(|cpu_id| ShardInfo {
-                        cpu_set: HashSet::from([cpu_id]),
-                        numa_node: None,
-                    })
-                    .collect();
+                if !self.pin_cores {
+                    info!("Using all available CPU cores ({available_cpus} 
shards, unpinned)");
+                    return Ok(unpinned(available_cpus));
+                }
+
+                let allowed = allowed_cpus();
+                let shard_assignments =
+                    pinned_from_allowed(&allowed, 
available_cpus.min(allowed.len()))?;
 
                 info!(
-                    "Using all available CPU cores ({} shards with affinity)",
-                    shard_assignments.len()
+                    "Using all available CPU cores ({} shards pinned within 
allowed set {:?})",
+                    shard_assignments.len(),
+                    allowed
                 );
 
                 Ok(shard_assignments)
             }
             CpuAllocation::Count(count) => {
-                let shard_assignments = (0..*count)
-                    .map(|cpu_id| ShardInfo {
-                        cpu_set: HashSet::from([cpu_id]),
-                        numa_node: None,
-                    })
+                if !self.pin_cores {
+                    info!("Using {count} shard(s), unpinned");
+                    return Ok(unpinned(*count));
+                }
+
+                let allowed = allowed_cpus();
+                let shard_assignments = pinned_from_allowed(&allowed, *count)?;
+                let cores: Vec<usize> = shard_assignments
+                    .iter()
+                    .flat_map(|shard| shard.cpu_set.iter().copied())
                     .collect();
 
-                info!("Using {count} shards with affinity to cores 
0..{count}");
+                info!("Using {count} shard(s) with affinity to cores 
{cores:?}");
 
                 Ok(shard_assignments)
             }
             CpuAllocation::Range(start, end) => {
+                if !self.pin_cores {
+                    info!(
+                        "Using {} shard(s) for range {start}..{end}, unpinned",
+                        end - start
+                    );
+                    return Ok(unpinned(end - start));
+                }
+
+                // An explicit range is operator intent for exact cores; honor

Review Comment:
   the comment says the range is honored as-is, but the validator already caps 
a pinned range by `available_parallelism()` before this runs, so a range wider 
than the quota never reaches here. minor, but the comment overstates what this 
arm actually sees.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to