// Copyright (C) 2023 Huawei Device Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Thread-safe counter for tracking active tasks or operations.
//!
//! Provides a simple, atomic counter implementation for tracking whether any
//! operations are currently active. This is useful for determining when a
//! system can safely shut down or enter an idle state.
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
/// Thread-safe counter for tracking active operations.
///
/// Uses an atomic counter to safely track whether operations are in progress
/// across multiple threads. Provides methods to increment, decrement, and check
/// if any operations are currently active.
#[derive(Clone)]
pub(crate) struct ActiveCounter {
/// Atomic counter storing the number of active operations
count: Arc<AtomicU32>,
}
impl ActiveCounter {
/// Creates a new active counter with an initial value of zero.
///
/// # Returns
///
/// A new `ActiveCounter` instance that is not active initially
pub(crate) fn new() -> Self {
Self {
count: Arc::new(AtomicU32::new(0)),
}
}
/// Increments the active count by one.
///
/// # Note
///
/// Uses `Ordering::Relaxed` as the memory ordering since this counter is
/// primarily used for liveness checks rather than precise
/// synchronization.
pub(crate) fn increment(&self) {
self.count.fetch_add(1, Ordering::Relaxed);
}
/// Decrements the active count by one.
///
/// # Note
///
/// Uses `Ordering::Relaxed` as the memory ordering. Decrementing below zero
/// will cause an underflow, which will result in the counter wrapping
/// to a large value.
pub(crate) fn decrement(&self) {
self.count.fetch_sub(1, Ordering::Relaxed);
}
/// Checks if there are any active operations.
///
/// # Returns
///
/// `true` if the count is greater than zero, indicating active operations
pub(crate) fn is_active(&self) -> bool {
// Load the current count with relaxed ordering
let count = self.count.load(Ordering::Relaxed);
info!("active count: {}", count);
count > 0
}
}
#[cfg(test)]
mod test {
use std::sync::Arc;
use std::thread;
use super::*;
// @tc.name: ut_active_counter_new
// @tc.desc: Test ActiveCounter initialization
// @tc.precon: NA
// @tc.step: 1. Create a new ActiveCounter instance
// 2. Check if is_active returns false
// @tc.expect: New counter should not be active
// @tc.type: FUNC
// @tc.require: issues#ICN16H
/// Verifies that a newly created counter reports itself as not active.
#[test]
fn ut_active_counter_new_001() {
let counter = ActiveCounter::new();
assert!(!counter.is_active());
}
// @tc.name: ut_active_counter_increment
// @tc.desc: Test single increment operation
// @tc.precon: NA
// @tc.step: 1. Create a new ActiveCounter instance
// 2. Call increment method once
// 3. Check if is_active returns true
// @tc.expect: Counter should be active after increment
// @tc.type: FUNC
// @tc.require: issues#ICN16H
/// Verifies that a single increment marks the counter as active.
#[test]
fn ut_active_counter_increment_001() {
let counter = ActiveCounter::new();
counter.increment();
assert!(counter.is_active());
}
// @tc.name: ut_active_counter_decrement
// @tc.desc: Test single decrement operation
// @tc.precon: Counter should have count > 0
// @tc.step: 1. Create a new ActiveCounter instance
// 2. Call increment method once
// 3. Call decrement method once
// 4. Check if is_active returns false
// @tc.expect: Counter should not be active after decrement
// @tc.type: FUNC
// @tc.require: issues#ICN16H
/// Verifies that a decrement following an increment returns the counter to inactive.
#[test]
fn ut_active_counter_decrement_001() {
let counter = ActiveCounter::new();
counter.increment();
counter.decrement();
assert!(!counter.is_active());
}
// @tc.name: ut_active_counter_multiple_increments
// @tc.desc: Test multiple increment operations
// @tc.precon: NA
// @tc.step: 1. Create a new ActiveCounter instance
// 2. Call increment method multiple times
// 3. Check if is_active returns true
// @tc.expect: Counter should remain active with count > 1
// @tc.type: FUNC
// @tc.require: issues#ICN16H
/// Verifies that multiple increments keep the counter in the active state.
#[test]
fn ut_active_counter_multiple_increments_001() {
let counter = ActiveCounter::new();
for _ in 0..5 {
counter.increment();
}
assert!(counter.is_active());
}
// @tc.name: ut_active_counter_multiple_decrements
// @tc.desc: Test multiple decrement operations
// @tc.precon: Counter should have count >= number of decrements
// @tc.step: 1. Create a new ActiveCounter instance
// 2. Call increment method multiple times
// 3. Call decrement method multiple times
// 4. Check if is_active returns false when count reaches 0
// @tc.expect: Counter should not be active after sufficient decrements
// @tc.type: FUNC
// @tc.require: issues#ICN16H
/// Verifies that an equal number of decrements cancels out prior increments.
#[test]
fn ut_active_counter_multiple_decrements_001() {
let counter = ActiveCounter::new();
for _ in 0..3 {
counter.increment();
}
for _ in 0..3 {
counter.decrement();
}
assert!(!counter.is_active());
}
// @tc.name: ut_active_counter_decrement_below_zero
// @tc.desc: Test decrement operation when count is 0 (negative case)
// @tc.precon: Counter should have count = 0
// @tc.step: 1. Create a new ActiveCounter instance
// 2. Call decrement method
// 3. Check if is_active returns false
// @tc.expect: Counter should remain not active, count should wrap around
// (underflow)
// @tc.type: FUNC
// @tc.require: issues#ICN16H
/// Verifies underflow behavior when decrementing a counter that is already at zero.
#[test]
fn ut_active_counter_decrement_below_zero_001() {
let counter = ActiveCounter::new();
counter.decrement(); // This will underflow to u32::MAX
assert!(counter.is_active()); // Should be true due to underflow
}
// @tc.name: sdv_active_counter_concurrent_access
// @tc.desc: Test concurrent increment and decrement operations
// @tc.precon: NA
// @tc.step: 1. Create a shared ActiveCounter instance
// 2. Spawn multiple threads to increment and decrement
// 3. Wait for all threads to complete
// 4. Check final state
// @tc.expect: Counter should be thread-safe and maintain consistency
// @tc.type: FUNC
// @tc.require: issues#ICN16H
/// Verifies thread-safe concurrent increments and decrements from multiple threads.
#[test]
fn sdv_active_counter_concurrent_access_001() {
let counter = Arc::new(ActiveCounter::new());
let mut handles = vec![];
// Spawn 5 threads, each incrementing 100 times
for _ in 0..5 {
let counter_clone = counter.clone();
let handle = thread::spawn(move || {
for _ in 0..100 {
counter_clone.increment();
}
});
handles.push(handle);
}
// Spawn 5 threads, each decrementing 100 times
for _ in 0..5 {
let counter_clone = counter.clone();
let handle = thread::spawn(move || {
for _ in 0..100 {
counter_clone.decrement();
}
});
handles.push(handle);
}
// Wait for all threads to complete
for handle in handles {
handle.join().unwrap();
}
// Since we have equal increments and decrements, counter should be 0
assert!(!counter.is_active());
}
// @tc.name: ut_active_counter_clone_behavior
// @tc.desc: Test clone behavior of ActiveCounter
// @tc.precon: NA
// @tc.step: 1. Create a new ActiveCounter instance
// 2. Clone the counter
// 3. Modify original counter
// 4. Check if cloned counter reflects changes
// @tc.expect: Cloned counter should share the same underlying state
// @tc.type: FUNC
// @tc.require: issues#ICN16H
/// Verifies that a cloned counter shares the same underlying state as the original.
#[test]
fn ut_active_counter_clone_behavior_001() {
let counter1 = ActiveCounter::new();
let counter2 = counter1.clone();
counter1.increment();
assert!(counter2.is_active()); // counter2 should see the change
counter2.decrement();
assert!(!counter1.is_active()); // counter1 should see the change
}
// @tc.name: ut_active_counter_large_count
// @tc.desc: Test ActiveCounter with large count values
// @tc.precon: NA
// @tc.step: 1. Create a new ActiveCounter instance
// 2. Increment a large number of times
// 3. Check if is_active returns true
// 4. Decrement the same number of times
// 5. Check if is_active returns false
// @tc.expect: Counter should handle large counts correctly
// @tc.type: FUNC
// @tc.require: issues#ICN16H
/// Verifies counter behavior with a large number of increment and decrement operations.
#[test]
fn ut_active_counter_large_count_001() {
let counter = ActiveCounter::new();
let large_count = 10000;
for _ in 0..large_count {
counter.increment();
}
assert!(counter.is_active());
for _ in 0..large_count {
counter.decrement();
}
assert!(!counter.is_active());
}
}