From 543fbd45de1771f5a15d416aafd3d2d2d4461cd8 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Wed, 8 Apr 2026 23:10:27 +0200 Subject: [PATCH 1/5] env: posix: Divide env into overridable sections Split posix env into sections enabled by set of defines, so that user can include some sections from default posix env while having ability to override selected sections with custom implementation. This allows to reuse default posix env implementation in custom integration without maintaining a full copy of the env files. Signed-off-by: Robert Baldyga --- env/posix/ocf_env.h | 714 ++------------------------------- env/posix/ocf_env_default.h | 770 ++++++++++++++++++++++++++++++++++++ 2 files changed, 792 insertions(+), 692 deletions(-) create mode 100644 env/posix/ocf_env_default.h diff --git a/env/posix/ocf_env.h b/env/posix/ocf_env.h index 6acc898f..25084f0b 100644 --- a/env/posix/ocf_env.h +++ b/env/posix/ocf_env.h @@ -1,6 +1,4 @@ /* - * Copyright(c) 2019-2022 Intel Corporation - * Copyright(c) 2023-2025 Huawei Technologies * Copyright(c) 2026 Unvertical * SPDX-License-Identifier: BSD-3-Clause */ @@ -8,699 +6,31 @@ #ifndef __OCF_ENV_H__ #define __OCF_ENV_H__ -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif -#ifndef __USE_GNU -#define __USE_GNU -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ocf_env_list.h" -#include "ocf_env_headers.h" -#include "ocf/ocf_err.h" -#include "utils_mpool.h" - -/* linux sector 512-bytes */ -#define ENV_SECTOR_SHIFT 9 - -#define OCF_ALLOCATOR_NAME_MAX 128 - -#define PAGE_SIZE 4096 - -#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) -#define min(a,b) MIN(a,b) - -#define ENV_PRIu64 "lu" -#define ENV_PRId64 "ld" - -typedef uint8_t u8; -typedef uint16_t u16; -typedef uint32_t u32; -typedef uint64_t u64; - -typedef uint64_t sector_t; - -#define __packed __attribute__((packed)) - -#define likely(cond) __builtin_expect(!!(cond), 1) -#define unlikely(cond) __builtin_expect(!!(cond), 0) - -/* MEMORY MANAGEMENT */ -#define ENV_MEM_NORMAL 0 -#define ENV_MEM_NOIO 0 -#define ENV_MEM_ATOMIC 0 - -/* DEBUGING */ -void env_stack_trace(void); - -#define ENV_WARN(cond, fmt...) printf(fmt) -#define ENV_WARN_ON(cond) ; -#define ENV_WARN_ONCE(cond, fmt...) ENV_WARN(cond, fmt) - -#define ENV_BUG() do {env_stack_trace(); assert(0);} while(0) -#define ENV_BUG_ON(cond) do { if (cond) ENV_BUG(); } while (0) -#define ENV_BUILD_BUG_ON(cond) _Static_assert(!(cond), "static "\ - "assertion failure") - -/* MISC UTILITIES */ -#define container_of(ptr, type, member) ({ \ - const typeof(((type *)0)->member)*__mptr = (ptr); \ - (type *)((char *)__mptr - offsetof(type, member)); }) - -#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) - -/* STRING OPERATIONS */ -#define env_memcpy(dest, dmax, src, slen) ({ \ - memcpy(dest, src, min(dmax, slen)); \ - 0; \ - }) -#define env_memset(dest, dmax, val) ({ \ - memset(dest, val, dmax); \ - 0; \ - }) -#define env_memcmp(s1, s1max, s2, s2max, diff) ({ \ - *diff = memcmp(s1, s2, min(s1max, s2max)); \ - 0; \ - }) -#define env_strdup strndup -#define env_strnlen(s, smax) strnlen(s, smax) -#define env_strncmp(s1, slen1, s2, slen2) strncmp(s1, s2, min(slen1, slen2)) -#define env_strncpy(dest, dmax, src, slen) ({ \ - strncpy(dest, src, min(dmax - 1, slen)); \ - dest[dmax - 1] = '\0'; \ - 0; \ - }) - -/* MEMORY MANAGEMENT */ -static inline void *env_malloc(size_t size, int flags) -{ - return malloc(size); -} - -static inline void *env_zalloc(size_t size, int flags) -{ - void *ptr = malloc(size); - - if (ptr) - memset(ptr, 0, size); - - return ptr; -} - -static inline void env_free(const void *ptr) -{ - free((void *)ptr); -} - -static inline void *env_vmalloc_flags(size_t size, int flags) -{ - return malloc(size); -} - -static inline void *env_vzalloc_flags(size_t size, int flags) -{ - return env_zalloc(size, 0); -} - -static inline void *env_vmalloc(size_t size) -{ - return malloc(size); -} - -static inline void *env_vzalloc(size_t size) -{ - return env_zalloc(size, 0); -} - -static inline void env_vfree(const void *ptr) -{ - free((void *)ptr); -} - -/* SECURE MEMORY MANAGEMENT */ /* - * OCF adapter can opt to take additional steps to securely allocate and free - * memory used by OCF to store cache metadata. This is to prevent other - * entities in the system from acquiring parts of OCF cache metadata via - * memory allocations. If this is not a concern in given product, secure - * alloc/free should default to vmalloc/vfree. + * Default posix environment configuration. * - * Memory returned from secure alloc is not expected to be physically continous - * nor zeroed. + * Each section in ocf_env_default.h is guarded by an OCF_ENV_POSIX_* + * define. To replace a section with a custom implementation, remove the + * corresponding define below and provide your own definitions. */ -/* default to standard memory allocations for secure allocations */ -#define SECURE_MEMORY_HANDLING 0 - -static inline void *env_secure_alloc(size_t size) -{ - void *ptr = malloc(size); - -#if SECURE_MEMORY_HANDLING - if (ptr && mlock(ptr, size)) { - free(ptr); - ptr = NULL; - } -#endif - - return ptr; -} - -static inline void env_secure_free(const void *ptr, size_t size) -{ - if (ptr) { -#if SECURE_MEMORY_HANDLING - memset(ptr, 0, size); - /* TODO: flush CPU caches ? */ - ENV_BUG_ON(munlock(ptr)); -#endif - free((void*)ptr); - } -} - -static inline uint64_t env_get_free_memory(void) -{ - return (uint64_t)(-1); -} - -/* ALLOCATOR */ -typedef struct _env_allocator env_allocator; - -env_allocator *env_allocator_create(uint32_t size, const char *name, bool zero); - -#define env_allocator_create_extended(size, name, limit, zero) \ - env_allocator_create(size, name, zero) - -void env_allocator_destroy(env_allocator *allocator); - -void *env_allocator_new(env_allocator *allocator); - -void env_allocator_del(env_allocator *allocator, void *item); - -/* MUTEX */ -typedef struct { - pthread_mutex_t m; -} env_mutex; - -#define env_cond_resched() ({}) - -static inline int env_mutex_init(env_mutex *mutex) -{ - if(pthread_mutex_init(&mutex->m, NULL)) - return 1; - - return 0; -} - -static inline void env_mutex_lock(env_mutex *mutex) -{ - ENV_BUG_ON(pthread_mutex_lock(&mutex->m)); -} - -static inline int env_mutex_trylock(env_mutex *mutex) -{ - return pthread_mutex_trylock(&mutex->m); -} - -static inline int env_mutex_lock_interruptible(env_mutex *mutex) -{ - env_mutex_lock(mutex); - return 0; -} - -static inline void env_mutex_unlock(env_mutex *mutex) -{ - ENV_BUG_ON(pthread_mutex_unlock(&mutex->m)); -} - -static inline int env_mutex_destroy(env_mutex *mutex) -{ - if(pthread_mutex_destroy(&mutex->m)) - return 1; - - return 0; -} - -/* RECURSIVE MUTEX */ -typedef env_mutex env_rmutex; - -static inline int env_rmutex_init(env_rmutex *rmutex) -{ - pthread_mutexattr_t attr; - - pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); - pthread_mutex_init(&rmutex->m, &attr); - - return 0; -} - -static inline void env_rmutex_lock(env_rmutex *rmutex) -{ - env_mutex_lock(rmutex); -} - -static inline int env_rmutex_lock_interruptible(env_rmutex *rmutex) -{ - return env_mutex_lock_interruptible(rmutex); -} - -static inline void env_rmutex_unlock(env_rmutex *rmutex) -{ - env_mutex_unlock(rmutex); -} - -static inline int env_rmutex_destroy(env_rmutex *rmutex) -{ - if(pthread_mutex_destroy(&rmutex->m)) - return 1; - - return 0; -} - -/* RW SEMAPHORE */ -typedef struct { - pthread_rwlock_t lock; -} env_rwsem; - -static inline int env_rwsem_init(env_rwsem *s) -{ - return pthread_rwlock_init(&s->lock, NULL); -} - -static inline void env_rwsem_up_read(env_rwsem *s) -{ - ENV_BUG_ON(pthread_rwlock_unlock(&s->lock)); -} - -static inline void env_rwsem_down_read(env_rwsem *s) -{ - ENV_BUG_ON(pthread_rwlock_rdlock(&s->lock)); -} - -static inline int env_rwsem_down_read_trylock(env_rwsem *s) -{ - return pthread_rwlock_tryrdlock(&s->lock) ? -OCF_ERR_NO_LOCK : 0; -} - -static inline void env_rwsem_up_write(env_rwsem *s) -{ - ENV_BUG_ON(pthread_rwlock_unlock(&s->lock)); -} - -static inline void env_rwsem_down_write(env_rwsem *s) -{ - ENV_BUG_ON(pthread_rwlock_wrlock(&s->lock)); -} - -static inline int env_rwsem_down_write_trylock(env_rwsem *s) -{ - return pthread_rwlock_trywrlock(&s->lock) ? -OCF_ERR_NO_LOCK : 0; -} - -static inline int env_rwsem_destroy(env_rwsem *s) -{ - return pthread_rwlock_destroy(&s->lock); -} - -/* COMPLETION */ -struct completion { - sem_t sem; -}; - -typedef struct completion env_completion; - -static inline void env_completion_init(env_completion *completion) -{ - sem_init(&completion->sem, 0, 0); -} - -static inline void env_completion_wait(env_completion *completion) -{ - sem_wait(&completion->sem); -} - -static inline void env_completion_complete(env_completion *completion) -{ - sem_post(&completion->sem); -} - -static inline void env_completion_destroy(env_completion *completion) -{ - sem_destroy(&completion->sem); -} - -/* ATOMIC VARIABLES */ - -typedef struct { - volatile uint8_t counter; -} env_atomic8; - -static inline uint8_t env_atomic8_read(const env_atomic8 *a) -{ - return __atomic_load_n(&a->counter, __ATOMIC_SEQ_CST); -} - -static inline void env_atomic8_set(env_atomic8 *a, uint8_t i) -{ - __atomic_store_n(&a->counter, i, __ATOMIC_SEQ_CST); -} - -static inline uint8_t env_atomic8_cmpxchg(env_atomic8 *a, - uint8_t old, uint8_t new_value) -{ - return __sync_val_compare_and_swap(&a->counter, old, new_value); -} - -static inline void env_atomic8_sub(uint8_t i, env_atomic8 *a) -{ - __sync_sub_and_fetch(&a->counter, i); -} - -static inline void env_atomic8_dec(env_atomic8 *a) -{ - env_atomic8_sub(1, a); -} - -static inline uint8_t env_atomic8_add_unless(env_atomic8 *a, - uint8_t i, uint8_t u) -{ - uint8_t c, old; - - c = env_atomic8_read(a); - for (;;) { - if (unlikely(c == (u))) - break; - old = env_atomic8_cmpxchg((a), c, c + (i)); - if (likely(old == c)) - break; - c = old; - } - return c != (u); -} - -typedef struct { - volatile int counter; -} env_atomic; - -static inline int env_atomic_read(const env_atomic *a) -{ - return __atomic_load_n(&a->counter, __ATOMIC_SEQ_CST); -} - -static inline void env_atomic_set(env_atomic *a, int i) -{ - __atomic_store_n(&a->counter, i, __ATOMIC_SEQ_CST); -} - -static inline void env_atomic_add(int i, env_atomic *a) -{ - __sync_add_and_fetch(&a->counter, i); -} - -static inline void env_atomic_sub(int i, env_atomic *a) -{ - __sync_sub_and_fetch(&a->counter, i); -} - -static inline void env_atomic_inc(env_atomic *a) -{ - env_atomic_add(1, a); -} - -static inline void env_atomic_dec(env_atomic *a) -{ - env_atomic_sub(1, a); -} - -static inline bool env_atomic_dec_and_test(env_atomic *a) -{ - return __sync_sub_and_fetch(&a->counter, 1) == 0; -} - -static inline int env_atomic_add_return(int i, env_atomic *a) -{ - return __sync_add_and_fetch(&a->counter, i); -} - -static inline int env_atomic_sub_return(int i, env_atomic *a) -{ - return __sync_sub_and_fetch(&a->counter, i); -} - -static inline int env_atomic_inc_return(env_atomic *a) -{ - return env_atomic_add_return(1, a); -} - -static inline int env_atomic_dec_return(env_atomic *a) -{ - return env_atomic_sub_return(1, a); -} - -static inline int env_atomic_cmpxchg(env_atomic *a, int old, int new_value) -{ - return __sync_val_compare_and_swap(&a->counter, old, new_value); -} - -static inline int env_atomic_add_unless(env_atomic *a, int i, int u) -{ - int c, old; - c = env_atomic_read(a); - for (;;) { - if (unlikely(c == (u))) - break; - old = env_atomic_cmpxchg((a), c, c + (i)); - if (likely(old == c)) - break; - c = old; - } - return c != (u); -} - -typedef struct { - volatile long counter; -} env_atomic64; - -static inline long env_atomic64_read(const env_atomic64 *a) -{ - return __atomic_load_n(&a->counter, __ATOMIC_SEQ_CST); -} - -static inline void env_atomic64_set(env_atomic64 *a, long i) -{ - __atomic_store_n(&a->counter, i, __ATOMIC_SEQ_CST); -} - -static inline void env_atomic64_add(long i, env_atomic64 *a) -{ - __sync_add_and_fetch(&a->counter, i); -} - -static inline void env_atomic64_sub(long i, env_atomic64 *a) -{ - __sync_sub_and_fetch(&a->counter, i); -} - -static inline void env_atomic64_inc(env_atomic64 *a) -{ - env_atomic64_add(1, a); -} - -static inline void env_atomic64_dec(env_atomic64 *a) -{ - env_atomic64_sub(1, a); -} - -static inline long env_atomic64_inc_return(env_atomic64 *a) -{ - return __sync_add_and_fetch(&a->counter, 1); -} - -static inline long env_atomic64_cmpxchg(env_atomic64 *a, long old_v, long new_v) -{ - return __sync_val_compare_and_swap(&a->counter, old_v, new_v); -} - -/* SPIN LOCKS */ -typedef struct { - pthread_spinlock_t lock; -} env_spinlock; - -static inline int env_spinlock_init(env_spinlock *l) -{ - return pthread_spin_init(&l->lock, 0); -} - -static inline int env_spinlock_trylock(env_spinlock *l) -{ - return pthread_spin_trylock(&l->lock) ? -OCF_ERR_NO_LOCK : 0; -} - -static inline void env_spinlock_lock(env_spinlock *l) -{ - ENV_BUG_ON(pthread_spin_lock(&l->lock)); -} - -static inline void env_spinlock_unlock(env_spinlock *l) -{ - ENV_BUG_ON(pthread_spin_unlock(&l->lock)); -} - -#define env_spinlock_lock_irqsave(l, flags) \ - (void)flags; \ - env_spinlock_lock(l) - -#define env_spinlock_unlock_irqrestore(l, flags) \ - (void)flags; \ - env_spinlock_unlock(l) - -static inline void env_spinlock_destroy(env_spinlock *l) -{ - ENV_BUG_ON(pthread_spin_destroy(&l->lock)); -} - -/* RW LOCKS */ -typedef struct { - pthread_rwlock_t lock; -} env_rwlock; - -static inline void env_rwlock_init(env_rwlock *l) -{ - ENV_BUG_ON(pthread_rwlock_init(&l->lock, NULL)); -} - -static inline void env_rwlock_read_lock(env_rwlock *l) -{ - ENV_BUG_ON(pthread_rwlock_rdlock(&l->lock)); -} - -static inline void env_rwlock_read_unlock(env_rwlock *l) -{ - ENV_BUG_ON(pthread_rwlock_unlock(&l->lock)); -} - -static inline void env_rwlock_write_lock(env_rwlock *l) -{ - ENV_BUG_ON(pthread_rwlock_wrlock(&l->lock)); -} - -static inline void env_rwlock_write_unlock(env_rwlock *l) -{ - ENV_BUG_ON(pthread_rwlock_unlock(&l->lock)); -} - -static inline void env_rwlock_destroy(env_rwlock *l) -{ - ENV_BUG_ON(pthread_rwlock_destroy(&l->lock)); -} - -/* BIT OPERATIONS */ -static inline void env_bit_set(int nr, volatile void *addr) -{ - char *byte = (char *)addr + (nr >> 3); - char mask = 1 << (nr & 7); - - __sync_or_and_fetch(byte, mask); -} - -static inline void env_bit_clear(int nr, volatile void *addr) -{ - char *byte = (char *)addr + (nr >> 3); - char mask = 1 << (nr & 7); - - mask = ~mask; - __sync_and_and_fetch(byte, mask); -} - -static inline bool env_bit_test(int nr, const volatile unsigned long *addr) -{ - const char *byte = (char *)addr + (nr >> 3); - char mask = 1 << (nr & 7); - - __sync_synchronize(); - return !!(*byte & mask); -} - -/* SCHEDULING */ -static inline int env_in_interrupt(void) -{ - return 0; -} - -/* TIME */ -#define ENV_SEC_TO_NSEC(_sec) ((_sec) * 1000000000) -#define ENV_NSEC_TO_SEC(_sec) ((_sec) / 1000000000) -#define ENV_NSEC_TO_MSEC(_sec) ((_sec) / 1000000) - -static inline uint64_t env_get_tick_count(void) -{ - struct timespec tv; - - return clock_gettime(CLOCK_REALTIME, &tv) ? - 0 : ENV_SEC_TO_NSEC(tv.tv_sec) + tv.tv_nsec; -} - -static inline uint64_t env_ticks_to_nsecs(uint64_t j) -{ - return j; -} - -static inline uint64_t env_ticks_to_msecs(uint64_t j) -{ - return ENV_NSEC_TO_MSEC(j); -} - -static inline uint64_t env_ticks_to_secs(uint64_t j) -{ - return ENV_NSEC_TO_SEC(j); -} - -static inline uint64_t env_secs_to_ticks(uint64_t j) -{ - return ENV_SEC_TO_NSEC(j); -} - -/* SORTING */ -static inline void env_sort(void *base, size_t num, size_t size, - int (*cmp_fn)(const void *, const void *), - void (*swap_fn)(void *, void *, int size)) -{ - qsort(base, num, size, cmp_fn); -} - -/* TIME */ -static inline void env_msleep(uint64_t n) -{ - usleep(n * 1000); -} - -struct env_timeval { - uint64_t sec, usec; -}; - -uint32_t env_crc32(uint32_t crc, uint8_t const *data, size_t len); - -unsigned env_get_execution_context(void); -void env_put_execution_context(unsigned ctx); -unsigned env_get_execution_context_count(void); +#define OCF_ENV_POSIX_DEBUG +#define OCF_ENV_POSIX_STRING +#define OCF_ENV_POSIX_MEMORY +#define OCF_ENV_POSIX_MUTEX +#define OCF_ENV_POSIX_RMUTEX +#define OCF_ENV_POSIX_RWSEM +#define OCF_ENV_POSIX_COMPLETION +#define OCF_ENV_POSIX_ATOMIC +#define OCF_ENV_POSIX_SPINLOCK +#define OCF_ENV_POSIX_RWLOCK +#define OCF_ENV_POSIX_BIT +#define OCF_ENV_POSIX_SCHEDULING +#define OCF_ENV_POSIX_TIME +#define OCF_ENV_POSIX_SORTING +#define OCF_ENV_POSIX_CRC +#define OCF_ENV_POSIX_EXECUTION_CONTEXT + +#include "ocf_env_default.h" #endif /* __OCF_ENV_H__ */ diff --git a/env/posix/ocf_env_default.h b/env/posix/ocf_env_default.h new file mode 100644 index 00000000..956a27dc --- /dev/null +++ b/env/posix/ocf_env_default.h @@ -0,0 +1,770 @@ +/* + * Copyright(c) 2019-2022 Intel Corporation + * Copyright(c) 2023-2025 Huawei Technologies + * Copyright(c) 2026 Unvertical + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __OCF_ENV_DEFAULT_H__ +#define __OCF_ENV_DEFAULT_H__ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#ifndef __USE_GNU +#define __USE_GNU +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ocf_env_list.h" +#include "ocf_env_headers.h" +#include "ocf/ocf_err.h" +#include "utils_mpool.h" + +/* TYPES */ +/* linux sector 512-bytes */ +#define ENV_SECTOR_SHIFT 9 + +#define OCF_ALLOCATOR_NAME_MAX 128 + +#define PAGE_SIZE 4096 + +#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) +#define min(a,b) MIN(a,b) + +#define ENV_PRIu64 "lu" +#define ENV_PRId64 "ld" + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +typedef uint64_t sector_t; + +#define __packed __attribute__((packed)) + +#define likely(cond) __builtin_expect(!!(cond), 1) +#define unlikely(cond) __builtin_expect(!!(cond), 0) + +/* MISC UTILITIES */ +#define container_of(ptr, type, member) ({ \ + const typeof(((type *)0)->member)*__mptr = (ptr); \ + (type *)((char *)__mptr - offsetof(type, member)); }) + +#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) + +/* DEBUG */ +#ifdef OCF_ENV_POSIX_DEBUG + +void env_stack_trace(void); + +#define ENV_WARN(cond, fmt...) printf(fmt) +#define ENV_WARN_ON(cond) ; +#define ENV_WARN_ONCE(cond, fmt...) ENV_WARN(cond, fmt) + +#define ENV_BUG() do {env_stack_trace(); assert(0);} while(0) +#define ENV_BUG_ON(cond) do { if (cond) ENV_BUG(); } while (0) +#define ENV_BUILD_BUG_ON(cond) _Static_assert(!(cond), "static "\ + "assertion failure") + +#endif /* OCF_ENV_POSIX_DEBUG */ + +/* STRING */ +#ifdef OCF_ENV_POSIX_STRING + +#define env_memcpy(dest, dmax, src, slen) ({ \ + memcpy(dest, src, min(dmax, slen)); \ + 0; \ + }) +#define env_memset(dest, dmax, val) ({ \ + memset(dest, val, dmax); \ + 0; \ + }) +#define env_memcmp(s1, s1max, s2, s2max, diff) ({ \ + *diff = memcmp(s1, s2, min(s1max, s2max)); \ + 0; \ + }) +#define env_strdup strndup +#define env_strnlen(s, smax) strnlen(s, smax) +#define env_strncmp(s1, slen1, s2, slen2) strncmp(s1, s2, min(slen1, slen2)) +#define env_strncpy(dest, dmax, src, slen) ({ \ + strncpy(dest, src, min(dmax - 1, slen)); \ + dest[dmax - 1] = '\0'; \ + 0; \ + }) + +#endif /* OCF_ENV_POSIX_STRING */ + +/* MEMORY */ +#ifdef OCF_ENV_POSIX_MEMORY + +#define ENV_MEM_NORMAL 0 +#define ENV_MEM_NOIO 0 +#define ENV_MEM_ATOMIC 0 + +static inline void *env_malloc(size_t size, int flags) +{ + return malloc(size); +} + +static inline void *env_zalloc(size_t size, int flags) +{ + void *ptr = malloc(size); + + if (ptr) + memset(ptr, 0, size); + + return ptr; +} + +static inline void env_free(const void *ptr) +{ + free((void *)ptr); +} + +static inline void *env_vmalloc_flags(size_t size, int flags) +{ + return malloc(size); +} + +static inline void *env_vzalloc_flags(size_t size, int flags) +{ + return env_zalloc(size, 0); +} + +static inline void *env_vmalloc(size_t size) +{ + return malloc(size); +} + +static inline void *env_vzalloc(size_t size) +{ + return env_zalloc(size, 0); +} + +static inline void env_vfree(const void *ptr) +{ + free((void *)ptr); +} + +/* SECURE MEMORY */ +/* + * OCF adapter can opt to take additional steps to securely allocate and free + * memory used by OCF to store cache metadata. This is to prevent other + * entities in the system from acquiring parts of OCF cache metadata via + * memory allocations. If this is not a concern in given product, secure + * alloc/free should default to vmalloc/vfree. + * + * Memory returned from secure alloc is not expected to be physically continous + * nor zeroed. + */ + +/* default to standard memory allocations for secure allocations */ +#define SECURE_MEMORY_HANDLING 0 + +static inline void *env_secure_alloc(size_t size) +{ + void *ptr = malloc(size); + +#if SECURE_MEMORY_HANDLING + if (ptr && mlock(ptr, size)) { + free(ptr); + ptr = NULL; + } +#endif + + return ptr; +} + +static inline void env_secure_free(const void *ptr, size_t size) +{ + if (ptr) { +#if SECURE_MEMORY_HANDLING + memset(ptr, 0, size); + /* TODO: flush CPU caches ? */ + ENV_BUG_ON(munlock(ptr)); +#endif + free((void*)ptr); + } +} + +static inline uint64_t env_get_free_memory(void) +{ + return (uint64_t)(-1); +} + +/* ALLOCATOR */ +typedef struct _env_allocator env_allocator; + +env_allocator *env_allocator_create(uint32_t size, const char *name, bool zero); + +#define env_allocator_create_extended(size, name, limit, zero) \ + env_allocator_create(size, name, zero) + +void env_allocator_destroy(env_allocator *allocator); + +void *env_allocator_new(env_allocator *allocator); + +void env_allocator_del(env_allocator *allocator, void *item); + +#endif /* OCF_ENV_POSIX_MEMORY */ + +/* MUTEX */ +#ifdef OCF_ENV_POSIX_MUTEX + +typedef struct { + pthread_mutex_t m; +} env_mutex; + +#define env_cond_resched() ({}) + +static inline int env_mutex_init(env_mutex *mutex) +{ + if(pthread_mutex_init(&mutex->m, NULL)) + return 1; + + return 0; +} + +static inline void env_mutex_lock(env_mutex *mutex) +{ + ENV_BUG_ON(pthread_mutex_lock(&mutex->m)); +} + +static inline int env_mutex_trylock(env_mutex *mutex) +{ + return pthread_mutex_trylock(&mutex->m); +} + +static inline int env_mutex_lock_interruptible(env_mutex *mutex) +{ + env_mutex_lock(mutex); + return 0; +} + +static inline void env_mutex_unlock(env_mutex *mutex) +{ + ENV_BUG_ON(pthread_mutex_unlock(&mutex->m)); +} + +static inline int env_mutex_destroy(env_mutex *mutex) +{ + if(pthread_mutex_destroy(&mutex->m)) + return 1; + + return 0; +} + +#endif /* OCF_ENV_POSIX_MUTEX */ + +/* RECURSIVE MUTEX */ +#ifdef OCF_ENV_POSIX_RMUTEX + +typedef env_mutex env_rmutex; + +static inline int env_rmutex_init(env_rmutex *rmutex) +{ + pthread_mutexattr_t attr; + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&rmutex->m, &attr); + + return 0; +} + +static inline void env_rmutex_lock(env_rmutex *rmutex) +{ + env_mutex_lock(rmutex); +} + +static inline int env_rmutex_lock_interruptible(env_rmutex *rmutex) +{ + return env_mutex_lock_interruptible(rmutex); +} + +static inline void env_rmutex_unlock(env_rmutex *rmutex) +{ + env_mutex_unlock(rmutex); +} + +static inline int env_rmutex_destroy(env_rmutex *rmutex) +{ + if(pthread_mutex_destroy(&rmutex->m)) + return 1; + + return 0; +} + +#endif /* OCF_ENV_POSIX_RMUTEX */ + +/* RWSEM */ +#ifdef OCF_ENV_POSIX_RWSEM + +typedef struct { + pthread_rwlock_t lock; +} env_rwsem; + +static inline int env_rwsem_init(env_rwsem *s) +{ + return pthread_rwlock_init(&s->lock, NULL); +} + +static inline void env_rwsem_up_read(env_rwsem *s) +{ + ENV_BUG_ON(pthread_rwlock_unlock(&s->lock)); +} + +static inline void env_rwsem_down_read(env_rwsem *s) +{ + ENV_BUG_ON(pthread_rwlock_rdlock(&s->lock)); +} + +static inline int env_rwsem_down_read_trylock(env_rwsem *s) +{ + return pthread_rwlock_tryrdlock(&s->lock) ? -OCF_ERR_NO_LOCK : 0; +} + +static inline void env_rwsem_up_write(env_rwsem *s) +{ + ENV_BUG_ON(pthread_rwlock_unlock(&s->lock)); +} + +static inline void env_rwsem_down_write(env_rwsem *s) +{ + ENV_BUG_ON(pthread_rwlock_wrlock(&s->lock)); +} + +static inline int env_rwsem_down_write_trylock(env_rwsem *s) +{ + return pthread_rwlock_trywrlock(&s->lock) ? -OCF_ERR_NO_LOCK : 0; +} + +static inline int env_rwsem_destroy(env_rwsem *s) +{ + return pthread_rwlock_destroy(&s->lock); +} + +#endif /* OCF_ENV_POSIX_RWSEM */ + +/* COMPLETION */ +#ifdef OCF_ENV_POSIX_COMPLETION + +struct completion { + sem_t sem; +}; + +typedef struct completion env_completion; + +static inline void env_completion_init(env_completion *completion) +{ + sem_init(&completion->sem, 0, 0); +} + +static inline void env_completion_wait(env_completion *completion) +{ + sem_wait(&completion->sem); +} + +static inline void env_completion_complete(env_completion *completion) +{ + sem_post(&completion->sem); +} + +static inline void env_completion_destroy(env_completion *completion) +{ + sem_destroy(&completion->sem); +} + +#endif /* OCF_ENV_POSIX_COMPLETION */ + +/* ATOMIC */ +#ifdef OCF_ENV_POSIX_ATOMIC + +typedef struct { + volatile uint8_t counter; +} env_atomic8; + +static inline uint8_t env_atomic8_read(const env_atomic8 *a) +{ + return __atomic_load_n(&a->counter, __ATOMIC_SEQ_CST); +} + +static inline void env_atomic8_set(env_atomic8 *a, uint8_t i) +{ + __atomic_store_n(&a->counter, i, __ATOMIC_SEQ_CST); +} + +static inline uint8_t env_atomic8_cmpxchg(env_atomic8 *a, + uint8_t old, uint8_t new_value) +{ + return __sync_val_compare_and_swap(&a->counter, old, new_value); +} + +static inline void env_atomic8_sub(uint8_t i, env_atomic8 *a) +{ + __sync_sub_and_fetch(&a->counter, i); +} + +static inline void env_atomic8_dec(env_atomic8 *a) +{ + env_atomic8_sub(1, a); +} + +static inline uint8_t env_atomic8_add_unless(env_atomic8 *a, + uint8_t i, uint8_t u) +{ + uint8_t c, old; + + c = env_atomic8_read(a); + for (;;) { + if (unlikely(c == (u))) + break; + old = env_atomic8_cmpxchg((a), c, c + (i)); + if (likely(old == c)) + break; + c = old; + } + return c != (u); +} + +typedef struct { + volatile int counter; +} env_atomic; + +static inline int env_atomic_read(const env_atomic *a) +{ + return __atomic_load_n(&a->counter, __ATOMIC_SEQ_CST); +} + +static inline void env_atomic_set(env_atomic *a, int i) +{ + __atomic_store_n(&a->counter, i, __ATOMIC_SEQ_CST); +} + +static inline void env_atomic_add(int i, env_atomic *a) +{ + __sync_add_and_fetch(&a->counter, i); +} + +static inline void env_atomic_sub(int i, env_atomic *a) +{ + __sync_sub_and_fetch(&a->counter, i); +} + +static inline void env_atomic_inc(env_atomic *a) +{ + env_atomic_add(1, a); +} + +static inline void env_atomic_dec(env_atomic *a) +{ + env_atomic_sub(1, a); +} + +static inline bool env_atomic_dec_and_test(env_atomic *a) +{ + return __sync_sub_and_fetch(&a->counter, 1) == 0; +} + +static inline int env_atomic_add_return(int i, env_atomic *a) +{ + return __sync_add_and_fetch(&a->counter, i); +} + +static inline int env_atomic_sub_return(int i, env_atomic *a) +{ + return __sync_sub_and_fetch(&a->counter, i); +} + +static inline int env_atomic_inc_return(env_atomic *a) +{ + return env_atomic_add_return(1, a); +} + +static inline int env_atomic_dec_return(env_atomic *a) +{ + return env_atomic_sub_return(1, a); +} + +static inline int env_atomic_cmpxchg(env_atomic *a, int old, int new_value) +{ + return __sync_val_compare_and_swap(&a->counter, old, new_value); +} + +static inline int env_atomic_add_unless(env_atomic *a, int i, int u) +{ + int c, old; + c = env_atomic_read(a); + for (;;) { + if (unlikely(c == (u))) + break; + old = env_atomic_cmpxchg((a), c, c + (i)); + if (likely(old == c)) + break; + c = old; + } + return c != (u); +} + +typedef struct { + volatile long counter; +} env_atomic64; + +static inline long env_atomic64_read(const env_atomic64 *a) +{ + return __atomic_load_n(&a->counter, __ATOMIC_SEQ_CST); +} + +static inline void env_atomic64_set(env_atomic64 *a, long i) +{ + __atomic_store_n(&a->counter, i, __ATOMIC_SEQ_CST); +} + +static inline void env_atomic64_add(long i, env_atomic64 *a) +{ + __sync_add_and_fetch(&a->counter, i); +} + +static inline void env_atomic64_sub(long i, env_atomic64 *a) +{ + __sync_sub_and_fetch(&a->counter, i); +} + +static inline void env_atomic64_inc(env_atomic64 *a) +{ + env_atomic64_add(1, a); +} + +static inline void env_atomic64_dec(env_atomic64 *a) +{ + env_atomic64_sub(1, a); +} + +static inline long env_atomic64_inc_return(env_atomic64 *a) +{ + return __sync_add_and_fetch(&a->counter, 1); +} + +static inline long env_atomic64_cmpxchg(env_atomic64 *a, long old_v, long new_v) +{ + return __sync_val_compare_and_swap(&a->counter, old_v, new_v); +} + +#endif /* OCF_ENV_POSIX_ATOMIC */ + +/* SPINLOCK */ +#ifdef OCF_ENV_POSIX_SPINLOCK + +typedef struct { + pthread_spinlock_t lock; +} env_spinlock; + +static inline int env_spinlock_init(env_spinlock *l) +{ + return pthread_spin_init(&l->lock, 0); +} + +static inline int env_spinlock_trylock(env_spinlock *l) +{ + return pthread_spin_trylock(&l->lock) ? -OCF_ERR_NO_LOCK : 0; +} + +static inline void env_spinlock_lock(env_spinlock *l) +{ + ENV_BUG_ON(pthread_spin_lock(&l->lock)); +} + +static inline void env_spinlock_unlock(env_spinlock *l) +{ + ENV_BUG_ON(pthread_spin_unlock(&l->lock)); +} + +#define env_spinlock_lock_irqsave(l, flags) \ + (void)flags; \ + env_spinlock_lock(l) + +#define env_spinlock_unlock_irqrestore(l, flags) \ + (void)flags; \ + env_spinlock_unlock(l) + +static inline void env_spinlock_destroy(env_spinlock *l) +{ + ENV_BUG_ON(pthread_spin_destroy(&l->lock)); +} + +#endif /* OCF_ENV_POSIX_SPINLOCK */ + +/* RWLOCK */ +#ifdef OCF_ENV_POSIX_RWLOCK + +typedef struct { + pthread_rwlock_t lock; +} env_rwlock; + +static inline void env_rwlock_init(env_rwlock *l) +{ + ENV_BUG_ON(pthread_rwlock_init(&l->lock, NULL)); +} + +static inline void env_rwlock_read_lock(env_rwlock *l) +{ + ENV_BUG_ON(pthread_rwlock_rdlock(&l->lock)); +} + +static inline void env_rwlock_read_unlock(env_rwlock *l) +{ + ENV_BUG_ON(pthread_rwlock_unlock(&l->lock)); +} + +static inline void env_rwlock_write_lock(env_rwlock *l) +{ + ENV_BUG_ON(pthread_rwlock_wrlock(&l->lock)); +} + +static inline void env_rwlock_write_unlock(env_rwlock *l) +{ + ENV_BUG_ON(pthread_rwlock_unlock(&l->lock)); +} + +static inline void env_rwlock_destroy(env_rwlock *l) +{ + ENV_BUG_ON(pthread_rwlock_destroy(&l->lock)); +} + +#endif /* OCF_ENV_POSIX_RWLOCK */ + +/* BIT */ +#ifdef OCF_ENV_POSIX_BIT + +static inline void env_bit_set(int nr, volatile void *addr) +{ + char *byte = (char *)addr + (nr >> 3); + char mask = 1 << (nr & 7); + + __sync_or_and_fetch(byte, mask); +} + +static inline void env_bit_clear(int nr, volatile void *addr) +{ + char *byte = (char *)addr + (nr >> 3); + char mask = 1 << (nr & 7); + + mask = ~mask; + __sync_and_and_fetch(byte, mask); +} + +static inline bool env_bit_test(int nr, const volatile unsigned long *addr) +{ + const char *byte = (char *)addr + (nr >> 3); + char mask = 1 << (nr & 7); + + __sync_synchronize(); + return !!(*byte & mask); +} + +#endif /* OCF_ENV_POSIX_BIT */ + +/* SCHEDULING */ +#ifdef OCF_ENV_POSIX_SCHEDULING + +static inline int env_in_interrupt(void) +{ + return 0; +} + +#endif /* OCF_ENV_POSIX_SCHEDULING */ + +/* TIME */ +#ifdef OCF_ENV_POSIX_TIME + +#define ENV_SEC_TO_NSEC(_sec) ((_sec) * 1000000000) +#define ENV_NSEC_TO_SEC(_sec) ((_sec) / 1000000000) +#define ENV_NSEC_TO_MSEC(_sec) ((_sec) / 1000000) + +static inline uint64_t env_get_tick_count(void) +{ + struct timespec tv; + + return clock_gettime(CLOCK_REALTIME, &tv) ? + 0 : ENV_SEC_TO_NSEC(tv.tv_sec) + tv.tv_nsec; +} + +static inline uint64_t env_ticks_to_nsecs(uint64_t j) +{ + return j; +} + +static inline uint64_t env_ticks_to_msecs(uint64_t j) +{ + return ENV_NSEC_TO_MSEC(j); +} + +static inline uint64_t env_ticks_to_secs(uint64_t j) +{ + return ENV_NSEC_TO_SEC(j); +} + +static inline uint64_t env_secs_to_ticks(uint64_t j) +{ + return ENV_SEC_TO_NSEC(j); +} + +static inline void env_msleep(uint64_t n) +{ + usleep(n * 1000); +} + +struct env_timeval { + uint64_t sec, usec; +}; + +#endif /* OCF_ENV_POSIX_TIME */ + +/* SORTING */ +#ifdef OCF_ENV_POSIX_SORTING + +static inline void env_sort(void *base, size_t num, size_t size, + int (*cmp_fn)(const void *, const void *), + void (*swap_fn)(void *, void *, int size)) +{ + qsort(base, num, size, cmp_fn); +} + +#endif /* OCF_ENV_POSIX_SORTING */ + +/* CRC */ +#ifdef OCF_ENV_POSIX_CRC + +uint32_t env_crc32(uint32_t crc, uint8_t const *data, size_t len); + +#endif /* OCF_ENV_POSIX_CRC */ + +/* EXECUTION CONTEXT */ +#ifdef OCF_ENV_POSIX_EXECUTION_CONTEXT + +unsigned env_get_execution_context(void); +void env_put_execution_context(unsigned ctx); +unsigned env_get_execution_context_count(void); + +#endif /* OCF_ENV_POSIX_EXECUTION_CONTEXT */ + +#endif /* __OCF_ENV_DEFAULT_H__ */ From 127c35292aa3f1504d6591bea1b8a7dc0828b85c Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Tue, 14 Apr 2026 10:07:37 +0200 Subject: [PATCH 2/5] pyocf: Support ratio division of Size Return unitless value when dividing Size by Size. Signed-off-by: Robert Baldyga --- tests/functional/pyocf/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/functional/pyocf/utils.py b/tests/functional/pyocf/utils.py index 01b5dba6..1799120f 100644 --- a/tests/functional/pyocf/utils.py +++ b/tests/functional/pyocf/utils.py @@ -200,9 +200,13 @@ def __mul__(self, other): return Size(self.bytes * int(other)) def __truediv__(self, other): + if isinstance(other, Size): + return self.bytes / other.bytes return Size(self.bytes / int(other)) def __floordiv__(self, other): + if isinstance(other, Size): + return self.bytes // other.bytes return Size(self.bytes // int(other)) def __rmul__(self, other): From 794b1b31200579f9fddc506ef9d3c01b703135e9 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Tue, 14 Apr 2026 10:46:20 +0200 Subject: [PATCH 3/5] pyocf: Introduce time manipulation API Signed-off-by: Robert Baldyga --- tests/functional/Makefile | 11 +++- tests/functional/pyocf/c/env/ocf_env.h | 37 +++++++++++ tests/functional/pyocf/c/env/ocf_env_time.h | 66 +++++++++++++++++++ .../functional/pyocf/c/helpers/ocf_helpers.c | 2 + tests/functional/pyocf/time.py | 28 ++++++++ tests/functional/tests/conftest.py | 2 + 6 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 tests/functional/pyocf/c/env/ocf_env.h create mode 100644 tests/functional/pyocf/c/env/ocf_env_time.h create mode 100644 tests/functional/pyocf/time.py diff --git a/tests/functional/Makefile b/tests/functional/Makefile index b7ad1e4e..dcc3af78 100644 --- a/tests/functional/Makefile +++ b/tests/functional/Makefile @@ -1,6 +1,7 @@ # # Copyright(c) 2019-2022 Intel Corporation # Copyright(c) 2025 Huawei Technologies +# Copyright(c) 2026 Unvertical # SPDX-License-Identifier: BSD-3-Clause # @@ -11,6 +12,7 @@ SRCDIR=$(ADAPTERDIR)/ocf/src INCDIR=$(ADAPTERDIR)/ocf/include WRAPDIR=$(ADAPTERDIR)/c/wrappers HELPDIR=$(ADAPTERDIR)/c/helpers +ENVDIR=$(ADAPTERDIR)/c/env CFLAGS=-g -Wall -I$(INCDIR) -I$(SRCDIR)/ocf/env $(OPT_CFLAGS) LDFLAGS=-pthread #-lz @@ -19,9 +21,14 @@ SRC=$(shell find $(SRCDIR) $(WRAPDIR) $(HELPDIR) -name \*.c) OBJS=$(patsubst %.c, %.o, $(SRC)) OCFLIB=$(ADAPTERDIR)/libocf.so -all: | sync config_random +all: | sync env_override config_random $(MAKE) $(OCFLIB) +env_override: sync + @rm -f $(SRCDIR)/ocf/env/ocf_env.h + @cp $(ENVDIR)/ocf_env.h $(SRCDIR)/ocf/env/ocf_env.h + @cp $(ENVDIR)/ocf_env_time.h $(SRCDIR)/ocf/env/ocf_env_time.h + $(OCFLIB): $(OBJS) @echo "Building $@" @$(CC) -coverage -shared -o $@ $(CFLAGS) $^ -fPIC $(LDFLAGS) @@ -51,4 +58,4 @@ distclean: clean @find . -name *.gc* -delete @echo " DISTCLEAN " -.PHONY: all clean sync config_random distclean +.PHONY: all clean sync env_override config_random distclean diff --git a/tests/functional/pyocf/c/env/ocf_env.h b/tests/functional/pyocf/c/env/ocf_env.h new file mode 100644 index 00000000..53666770 --- /dev/null +++ b/tests/functional/pyocf/c/env/ocf_env.h @@ -0,0 +1,37 @@ +/* + * Copyright(c) 2026 Unvertical + * SPDX-License-Identifier: BSD-3-Clause + */ + +/* + * Custom ocf_env.h for pyocf test environment. + * + * Replaces OCF_ENV_POSIX_TIME with a custom implementation that supports + * controllable time offset for testing. + */ + +#ifndef __OCF_ENV_H__ +#define __OCF_ENV_H__ + +#define OCF_ENV_POSIX_DEBUG +#define OCF_ENV_POSIX_STRING +#define OCF_ENV_POSIX_MEMORY +#define OCF_ENV_POSIX_MUTEX +#define OCF_ENV_POSIX_RMUTEX +#define OCF_ENV_POSIX_RWSEM +#define OCF_ENV_POSIX_COMPLETION +#define OCF_ENV_POSIX_ATOMIC +#define OCF_ENV_POSIX_SPINLOCK +#define OCF_ENV_POSIX_RWLOCK +#define OCF_ENV_POSIX_BIT +#define OCF_ENV_POSIX_SCHEDULING +/* OCF_ENV_POSIX_TIME intentionally not defined - using custom impl */ +#define OCF_ENV_POSIX_SORTING +#define OCF_ENV_POSIX_CRC +#define OCF_ENV_POSIX_EXECUTION_CONTEXT + +#include "ocf_env_default.h" + +#include "ocf_env_time.h" + +#endif /* __OCF_ENV_H__ */ diff --git a/tests/functional/pyocf/c/env/ocf_env_time.h b/tests/functional/pyocf/c/env/ocf_env_time.h new file mode 100644 index 00000000..8711252a --- /dev/null +++ b/tests/functional/pyocf/c/env/ocf_env_time.h @@ -0,0 +1,66 @@ +/* + * Copyright(c) 2026 Unvertical + * SPDX-License-Identifier: BSD-3-Clause + */ + +/* + * Custom TIME section for pyocf test environment. + * + * Replaces the default posix env_get_tick_count with a fully test-controlled + * counter. The "current time" is solely the value of ocf_env_tick_count_offset, + * which tests advance explicitly. This makes time deterministic for cleaner + * tests and avoids real-time drift between OCF time queries within a single + * test. + * + * All other time functions (conversions, sleep) remain identical to the default + * posix implementation. + */ + +#ifndef __OCF_ENV_TIME_H__ +#define __OCF_ENV_TIME_H__ + +#include +#include +#include + +#define ENV_SEC_TO_NSEC(_sec) ((_sec) * 1000000000) +#define ENV_NSEC_TO_SEC(_sec) ((_sec) / 1000000000) +#define ENV_NSEC_TO_MSEC(_sec) ((_sec) / 1000000) + +extern uint64_t ocf_env_tick_count_offset; + +static inline uint64_t env_get_tick_count(void) +{ + return ocf_env_tick_count_offset; +} + +static inline uint64_t env_ticks_to_nsecs(uint64_t j) +{ + return j; +} + +static inline uint64_t env_ticks_to_msecs(uint64_t j) +{ + return ENV_NSEC_TO_MSEC(j); +} + +static inline uint64_t env_ticks_to_secs(uint64_t j) +{ + return ENV_NSEC_TO_SEC(j); +} + +static inline uint64_t env_secs_to_ticks(uint64_t j) +{ + return ENV_SEC_TO_NSEC(j); +} + +static inline void env_msleep(uint64_t n) +{ + usleep(n * 1000); +} + +struct env_timeval { + uint64_t sec, usec; +}; + +#endif /* __OCF_ENV_TIME_H__ */ diff --git a/tests/functional/pyocf/c/helpers/ocf_helpers.c b/tests/functional/pyocf/c/helpers/ocf_helpers.c index dd149cc4..2b578ec2 100644 --- a/tests/functional/pyocf/c/helpers/ocf_helpers.c +++ b/tests/functional/pyocf/c/helpers/ocf_helpers.c @@ -7,6 +7,8 @@ #include "ocf/ocf.h" #include "../src/ocf/ocf_def_priv.h" +uint64_t ocf_env_tick_count_offset = 0; + bool ocf_is_block_size_4k(void) { #ifdef OCF_BLOCK_SIZE_4K diff --git a/tests/functional/pyocf/time.py b/tests/functional/pyocf/time.py new file mode 100644 index 00000000..4ca3c61e --- /dev/null +++ b/tests/functional/pyocf/time.py @@ -0,0 +1,28 @@ +# +# Copyright(c) 2026 Unvertical +# SPDX-License-Identifier: BSD-3-Clause +# + +from ctypes import c_uint64 + +from .ocf import OcfLib + +SEC_IN_NS = 1_000_000_000 + + +def advance_time(secs): + lib = OcfLib.getInstance() + offset = c_uint64.in_dll(lib, "ocf_env_tick_count_offset") + offset.value += secs * SEC_IN_NS + + +def advance_time_ms(ms): + lib = OcfLib.getInstance() + offset = c_uint64.in_dll(lib, "ocf_env_tick_count_offset") + offset.value += ms * 1_000_000 + + +def reset_time(): + lib = OcfLib.getInstance() + offset = c_uint64.in_dll(lib, "ocf_env_tick_count_offset") + offset.value = 0 diff --git a/tests/functional/tests/conftest.py b/tests/functional/tests/conftest.py index 94920a3e..6b39d68d 100644 --- a/tests/functional/tests/conftest.py +++ b/tests/functional/tests/conftest.py @@ -19,6 +19,7 @@ from pyocf.types.ctx import OcfCtx from pyocf.helpers import get_composite_volume_type_id from pyocf.types.volume import Volume +from pyocf.time import reset_time import warnings default_registered_volumes = [RamVolume, ErrorDevice, CacheVolume, CoreVolume, ReplicatedVolume] @@ -44,6 +45,7 @@ def pyocf_ctx(request): c.register_internal_volume_type_id(CVolume, get_composite_volume_type_id()) yield c c.exit() + reset_time() gc.collect() if getattr(request.node, "test_failed", False): return From 5554c1bd58f04f81f0f1068b60ad63ea104190b9 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Tue, 14 Apr 2026 10:46:44 +0200 Subject: [PATCH 4/5] pyocf: Implement cleaner Signed-off-by: Robert Baldyga --- tests/functional/pyocf/types/cleaner.py | 141 +++++++++++++++++++++++- 1 file changed, 138 insertions(+), 3 deletions(-) diff --git a/tests/functional/pyocf/types/cleaner.py b/tests/functional/pyocf/types/cleaner.py index 0315b175..0af66cff 100644 --- a/tests/functional/pyocf/types/cleaner.py +++ b/tests/functional/pyocf/types/cleaner.py @@ -1,10 +1,13 @@ # # Copyright(c) 2019-2021 Intel Corporation +# Copyright(c) 2026 Unvertical # SPDX-License-Identifier: BSD-3-Clause # -from ctypes import c_void_p, CFUNCTYPE, Structure, c_int +from ctypes import c_void_p, c_uint32, c_int, CFUNCTYPE, Structure +from threading import Thread, Timer, Event from .shared import SharedOcfObject +from ..ocf import OcfLib class CleanerOps(Structure): @@ -15,9 +18,25 @@ class CleanerOps(Structure): _fields_ = [("init", INIT), ("kick", KICK), ("stop", STOP)] +CLEANER_END = CFUNCTYPE(None, c_void_p, c_uint32) + + +class _CleanerState: + def __init__(self): + self.kick_event = Event() + self.stop_event = Event() + self.thread = None + self.timer = None + self.queue = None + self.last_interval = 0 + + class Cleaner(SharedOcfObject): _instances_ = {} _fields_ = [("cleaner", c_void_p)] + _cleaners = {} + _end_handler = None + _kick_handler = None def __init__(self): self._as_parameter_ = self.cleaner @@ -27,17 +46,133 @@ def __init__(self): def get_ops(cls): return CleanerOps(init=cls._init, kick=cls._kick, stop=cls._stop) + @classmethod + def set_end_handler(cls, handler): + """Override the default end handler. + + The handler receives (cleaner, interval) and is responsible for + scheduling the next cleaner iteration. Set to None to restore + the default behaviour (wait the interval before re-kicking). + """ + cls._end_handler = handler + + @classmethod + def set_kick_handler(cls, handler): + """Override the default kick handler. + + The handler receives (cleaner) and decides whether to schedule a + cleaner iteration. Set to None to restore the default behaviour + (kick the cleaner thread to run an iteration). + """ + cls._kick_handler = handler + + @staticmethod + def _resolve_queue(cleaner): + from pyocf.types.ctx import OcfCtx + + cache_handle = lib.ocf_cleaner_get_cache(cleaner) + + ctx = OcfCtx.get_default() + if ctx is None: + return None + + for c in ctx.caches: + if c.cache_handle.value == cache_handle and c.io_queues: + return c.io_queues[0] + + return None + + @staticmethod + def _cleaner_thread(cleaner): + state = Cleaner._cleaners.get(cleaner) + if not state: + return + + while not state.stop_event.is_set(): + state.kick_event.wait() + if state.stop_event.is_set(): + break + state.kick_event.clear() + if state.queue: + lib.ocf_cleaner_run(cleaner, state.queue.handle) + + @staticmethod + def _default_end(cleaner, interval): + state = Cleaner._cleaners.get(cleaner) + if state is None or state.stop_event.is_set(): + return + state.last_interval = interval + if interval > 0: + state.timer = Timer( + interval / 1000.0, state.kick_event.set + ) + state.timer.daemon = True + state.timer.start() + else: + state.kick_event.set() + @staticmethod @CleanerOps.INIT def _init(cleaner): + lib.ocf_cleaner_set_cmpl(cleaner, Cleaner._end) + + state = _CleanerState() + state.queue = Cleaner._resolve_queue(cleaner) + Cleaner._cleaners[cleaner] = state + + state.thread = Thread( + target=Cleaner._cleaner_thread, + args=(cleaner,), + daemon=True, + name="cleaner", + ) + state.thread.start() return 0 + @staticmethod + def _default_kick(cleaner): + state = Cleaner._cleaners.get(cleaner) + if state is None: + return + if state.queue is None: + state.queue = Cleaner._resolve_queue(cleaner) + state.kick_event.set() + @staticmethod @CleanerOps.KICK def _kick(cleaner): - pass + handler = Cleaner._kick_handler + if handler is not None: + handler(cleaner) + else: + Cleaner._default_kick(cleaner) + + @staticmethod + @CLEANER_END + def _end(cleaner, interval): + handler = Cleaner._end_handler + if handler is not None: + handler(cleaner, interval) + else: + Cleaner._default_end(cleaner, interval) @staticmethod @CleanerOps.STOP def _stop(cleaner): - pass + state = Cleaner._cleaners.pop(cleaner, None) + if state is None: + return + state.stop_event.set() + if state.timer: + state.timer.cancel() + state.kick_event.set() + if state.thread and state.thread.is_alive(): + state.thread.join(timeout=5) + + +lib = OcfLib.getInstance() +lib.ocf_cleaner_set_cmpl.argtypes = [c_void_p, CLEANER_END] +lib.ocf_cleaner_get_cache.argtypes = [c_void_p] +lib.ocf_cleaner_get_cache.restype = c_void_p +lib.ocf_cleaner_run.argtypes = [c_void_p, c_void_p] +lib.ocf_kick_cleaner.argtypes = [c_void_p] From 72a3d85b9167bba6329d4cf3cf52b24e88d2d473 Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Tue, 14 Apr 2026 10:47:01 +0200 Subject: [PATCH 5/5] pyocf: Introduce cleaning policy tests Signed-off-by: Robert Baldyga --- tests/functional/tests/cleaning/__init__.py | 4 + tests/functional/tests/cleaning/conftest.py | 79 ++++++ tests/functional/tests/cleaning/test_acp.py | 137 ++++++++++ tests/functional/tests/cleaning/test_alru.py | 259 +++++++++++++++++++ 4 files changed, 479 insertions(+) create mode 100644 tests/functional/tests/cleaning/__init__.py create mode 100644 tests/functional/tests/cleaning/conftest.py create mode 100644 tests/functional/tests/cleaning/test_acp.py create mode 100644 tests/functional/tests/cleaning/test_alru.py diff --git a/tests/functional/tests/cleaning/__init__.py b/tests/functional/tests/cleaning/__init__.py new file mode 100644 index 00000000..ccf889cf --- /dev/null +++ b/tests/functional/tests/cleaning/__init__.py @@ -0,0 +1,4 @@ +# +# Copyright(c) 2026 Unvertical +# SPDX-License-Identifier: BSD-3-Clause +# diff --git a/tests/functional/tests/cleaning/conftest.py b/tests/functional/tests/cleaning/conftest.py new file mode 100644 index 00000000..0470e646 --- /dev/null +++ b/tests/functional/tests/cleaning/conftest.py @@ -0,0 +1,79 @@ +# +# Copyright(c) 2026 Unvertical +# SPDX-License-Identifier: BSD-3-Clause +# + +import pytest +from ctypes import c_uint64 +from threading import Event + +from pyocf.types.cleaner import Cleaner +from pyocf.ocf import OcfLib +from pyocf.time import reset_time + + +class ManualCleaner: + """Controls cleaner iterations one at a time from the test. + + Suppresses background cleaner kicks (e.g. those issued by ALRU/ACP + when dirty data is created) so the cleaner only runs when run() is + called. Each run() executes a single ocf_cleaner_run iteration and + waits for its completion callback. + """ + + def __init__(self): + self._done = Event() + self.last_interval = None + + def _end_handler(self, cleaner, interval): + state = Cleaner._cleaners.get(cleaner) + if state is not None: + state.last_interval = interval + self.last_interval = interval + self._done.set() + + def run(self, cache): + """Kick one cleaner iteration and wait for it to complete.""" + self._done.clear() + self.last_interval = None + lib = OcfLib.getInstance() + cleaner_ptr = None + for ptr in Cleaner._cleaners: + if lib.ocf_cleaner_get_cache(ptr) == cache.cache_handle.value: + cleaner_ptr = ptr + break + assert cleaner_ptr is not None, "No cleaner registered for cache" + Cleaner._default_kick(cleaner_ptr) + assert self._done.wait(timeout=5), "Cleaner iteration timed out" + cache.settle() + + def run_until_idle(self, cache): + """Run iterations until the cleaner reports it has nothing to do. + + The cleaner returns interval == 0 from its end callback while it + still has work to do (asking to be kicked again immediately) and + a non-zero interval (the configured wake_up_time, in ms) once it + is finished. This loop keeps kicking the cleaner until that + non-zero interval is reported. + """ + for _ in range(10000): + self.run(cache) + if self.last_interval > 0: + return + raise AssertionError("Cleaner did not become idle after 10000 iterations") + + def install(self): + Cleaner.set_kick_handler(lambda cleaner: None) + Cleaner.set_end_handler(self._end_handler) + + def uninstall(self): + Cleaner.set_end_handler(None) + Cleaner.set_kick_handler(None) + + +@pytest.fixture +def manual_cleaner(): + mc = ManualCleaner() + mc.install() + yield mc + mc.uninstall() diff --git a/tests/functional/tests/cleaning/test_acp.py b/tests/functional/tests/cleaning/test_acp.py new file mode 100644 index 00000000..6a15400a --- /dev/null +++ b/tests/functional/tests/cleaning/test_acp.py @@ -0,0 +1,137 @@ +# +# Copyright(c) 2026 Unvertical +# SPDX-License-Identifier: BSD-3-Clause +# + +import pytest + +from pyocf.types.cache import ( + Cache, + CacheMode, + CleaningPolicy, + AcpParams, +) +from pyocf.types.core import Core +from pyocf.types.volume import RamVolume +from pyocf.types.volume_core import CoreVolume +from pyocf.types.shared import CacheLineSize +from pyocf.utils import Size +from pyocf.rio import Rio, ReadWrite + + +STATS_BLOCK_SIZE = Size.from_KiB(4) + + +def dirty_blocks(stats): + return stats["usage"]["dirty"]["value"] + + +def cleaner_req(stats): + return stats["req"]["cleaner"]["value"] + + +def cleaner_cache_rd(stats): + return stats["block"]["cleaner_cache_rd"]["value"] + + +def cleaner_core_wr(stats): + return stats["block"]["cleaner_core_wr"]["value"] + + +# -- cache setup helpers -- + +def setup_cache(pyocf_ctx, cls=CacheLineSize.DEFAULT): + cache_device = RamVolume(Size.from_MiB(50)) + core_device = RamVolume(Size.from_MiB(100)) + + cache = Cache.start_on_device( + cache_device, cache_mode=CacheMode.WB, + cache_line_size=cls, metadata_volatile=True, + ) + core = Core.using_device(core_device) + cache.add_core(core) + + cache.set_cleaning_policy(CleaningPolicy.NOP) + + vol = CoreVolume(core) + queue = cache.get_default_queue() + + return cache, core, vol, queue + + +def fill_dirty(vol, queue, offset, size): + Rio().target(vol).bs(Size.from_KiB(4)).size(size).offset(offset) \ + .readwrite(ReadWrite.WRITE).run([queue]) + + +# -- tests -- + +@pytest.mark.parametrize("wake_up", [0, 1, 5, 20, 100]) +def test_acp_wake_up_time(pyocf_ctx, manual_cleaner, wake_up): + """Verify wake_up_time is propagated as the interval to the cleaner + completion callback after a flush.""" + cache, core, vol, queue = setup_cache(pyocf_ctx) + + io_size = Size.from_MiB(1) + fill_dirty(vol, queue, Size(0), io_size) + + cache.set_cleaning_policy_param( + CleaningPolicy.ACP, AcpParams.WAKE_UP_TIME, wake_up + ) + cache.set_cleaning_policy(CleaningPolicy.ACP) + + manual_cleaner.run(cache) + + assert manual_cleaner.last_interval == wake_up + + +@pytest.mark.parametrize("cls", CacheLineSize) +@pytest.mark.parametrize("flush_max", [1, 10, 100]) +def test_acp_flush_max_buffers(pyocf_ctx, manual_cleaner, cls, flush_max): + """Verify each cleaner iteration flushes at most flush_max_buffers + cache lines.""" + cache, core, vol, queue = setup_cache(pyocf_ctx, cls=cls) + + io_size = Size.from_MiB(10) + fill_dirty(vol, queue, Size(0), io_size) + + total_clines = io_size // Size(cls) + blocks_per_cline = Size(cls) // STATS_BLOCK_SIZE + + cache.set_cleaning_policy_param( + CleaningPolicy.ACP, AcpParams.WAKE_UP_TIME, 0 + ) + cache.set_cleaning_policy_param( + CleaningPolicy.ACP, AcpParams.FLUSH_MAX_BUFFERS, flush_max + ) + cache.set_cleaning_policy(CleaningPolicy.ACP) + + cleaned_clines = 0 + remaining = total_clines + + while remaining > 0: + stats = cache.get_stats() + prev_req = cleaner_req(stats) + prev_rd = cleaner_cache_rd(stats) + prev_wr = cleaner_core_wr(stats) + + manual_cleaner.run(cache) + + stats = cache.get_stats() + batch = cleaner_req(stats) - prev_req + rd_batch = cleaner_cache_rd(stats) - prev_rd + wr_batch = cleaner_core_wr(stats) - prev_wr + + expected = min(flush_max, remaining) + assert batch == expected, \ + f"Expected {expected} cache lines flushed, got {batch}" + assert rd_batch == expected * blocks_per_cline, \ + f"Expected {expected * blocks_per_cline} cache read blocks, got {rd_batch}" + assert wr_batch == expected * blocks_per_cline, \ + f"Expected {expected * blocks_per_cline} core write blocks, got {wr_batch}" + + remaining -= batch + cleaned_clines += batch + + assert dirty_blocks(cache.get_stats()) == 0 + assert cleaned_clines == total_clines diff --git a/tests/functional/tests/cleaning/test_alru.py b/tests/functional/tests/cleaning/test_alru.py new file mode 100644 index 00000000..5bad93ba --- /dev/null +++ b/tests/functional/tests/cleaning/test_alru.py @@ -0,0 +1,259 @@ +# +# Copyright(c) 2026 Unvertical +# SPDX-License-Identifier: BSD-3-Clause +# + +import pytest + +from pyocf.time import advance_time, advance_time_ms, reset_time +from pyocf.types.cache import ( + Cache, + CacheMode, + CleaningPolicy, + AlruParams, +) +from pyocf.types.core import Core +from pyocf.types.volume import RamVolume +from pyocf.types.volume_core import CoreVolume +from pyocf.types.shared import CacheLineSize +from pyocf.utils import Size +from pyocf.rio import Rio, ReadWrite + + +STATS_BLOCK_SIZE = Size.from_KiB(4) + + +def dirty_blocks(stats): + return stats["usage"]["dirty"]["value"] + + +def cleaner_req(stats): + return stats["req"]["cleaner"]["value"] + + +def cleaner_cache_rd(stats): + return stats["block"]["cleaner_cache_rd"]["value"] + + +def cleaner_core_wr(stats): + return stats["block"]["cleaner_core_wr"]["value"] + + +# -- cache setup helpers -- + +def setup_cache(pyocf_ctx, cls=CacheLineSize.DEFAULT): + cache_device = RamVolume(Size.from_MiB(50)) + core_device = RamVolume(Size.from_MiB(100)) + + cache = Cache.start_on_device( + cache_device, cache_mode=CacheMode.WB, + cache_line_size=cls, metadata_volatile=True, + ) + core = Core.using_device(core_device) + cache.add_core(core) + + cache.set_cleaning_policy(CleaningPolicy.NOP) + + vol = CoreVolume(core) + queue = cache.get_default_queue() + + return cache, core, vol, queue + + +def fill_dirty(vol, queue, offset, size): + Rio().target(vol).bs(Size.from_KiB(4)).size(size).offset(offset) \ + .readwrite(ReadWrite.WRITE).run([queue]) + + +# -- tests -- + +@pytest.mark.parametrize("wake_up", [0, 1, 5, 20, 100]) +def test_alru_wake_up_time(pyocf_ctx, manual_cleaner, wake_up): + """Verify wake_up_time is propagated as the interval to the cleaner + completion callback when no flush was performed.""" + cache, core, vol, queue = setup_cache(pyocf_ctx) + + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.WAKE_UP_TIME, wake_up + ) + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.ACTIVITY_THRESHOLD, 0 + ) + cache.set_cleaning_policy(CleaningPolicy.ALRU) + + # No dirty data — cleaner has nothing to do. + # advance past clean_later check + advance_time(wake_up + 1) + + manual_cleaner.run(cache) + + assert manual_cleaner.last_interval == wake_up * 1000 + + +@pytest.mark.parametrize("stale_time", [3, 5, 10]) +def test_alru_staleness_time(pyocf_ctx, manual_cleaner, stale_time): + """Verify only data older than stale_buffer_time is cleaned.""" + cache, core, vol, queue = setup_cache(pyocf_ctx) + + # Enable ALRU before writes so each cache line gets its own per-write + # timestamp via add_alru_head. (Switching from NOP would reset all + # timestamps to a single "now" value for the existing dirty clines.) + # wake_up_time=1: the cleaner's clean_later check enforces at least + # 1 sec between back-to-back cleaner attempts. activity_threshold=200 + # is small enough never to interfere. + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.WAKE_UP_TIME, 1 + ) + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.STALE_BUFFER_TIME, stale_time + ) + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.ACTIVITY_THRESHOLD, 200 + ) + cache.set_cleaning_policy(CleaningPolicy.ALRU) + + half = Size.from_MiB(5) + half_blocks = half // STATS_BLOCK_SIZE + + # Fill first half (timestamp == 0 sec) + fill_dirty(vol, queue, Size(0), half) + + assert dirty_blocks(cache.get_stats()) == half_blocks + + # Advance 2.5 sec — crosses the activity threshold and moves the + # clock two whole seconds forward so the second half gets a + # distinct second-precision timestamp. + advance_time_ms(2500) + + # Fill second half (timestamp == 2 sec) + fill_dirty(vol, queue, half, half) + + assert dirty_blocks(cache.get_stats()) == half_blocks * 2 + + # Advance to exactly stale_time seconds from T0. This is the + # earliest point where compute_timestamp doesn't underflow (so + # the cleaner makes a meaningful eligibility decision), while + # keeping compute_timestamp == 0 — neither half eligible yet. + advance_time_ms(stale_time * 1000 - 2500) + + # Cleaner runs but flushes nothing — both halves still inside + # the staleness window. + manual_cleaner.run_until_idle(cache) + assert dirty_blocks(cache.get_stats()) == half_blocks * 2, \ + "No data should be flushed before staleness time is reached" + + # Advance another 2.5 sec → compute_timestamp == 2. First half + # (ts=0) becomes eligible; second half (ts=2) does not. The + # 2 sec gap also satisfies the wake_up_time clean_later check + # after the previous cleaner attempt. + advance_time_ms(2500) + + manual_cleaner.run_until_idle(cache) + assert dirty_blocks(cache.get_stats()) == half_blocks + + # Advance another 2 sec so compute_timestamp == 4 and the second + # half also becomes stale. + advance_time(2) + + manual_cleaner.run_until_idle(cache) + assert dirty_blocks(cache.get_stats()) == 0 + + +@pytest.mark.parametrize("cls", CacheLineSize) +@pytest.mark.parametrize("flush_max", [1, 10, 100]) +def test_alru_flush_max_buffers(pyocf_ctx, manual_cleaner, cls, flush_max): + """Verify each cleaner iteration flushes at most flush_max_buffers + cache lines.""" + cache, core, vol, queue = setup_cache(pyocf_ctx, cls=cls) + + io_size = Size.from_MiB(10) + fill_dirty(vol, queue, Size(0), io_size) + + total_clines = io_size // Size(cls) + blocks_per_cline = Size(cls) // STATS_BLOCK_SIZE + + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.WAKE_UP_TIME, 0 + ) + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.STALE_BUFFER_TIME, 1 + ) + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.ACTIVITY_THRESHOLD, 0 + ) + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.FLUSH_MAX_BUFFERS, flush_max + ) + cache.set_cleaning_policy(CleaningPolicy.ALRU) + + advance_time(2) + + cleaned_clines = 0 + remaining = total_clines + + while remaining > 0: + stats = cache.get_stats() + prev_req = cleaner_req(stats) + prev_rd = cleaner_cache_rd(stats) + prev_wr = cleaner_core_wr(stats) + + manual_cleaner.run(cache) + + stats = cache.get_stats() + batch = cleaner_req(stats) - prev_req + rd_batch = cleaner_cache_rd(stats) - prev_rd + wr_batch = cleaner_core_wr(stats) - prev_wr + + expected = min(flush_max, remaining) + assert batch == expected, \ + f"Expected {expected} cache lines flushed, got {batch}" + assert rd_batch == expected * blocks_per_cline, \ + f"Expected {expected * blocks_per_cline} cache read blocks, got {rd_batch}" + assert wr_batch == expected * blocks_per_cline, \ + f"Expected {expected * blocks_per_cline} core write blocks, got {wr_batch}" + + remaining -= batch + cleaned_clines += batch + + assert dirty_blocks(cache.get_stats()) == 0 + assert cleaned_clines == total_clines + + +@pytest.mark.parametrize("activity_threshold", [2000, 5000, 10000]) +def test_alru_activity_threshold(pyocf_ctx, manual_cleaner, activity_threshold): + """Verify cleaner does not run while I/O activity is recent, and starts + cleaning once the activity threshold is exceeded.""" + cache, core, vol, queue = setup_cache(pyocf_ctx) + + io_size = Size.from_MiB(10) + fill_dirty(vol, queue, Size(0), io_size) + + total_blocks = io_size // STATS_BLOCK_SIZE + + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.WAKE_UP_TIME, 1 + ) + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.STALE_BUFFER_TIME, 1 + ) + cache.set_cleaning_policy_param( + CleaningPolicy.ALRU, AlruParams.ACTIVITY_THRESHOLD, activity_threshold + ) + cache.set_cleaning_policy(CleaningPolicy.ALRU) + + # Advance past staleness (1 sec) but NOT past activity threshold. + # All test values are >= 2000 ms, so 1500 ms is always inside the + # activity window while still past the staleness boundary. + advance_time_ms(1500) + + manual_cleaner.run(cache) + + assert dirty_blocks(cache.get_stats()) == total_blocks, \ + "Cleaner should not run while within activity threshold" + + # Now advance past the activity threshold + advance_time_ms(activity_threshold) + + manual_cleaner.run_until_idle(cache) + + assert dirty_blocks(cache.get_stats()) == 0