From 0d0e2f50807f2285c99b364f4b19c0d7177dddeb Mon Sep 17 00:00:00 2001 From: samrach9 Date: Thu, 5 Nov 2026 12:20:38 -0500 Subject: [PATCH 1/2] Add opt-in thread safety and FREE_PAGE_IF_EMPTY to PAGED_SPARSE_ARRAY Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- devdoc/paged_sparse_array_requirements.md | 143 ++- inc/c_util/paged_sparse_array.h | 12 + inc/c_util/paged_sparse_array_ll.h | 442 ++++++++- src/paged_sparse_array.c | 2 + .../paged_sparse_array_int.c | 520 +++++++++++ .../paged_sparse_array_ut.c | 864 +++++++++++++++++- .../paged_sparse_array_ut_pch.h | 2 + 7 files changed, 1966 insertions(+), 19 deletions(-) diff --git a/devdoc/paged_sparse_array_requirements.md b/devdoc/paged_sparse_array_requirements.md index 028cc25f..1243ab43 100644 --- a/devdoc/paged_sparse_array_requirements.md +++ b/devdoc/paged_sparse_array_requirements.md @@ -2,7 +2,9 @@ ## Overview -`PAGED_SPARSE_ARRAY` is a module that provides a templatized paged sparse array data structure. The array has a maximum size and allocates memory in pages (groups of elements) on demand. When all elements in a page are released, the page is automatically freed. +`PAGED_SPARSE_ARRAY` is a module that provides a templatized paged sparse array data structure. The array has a maximum size and allocates memory in pages (groups of elements) on demand. By default, when all elements in a page are released, the page is automatically freed. + +The behavior can be customized at creation time with `PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)` (see [Options](#options)), which allows making the array thread-safe, keeping empty pages until the user frees them, and zeroing newly allocated pages. `PAGED_SPARSE_ARRAY` is a kind of `THANDLE`, all of the `THANDLE`'s API apply to `PAGED_SPARSE_ARRAY`. The following macros are provided with the same semantics as those of `THANDLE`'s: - `PAGED_SPARSE_ARRAY_INITIALIZE(T)` @@ -12,12 +14,25 @@ ## Design -The array is organized into pages. Each page contains `page_size` elements. The total number of elements that can be stored is `max_size`. Pages are allocated lazily when an element is first allocated within that page. When all elements in a page are released, the page is automatically freed. +The array is organized into pages. Each page contains `page_size` elements. The total number of elements that can be stored is `max_size`. Pages are allocated lazily when an element is first allocated within that page. By default, when all elements in a page are released, the page is automatically freed. Each element has an allocation state (allocated or not allocated). The user can: - Allocate an index for usage (fails if already allocated) -- Release an index (when all indexes in a page are released, the page is freed) +- Release an index (by default, when all indexes in a page are released, the page is freed) - Get an index (fails if not allocated) +- Free the page that contains an index, if no element in that page is allocated + +### Options + +`PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)` takes a `PAGED_SPARSE_ARRAY_OPTIONS` structure with the following fields: + +| Option | Meaning | Value used by `PAGED_SPARSE_ARRAY_CREATE(T)` | +|---|---|---| +| `thread_safe` | `ALLOCATE`, `RELEASE`, `GET` and `FREE_PAGE_IF_EMPTY` may be called concurrently from multiple threads (see [Threading Model](#threading-model)). | `false` | +| `auto_free_empty_pages` | `RELEASE` frees a page when the last allocated element in it is released. When `false`, empty pages are kept until `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)` is called or the array is disposed. This avoids freeing and re-allocating a page when its elements are released and allocated again in quick succession. | `true` | +| `zero_new_pages` | All the items of a newly allocated page are set to zero. Items are not zeroed when an element is released and allocated again in a page that was kept. | `false` | + +`PAGED_SPARSE_ARRAY_CREATE(T)` keeps the behavior that existed before options were introduced: it does not create a lock and does not call any lock or interlocked APIs. ### Custom Item Dispose Function @@ -31,9 +46,20 @@ If no custom dispose function is needed, `NULL` can be passed to `PAGED_SPARSE_A ### Threading Model -`PAGED_SPARSE_ARRAY` is a `THANDLE`, which means that the ownership of the array (reference counting, assignment, move) is thread-safe. However, the operations on the array contents (ALLOCATE, RELEASE, GET) are **not thread-safe**. +`PAGED_SPARSE_ARRAY` is a `THANDLE`, which means that the ownership of the array (reference counting, assignment, move) is thread-safe. + +When the array is created with `PAGED_SPARSE_ARRAY_CREATE(T)` (or with `thread_safe` set to `false`), the operations on the array contents (ALLOCATE, RELEASE, GET, FREE_PAGE_IF_EMPTY) are **not thread-safe**. If multiple threads need to access the same `PAGED_SPARSE_ARRAY` instance, the caller must provide external synchronization (e.g., using a mutex or SRW lock) to protect against concurrent modifications. + +When the array is created with `thread_safe` set to `true`, ALLOCATE, RELEASE, GET and FREE_PAGE_IF_EMPTY may be called concurrently from any number of threads: +- The array owns an `SRW_LOCK_LL`. ALLOCATE, RELEASE and GET hold it in shared mode, so they run concurrently with each other. +- The lock is held in exclusive mode only to allocate a page or to free a page (once per `page_size` elements in the common case). +- The allocation bitmap and the per-page allocated count are changed with interlocked operations while the lock is held in shared mode. +- A page is freed only while the lock is held in exclusive mode and only if no element in it is allocated. Therefore a pointer obtained from ALLOCATE or GET for an `index` remains valid until that `index` is released. -If multiple threads need to access the same `PAGED_SPARSE_ARRAY` instance, the caller must provide external synchronization (e.g., using a mutex or SRW lock) to protect against concurrent modifications. +The rules for callers of a thread-safe array are: +- Only the user that allocated an `index` shall release it, and an `index` shall not be released more than once per allocation. +- The array only protects its own bookkeeping. Synchronizing access to the contents of an element (e.g., one thread writing an element while another thread reads it after GET) is the responsibility of the caller. +- `item_dispose_func` is called while the lock is held. It shall not call back into the same array, otherwise it can deadlock (SRW locks are not re-entrant). ### Page Structure @@ -120,15 +146,38 @@ MU_DEFINE_ENUM(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_R PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED MU_DEFINE_ENUM(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_RESULT_VALUES) + +/*result codes for PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY*/ +#define PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT_VALUES \ + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_FREED, \ + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS, \ + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_ALLOCATED, \ + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY + +MU_DEFINE_ENUM(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT_VALUES) + +/*options for PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS*/ +typedef struct PAGED_SPARSE_ARRAY_OPTIONS_TAG +{ + bool thread_safe; + bool auto_free_empty_pages; + bool zero_new_pages; +} PAGED_SPARSE_ARRAY_OPTIONS; + +/*format and values to log a PAGED_SPARSE_ARRAY_OPTIONS, e.g. LogInfo("options=%" PRI_PAGED_SPARSE_ARRAY_OPTIONS "", PAGED_SPARSE_ARRAY_OPTIONS_VALUES(options))*/ +#define PRI_PAGED_SPARSE_ARRAY_OPTIONS "s{ thread_safe=%" PRI_BOOL ", auto_free_empty_pages=%" PRI_BOOL ", zero_new_pages=%" PRI_BOOL " }" +#define PAGED_SPARSE_ARRAY_OPTIONS_VALUES(options) "", MU_BOOL_VALUE((options).thread_safe), MU_BOOL_VALUE((options).auto_free_empty_pages), MU_BOOL_VALUE((options).zero_new_pages) ``` The macros expand to these useful APIs: ```c PAGED_SPARSE_ARRAY(T) PAGED_SPARSE_ARRAY_CREATE(T)(uint64_t max_size, uint64_t page_size, PAGED_SPARSE_ARRAY_ITEM_DISPOSE_FUNC(T) item_dispose_func); +PAGED_SPARSE_ARRAY(T) PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)(uint64_t max_size, uint64_t page_size, PAGED_SPARSE_ARRAY_ITEM_DISPOSE_FUNC(T) item_dispose_func, const PAGED_SPARSE_ARRAY_OPTIONS* options); PAGED_SPARSE_ARRAY_ALLOCATE_RESULT PAGED_SPARSE_ARRAY_ALLOCATE(T)(PAGED_SPARSE_ARRAY(T) paged_sparse_array, uint64_t index, T** allocated_ptr); void PAGED_SPARSE_ARRAY_RELEASE(T)(PAGED_SPARSE_ARRAY(T) paged_sparse_array, uint64_t index); PAGED_SPARSE_ARRAY_GET_RESULT PAGED_SPARSE_ARRAY_GET(T)(PAGED_SPARSE_ARRAY(T) paged_sparse_array, uint64_t index, T** item); +PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)(PAGED_SPARSE_ARRAY(T) paged_sparse_array, uint64_t index); ``` ### PAGED_SPARSE_ARRAY(T) @@ -197,6 +246,26 @@ PAGED_SPARSE_ARRAY(T) PAGED_SPARSE_ARRAY_CREATE(T)(uint64_t max_size, uint64_t p **SRS_PAGED_SPARSE_ARRAY_88_008: [** `PAGED_SPARSE_ARRAY_CREATE(T)` shall succeed and return a non-`NULL` value. **]** +**SRS_PAGED_SPARSE_ARRAY_88_046: [** `PAGED_SPARSE_ARRAY_CREATE(T)` shall create the paged sparse array with `thread_safe` set to `false`, `auto_free_empty_pages` set to `true` and `zero_new_pages` set to `false`. **]** + +### PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) + +```c +PAGED_SPARSE_ARRAY(T) PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)(uint64_t max_size, uint64_t page_size, PAGED_SPARSE_ARRAY_ITEM_DISPOSE_FUNC(T) item_dispose_func, const PAGED_SPARSE_ARRAY_OPTIONS* options); +``` + +`PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)` creates a new paged sparse array with the specified maximum size, page size and options (see [Options](#options)). + +**SRS_PAGED_SPARSE_ARRAY_88_047: [** If `options` is `NULL`, `PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)` shall fail and return `NULL`. **]** + +**SRS_PAGED_SPARSE_ARRAY_88_048: [** `PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)` shall validate `max_size` and `page_size`, and allocate and initialize the paged sparse array, in the same way as `PAGED_SPARSE_ARRAY_CREATE(T)`. **]** + +**SRS_PAGED_SPARSE_ARRAY_88_049: [** `PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)` shall store a copy of `*options` in the structure. **]** + +**SRS_PAGED_SPARSE_ARRAY_88_050: [** If `options->thread_safe` is `true`, `PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)` shall call `srw_lock_ll_init` to initialize the lock. **]** + +**SRS_PAGED_SPARSE_ARRAY_88_051: [** If `srw_lock_ll_init` fails, `PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)` shall free the paged sparse array and return `NULL`. **]** + ### PAGED_SPARSE_ARRAY_DISPOSE(T) ```c @@ -211,6 +280,8 @@ static void PAGED_SPARSE_ARRAY_DISPOSE(T)(PAGED_SPARSE_ARRAY(T) paged_sparse_arr **SRS_PAGED_SPARSE_ARRAY_88_010: [** `PAGED_SPARSE_ARRAY_DISPOSE(T)` shall free all pages that are non-`NULL`. **]** +**SRS_PAGED_SPARSE_ARRAY_88_052: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_DISPOSE(T)` shall call `srw_lock_ll_deinit`. **]** + ### PAGED_SPARSE_ARRAY_ALLOCATE(T) ```c @@ -237,6 +308,20 @@ PAGED_SPARSE_ARRAY_ALLOCATE_RESULT PAGED_SPARSE_ARRAY_ALLOCATE(T)(PAGED_SPARSE_A **SRS_PAGED_SPARSE_ARRAY_88_018: [** If `malloc_flex` fails, `PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall fail and return `PAGED_SPARSE_ARRAY_ALLOCATE_ERROR`. **]** +**SRS_PAGED_SPARSE_ARRAY_88_053: [** If `zero_new_pages` is `true`, `PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall set all the items of a newly allocated page to zero. **]** + +The following requirements apply when `thread_safe` is `true`. The common case (the page is already allocated) only holds the lock in shared mode. The lock is held in exclusive mode only when the page has to be allocated. + +**SRS_PAGED_SPARSE_ARRAY_88_054: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall acquire the lock in shared mode. **]** + +**SRS_PAGED_SPARSE_ARRAY_88_055: [** If `thread_safe` is `true` and the page is not allocated, `PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall release the shared lock and acquire the lock in exclusive mode. **]** + +**SRS_PAGED_SPARSE_ARRAY_88_056: [** After acquiring the lock in exclusive mode, `PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall check again whether the page is allocated, since another thread may have allocated it in the meantime. **]** + +**SRS_PAGED_SPARSE_ARRAY_88_057: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall mark the element at `index` as allocated and increment the allocated count of the page using interlocked operations. **]** + +**SRS_PAGED_SPARSE_ARRAY_88_058: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall release the lock (shared or exclusive, whichever is held) before returning. **]** + ### PAGED_SPARSE_ARRAY_RELEASE(T) ```c @@ -246,7 +331,7 @@ void PAGED_SPARSE_ARRAY_RELEASE(T)(PAGED_SPARSE_ARRAY(T) paged_sparse_array, uin `PAGED_SPARSE_ARRAY_RELEASE(T)` releases the element at the specified index. -> **Note:** If a custom `item_dispose_func` was provided to `PAGED_SPARSE_ARRAY_CREATE(T)`, it will be called for the element being released before marking it as unallocated. This allows automatic cleanup of elements during individual releases as well as during final disposal of the array. +> **Note:** If a custom `item_dispose_func` was provided at creation, it will be called for the element being released before marking it as unallocated. This allows automatic cleanup of elements during individual releases as well as during final disposal of the array. When `thread_safe` is `true`, `item_dispose_func` is called while the lock is held in shared mode, so it shall not call back into the same array. **SRS_PAGED_SPARSE_ARRAY_88_019: [** If `paged_sparse_array` is `NULL`, `PAGED_SPARSE_ARRAY_RELEASE(T)` shall return. **]** @@ -262,7 +347,17 @@ void PAGED_SPARSE_ARRAY_RELEASE(T)(PAGED_SPARSE_ARRAY(T) paged_sparse_array, uin **SRS_PAGED_SPARSE_ARRAY_88_024: [** `PAGED_SPARSE_ARRAY_RELEASE(T)` shall mark the element at `index` as not allocated. **]** -**SRS_PAGED_SPARSE_ARRAY_88_025: [** If all elements in the page are now not allocated, `PAGED_SPARSE_ARRAY_RELEASE(T)` shall free the page and set the page pointer to `NULL`. **]** +**SRS_PAGED_SPARSE_ARRAY_88_025: [** If `auto_free_empty_pages` is `true` and all elements in the page are now not allocated, `PAGED_SPARSE_ARRAY_RELEASE(T)` shall free the page and set the page pointer to `NULL`. **]** + +The following requirements apply when `thread_safe` is `true`. + +**SRS_PAGED_SPARSE_ARRAY_88_059: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_RELEASE(T)` shall acquire the lock in shared mode. **]** + +**SRS_PAGED_SPARSE_ARRAY_88_060: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_RELEASE(T)` shall mark the element at `index` as not allocated using interlocked operations and, only if the element was still marked as allocated, decrement the allocated count of the page using interlocked operations. **]** + +**SRS_PAGED_SPARSE_ARRAY_88_061: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_RELEASE(T)` shall release the shared lock. **]** + +**SRS_PAGED_SPARSE_ARRAY_88_062: [** If `thread_safe` is `true`, `auto_free_empty_pages` is `true` and the allocated count of the page became 0, `PAGED_SPARSE_ARRAY_RELEASE(T)` shall acquire the lock in exclusive mode, free the page only if it is still allocated and its allocated count is still 0, and release the exclusive lock. **]** ### PAGED_SPARSE_ARRAY_GET(T) @@ -285,3 +380,37 @@ PAGED_SPARSE_ARRAY_GET_RESULT PAGED_SPARSE_ARRAY_GET(T)(PAGED_SPARSE_ARRAY(T) pa **SRS_PAGED_SPARSE_ARRAY_88_038: [** If the element at `index` is not allocated, `PAGED_SPARSE_ARRAY_GET(T)` shall return `PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED`. **]** **SRS_PAGED_SPARSE_ARRAY_88_039: [** `PAGED_SPARSE_ARRAY_GET(T)` shall store in `item` a pointer to the element at `index` and return `PAGED_SPARSE_ARRAY_GET_OK`. **]** + +The following requirements apply when `thread_safe` is `true`. The pointer returned in `item` remains valid until the element at `index` is released. + +**SRS_PAGED_SPARSE_ARRAY_88_063: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_GET(T)` shall acquire the lock in shared mode. **]** + +**SRS_PAGED_SPARSE_ARRAY_88_064: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_GET(T)` shall check whether the element at `index` is allocated using interlocked operations. **]** + +**SRS_PAGED_SPARSE_ARRAY_88_065: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_GET(T)` shall release the shared lock before returning. **]** + +### PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) + +```c +PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)(PAGED_SPARSE_ARRAY(T) paged_sparse_array, uint64_t index); +``` + +`PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)` frees the page that contains the element at `index`, if no element in that page is allocated. `index` can be the index of any element in the page. + +This is intended for arrays created with `auto_free_empty_pages` set to `false`, where the user decides when an empty page is freed (for example, when a consumer is done with all the indexes in a page). It can also be used with `auto_free_empty_pages` set to `true`, although in that case empty pages are already freed by `PAGED_SPARSE_ARRAY_RELEASE(T)`. + +**SRS_PAGED_SPARSE_ARRAY_88_066: [** If `paged_sparse_array` is `NULL`, `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)` shall fail and return `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS`. **]** + +**SRS_PAGED_SPARSE_ARRAY_88_067: [** If `index` is greater than or equal to `max_size`, `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)` shall fail and return `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS`. **]** + +**SRS_PAGED_SPARSE_ARRAY_88_068: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)` shall acquire the lock in exclusive mode. **]** + +**SRS_PAGED_SPARSE_ARRAY_88_069: [** `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)` shall compute the page index as `index / page_size`. **]** + +**SRS_PAGED_SPARSE_ARRAY_88_070: [** If the page is not allocated, `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)` shall return `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_ALLOCATED`. **]** + +**SRS_PAGED_SPARSE_ARRAY_88_071: [** If the allocated count of the page is not 0, `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)` shall return `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY`. **]** + +**SRS_PAGED_SPARSE_ARRAY_88_072: [** Otherwise, `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)` shall free the page, set the page pointer to `NULL` and return `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_FREED`. **]** + +**SRS_PAGED_SPARSE_ARRAY_88_073: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)` shall release the exclusive lock before returning. **]** diff --git a/inc/c_util/paged_sparse_array.h b/inc/c_util/paged_sparse_array.h index 9aeeb87d..7a4edff8 100644 --- a/inc/c_util/paged_sparse_array.h +++ b/inc/c_util/paged_sparse_array.h @@ -13,6 +13,9 @@ #define PAGED_SPARSE_ARRAY_CREATE_DECLARE(T) PAGED_SPARSE_ARRAY_LL_CREATE_DECLARE(T, T) #define PAGED_SPARSE_ARRAY_CREATE_DEFINE(T) PAGED_SPARSE_ARRAY_LL_CREATE_DEFINE(T, T) +#define PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS_DECLARE(T) PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS_DECLARE(T, T) +#define PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS_DEFINE(T) PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS_DEFINE(T, T) + #define PAGED_SPARSE_ARRAY_DISPOSE_DEFINE(T) PAGED_SPARSE_ARRAY_LL_DISPOSE_DEFINE(T, T) #define PAGED_SPARSE_ARRAY_ALLOCATE_PAGE_INTERNAL_DEFINE(T) PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_DEFINE(T, T) @@ -26,10 +29,15 @@ #define PAGED_SPARSE_ARRAY_GET_DECLARE(T) PAGED_SPARSE_ARRAY_LL_GET_DECLARE(T, T) #define PAGED_SPARSE_ARRAY_GET_DEFINE(T) PAGED_SPARSE_ARRAY_LL_GET_DEFINE(T, T) +#define PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_DECLARE(T) PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_DECLARE(T, T) +#define PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_DEFINE(T) PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_DEFINE(T, T) + #define PAGED_SPARSE_ARRAY_CREATE(C) PAGED_SPARSE_ARRAY_LL_CREATE(C) +#define PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(C) PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS(C) #define PAGED_SPARSE_ARRAY_ALLOCATE(C) PAGED_SPARSE_ARRAY_LL_ALLOCATE(C) #define PAGED_SPARSE_ARRAY_RELEASE(C) PAGED_SPARSE_ARRAY_LL_RELEASE(C) #define PAGED_SPARSE_ARRAY_GET(C) PAGED_SPARSE_ARRAY_LL_GET(C) +#define PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(C) PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY(C) #define PAGED_SPARSE_ARRAY_INITIALIZE(T) PAGED_SPARSE_ARRAY_LL_INITIALIZE(T) #define PAGED_SPARSE_ARRAY_ASSIGN(T) PAGED_SPARSE_ARRAY_LL_ASSIGN(T) @@ -41,17 +49,21 @@ /*hint: have PAGED_SPARSE_ARRAY_DEFINE_STRUCT_TYPE(T) before PAGED_SPARSE_ARRAY_TYPE_DECLARE */ \ /*hint: have THANDLE_TYPE_DECLARE(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)) before PAGED_SPARSE_ARRAY_TYPE_DECLARE */ \ PAGED_SPARSE_ARRAY_CREATE_DECLARE(T) \ + PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS_DECLARE(T) \ PAGED_SPARSE_ARRAY_ALLOCATE_DECLARE(T) \ PAGED_SPARSE_ARRAY_RELEASE_DECLARE(T) \ PAGED_SPARSE_ARRAY_GET_DECLARE(T) \ + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_DECLARE(T) \ #define PAGED_SPARSE_ARRAY_TYPE_DEFINE(T) \ /*hint: have THANDLE_TYPE_DEFINE(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)) before PAGED_SPARSE_ARRAY_TYPE_DEFINE */ \ PAGED_SPARSE_ARRAY_DISPOSE_DEFINE(T) \ PAGED_SPARSE_ARRAY_ALLOCATE_PAGE_INTERNAL_DEFINE(T) \ PAGED_SPARSE_ARRAY_CREATE_DEFINE(T) \ + PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS_DEFINE(T) \ PAGED_SPARSE_ARRAY_ALLOCATE_DEFINE(T) \ PAGED_SPARSE_ARRAY_RELEASE_DEFINE(T) \ PAGED_SPARSE_ARRAY_GET_DEFINE(T) \ + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_DEFINE(T) \ #endif /*PAGED_SPARSE_ARRAY_H*/ diff --git a/inc/c_util/paged_sparse_array_ll.h b/inc/c_util/paged_sparse_array_ll.h index 9d8644f1..11501051 100644 --- a/inc/c_util/paged_sparse_array_ll.h +++ b/inc/c_util/paged_sparse_array_ll.h @@ -7,9 +7,12 @@ #ifdef __cplusplus #include #include +#include #else // __cplusplus #include +#include #include +#include #endif // __cplusplus #include "macro_utils/macro_utils.h" @@ -18,6 +21,8 @@ #include "c_pal/gballoc_hl.h" #include "c_pal/gballoc_hl_redirect.h" +#include "c_pal/interlocked.h" +#include "c_pal/srw_lock_ll.h" #include "c_pal/thandle_ll.h" #include "umock_c/umock_c_prod.h" @@ -39,6 +44,27 @@ MU_DEFINE_ENUM(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_R MU_DEFINE_ENUM(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_RESULT_VALUES) +/*result codes for PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY*/ +#define PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT_VALUES \ + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_FREED, \ + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS, \ + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_ALLOCATED, \ + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY + +MU_DEFINE_ENUM(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT_VALUES) + +/*options for PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS*/ +typedef struct PAGED_SPARSE_ARRAY_OPTIONS_TAG +{ + bool thread_safe; /*ALLOCATE, RELEASE, GET and FREE_PAGE_IF_EMPTY can be called concurrently*/ + bool auto_free_empty_pages; /*RELEASE frees a page when the last allocated element in it is released*/ + bool zero_new_pages; /*all the items of a newly allocated page are set to zero*/ +} PAGED_SPARSE_ARRAY_OPTIONS; + +/*format and values to log a PAGED_SPARSE_ARRAY_OPTIONS, e.g. LogInfo("options=%" PRI_PAGED_SPARSE_ARRAY_OPTIONS "", PAGED_SPARSE_ARRAY_OPTIONS_VALUES(options))*/ +#define PRI_PAGED_SPARSE_ARRAY_OPTIONS "s{ thread_safe=%" PRI_BOOL ", auto_free_empty_pages=%" PRI_BOOL ", zero_new_pages=%" PRI_BOOL " }" +#define PAGED_SPARSE_ARRAY_OPTIONS_VALUES(options) "", MU_BOOL_VALUE((options).thread_safe), MU_BOOL_VALUE((options).auto_free_empty_pages), MU_BOOL_VALUE((options).zero_new_pages) + /*PAGED_SPARSE_ARRAY is backed by a THANDLE build on the structure below*/ #define PAGED_SPARSE_ARRAY_STRUCT_TYPE_NAME_TAG(T) MU_C2(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T), _TAG) @@ -57,6 +83,7 @@ MU_DEFINE_ENUM(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_RESULT_VALU /*define the item dispose function type*/ \ PAGED_SPARSE_ARRAY_DEFINE_ITEM_DISPOSE_FUNC_TYPE(T); \ /*define the page structure first - bitmap is embedded after items[] in the same allocation*/ \ +/*when thread_safe is true, allocated_count and the bitmap bytes are only accessed with interlocked operations*/ \ typedef struct PAGED_SPARSE_ARRAY_PAGE_STRUCT_TYPE_NAME_TAG(T) \ { \ uint64_t allocated_count; \ @@ -70,6 +97,8 @@ struct PAGED_SPARSE_ARRAY_STRUCT_TYPE_NAME_TAG(T) uint64_t page_size; \ uint64_t page_count; \ PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T) item_dispose_func; \ + PAGED_SPARSE_ARRAY_OPTIONS options; \ + SRW_LOCK_LL lock; /*only initialized and used when options.thread_safe is true*/ \ PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)* pages[]; \ }; \ @@ -88,6 +117,13 @@ struct PAGED_SPARSE_ARRAY_STRUCT_TYPE_NAME_TAG(T) #define PAGED_SPARSE_ARRAY_LL_CREATE_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_CREATE_, C) #define PAGED_SPARSE_ARRAY_LL_CREATE(C) PAGED_SPARSE_ARRAY_LL_CREATE_NAME(C) +/*introduces a new name for a function that returns a PAGED_SPARSE_ARRAY_LL(T) created with options*/ +#define PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS_, C) +#define PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS(C) PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS_NAME(C) + +/*introduces a name for the internal function used by both CREATE and CREATE_WITH_OPTIONS*/ +#define PAGED_SPARSE_ARRAY_LL_CREATE_INTERNAL_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_CREATE_INTERNAL_, C) + /*introduces a name for the dispose function that is called when PAGED_SPARSE_ARRAY ref count goes to 0*/ #define PAGED_SPARSE_ARRAY_LL_DISPOSE_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_DISPOSE_, C) @@ -95,18 +131,38 @@ struct PAGED_SPARSE_ARRAY_STRUCT_TYPE_NAME_TAG(T) #define PAGED_SPARSE_ARRAY_LL_ALLOCATE_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_ALLOCATE_, C) #define PAGED_SPARSE_ARRAY_LL_ALLOCATE(C) PAGED_SPARSE_ARRAY_LL_ALLOCATE_NAME(C) +/*introduces a name for the internal allocate function used when thread_safe is true*/ +#define PAGED_SPARSE_ARRAY_LL_ALLOCATE_THREAD_SAFE_INTERNAL_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_ALLOCATE_THREAD_SAFE_INTERNAL_, C) + /*introduces a name for the release function*/ #define PAGED_SPARSE_ARRAY_LL_RELEASE_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_RELEASE_, C) #define PAGED_SPARSE_ARRAY_LL_RELEASE(C) PAGED_SPARSE_ARRAY_LL_RELEASE_NAME(C) +/*introduces a name for the internal release function used when thread_safe is true*/ +#define PAGED_SPARSE_ARRAY_LL_RELEASE_THREAD_SAFE_INTERNAL_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_RELEASE_THREAD_SAFE_INTERNAL_, C) + /*introduces a name for the get function*/ #define PAGED_SPARSE_ARRAY_LL_GET_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_GET_, C) #define PAGED_SPARSE_ARRAY_LL_GET(C) PAGED_SPARSE_ARRAY_LL_GET_NAME(C) +/*introduces a name for the internal get function used when thread_safe is true*/ +#define PAGED_SPARSE_ARRAY_LL_GET_THREAD_SAFE_INTERNAL_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_GET_THREAD_SAFE_INTERNAL_, C) + +/*introduces a name for the free page if empty function*/ +#define PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_, C) +#define PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY(C) PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_NAME(C) + +/*introduces a name for the internal function that frees a page if it is empty, used by RELEASE and FREE_PAGE_IF_EMPTY*/ +#define PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_INTERNAL_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_INTERNAL_, C) + /*introduces a function declaration for paged_sparse_array_create*/ #define PAGED_SPARSE_ARRAY_LL_CREATE_DECLARE(C, T) \ MOCKABLE_FUNCTION(, PAGED_SPARSE_ARRAY_LL(T), PAGED_SPARSE_ARRAY_LL_CREATE(C), uint64_t, max_size, uint64_t, page_size, PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T), item_dispose_func); +/*introduces a function declaration for paged_sparse_array_create_with_options*/ +#define PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS_DECLARE(C, T) \ + MOCKABLE_FUNCTION(, PAGED_SPARSE_ARRAY_LL(T), PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS(C), uint64_t, max_size, uint64_t, page_size, PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T), item_dispose_func, const PAGED_SPARSE_ARRAY_OPTIONS*, options); + /*introduces a function declaration for paged_sparse_array_allocate*/ #define PAGED_SPARSE_ARRAY_LL_ALLOCATE_DECLARE(C, T) \ MOCKABLE_FUNCTION(, PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_LL_ALLOCATE(C), PAGED_SPARSE_ARRAY_LL(T), paged_sparse_array, uint64_t, index, T**, allocated_ptr); @@ -119,6 +175,10 @@ struct PAGED_SPARSE_ARRAY_STRUCT_TYPE_NAME_TAG(T) #define PAGED_SPARSE_ARRAY_LL_GET_DECLARE(C, T) \ MOCKABLE_FUNCTION(, PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_LL_GET(C), PAGED_SPARSE_ARRAY_LL(T), paged_sparse_array, uint64_t, index, T**, item); +/*introduces a function declaration for paged_sparse_array_free_page_if_empty*/ +#define PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_DECLARE(C, T) \ + MOCKABLE_FUNCTION(, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY(C), PAGED_SPARSE_ARRAY_LL(T), paged_sparse_array, uint64_t, index); + /*helper to compute bitmap size in bytes (1 bit per element, rounded up to nearest byte)*/ #define PAGED_SPARSE_ARRAY_BITMAP_SIZE(page_size) (((page_size) + 7) / 8) @@ -137,6 +197,25 @@ struct PAGED_SPARSE_ARRAY_STRUCT_TYPE_NAME_TAG(T) #define PAGED_SPARSE_ARRAY_CLEAR_ALLOCATED(bitmap, index_in_page) \ ((bitmap)[(index_in_page) / 8] &= ~(1 << ((index_in_page) % 8))) +/*the helpers below are used when thread_safe is true, where allocated_count and the bitmap bytes are only accessed with interlocked operations*/ +#define PAGED_SPARSE_ARRAY_ALLOCATED_COUNT_ATOMIC(page) ((volatile_atomic int64_t*)&(page)->allocated_count) + +#define PAGED_SPARSE_ARRAY_BITMAP_BYTE_ATOMIC(bitmap, index_in_page) ((volatile_atomic int8_t*)&(bitmap)[(index_in_page) / 8]) + +#define PAGED_SPARSE_ARRAY_BITMAP_MASK(index_in_page) ((int8_t)(1 << ((index_in_page) % 8))) + +/*helper to atomically check if an element is allocated in the bitmap*/ +#define PAGED_SPARSE_ARRAY_IS_ALLOCATED_ATOMIC(bitmap, index_in_page) \ + ((interlocked_or_8(PAGED_SPARSE_ARRAY_BITMAP_BYTE_ATOMIC(bitmap, index_in_page), 0) & PAGED_SPARSE_ARRAY_BITMAP_MASK(index_in_page)) != 0) + +/*helper to atomically set an element as allocated in the bitmap, evaluates to true if the element was already allocated*/ +#define PAGED_SPARSE_ARRAY_TEST_AND_SET_ALLOCATED_ATOMIC(bitmap, index_in_page) \ + ((interlocked_or_8(PAGED_SPARSE_ARRAY_BITMAP_BYTE_ATOMIC(bitmap, index_in_page), PAGED_SPARSE_ARRAY_BITMAP_MASK(index_in_page)) & PAGED_SPARSE_ARRAY_BITMAP_MASK(index_in_page)) != 0) + +/*helper to atomically clear an element as not allocated in the bitmap, evaluates to true if the element was allocated*/ +#define PAGED_SPARSE_ARRAY_TEST_AND_CLEAR_ALLOCATED_ATOMIC(bitmap, index_in_page) \ + ((interlocked_and_8(PAGED_SPARSE_ARRAY_BITMAP_BYTE_ATOMIC(bitmap, index_in_page), (int8_t)~PAGED_SPARSE_ARRAY_BITMAP_MASK(index_in_page)) & PAGED_SPARSE_ARRAY_BITMAP_MASK(index_in_page)) != 0) + /*introduces a function definition for freeing the allocated resources for a PAGED_SPARSE_ARRAY*/ #define PAGED_SPARSE_ARRAY_LL_DISPOSE_DEFINE(C, T) \ static void PAGED_SPARSE_ARRAY_LL_DISPOSE_NAME(C)(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* paged_sparse_array) \ @@ -170,11 +249,20 @@ static void PAGED_SPARSE_ARRAY_LL_DISPOSE_NAME(C)(PAGED_SPARSE_ARRAY_TYPEDEF_NAM free((void*)paged_sparse_array->pages[i]); \ } \ } \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_052: [ If thread_safe is true, PAGED_SPARSE_ARRAY_DISPOSE(T) shall call srw_lock_ll_deinit. ]*/ \ + if (!paged_sparse_array->options.thread_safe) \ + { \ + /* do nothing, the lock was not initialized */ \ + } \ + else \ + { \ + srw_lock_ll_deinit(&paged_sparse_array->lock); \ + } \ } \ } #define PAGED_SPARSE_ARRAY_LL_CREATE_DEFINE(C, T) \ -PAGED_SPARSE_ARRAY_LL(T) PAGED_SPARSE_ARRAY_LL_CREATE(C)(uint64_t max_size, uint64_t page_size, PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T) item_dispose_func) \ +static PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* PAGED_SPARSE_ARRAY_LL_CREATE_INTERNAL_NAME(C)(uint64_t max_size, uint64_t page_size, PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T) item_dispose_func, const PAGED_SPARSE_ARRAY_OPTIONS* options) \ { \ PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* result; \ if ( \ @@ -186,7 +274,8 @@ PAGED_SPARSE_ARRAY_LL(T) PAGED_SPARSE_ARRAY_LL_CREATE(C)(uint64_t max_size, uint (max_size > UINT64_MAX - (page_size - 1)) \ ) \ { \ - LogError("Invalid arguments: uint64_t max_size=%" PRIu64 ", uint64_t page_size=%" PRIu64 "", max_size, page_size); \ + LogError("Invalid arguments: PAGED_SPARSE_ARRAY(" MU_TOSTRING(T) "), uint64_t max_size=%" PRIu64 ", uint64_t page_size=%" PRIu64 ", PAGED_SPARSE_ARRAY_ITEM_DISPOSE_FUNC(" MU_TOSTRING(T) ") item_dispose_func=%p, const PAGED_SPARSE_ARRAY_OPTIONS* options=%p (%" PRI_PAGED_SPARSE_ARRAY_OPTIONS ")", \ + max_size, page_size, item_dispose_func, options, PAGED_SPARSE_ARRAY_OPTIONS_VALUES(*options)); \ } \ else \ { \ @@ -197,7 +286,8 @@ PAGED_SPARSE_ARRAY_LL(T) PAGED_SPARSE_ARRAY_LL_CREATE(C)(uint64_t max_size, uint if (result == NULL) \ { \ /* Codes_SRS_PAGED_SPARSE_ARRAY_88_007: [ If there are any errors, PAGED_SPARSE_ARRAY_CREATE(T) shall fail and return NULL. ]*/ \ - LogError("failure in THANDLE_MALLOC_FLEX page_count=%" PRIu64 ", sizeof(PAGED_SPARSE_ARRAY_PAGE*)=%zu", page_count, sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)*)); \ + LogError("failure in THANDLE_MALLOC_FLEX(PAGED_SPARSE_ARRAY(" MU_TOSTRING(T) ")) page_count=%" PRIu64 ", sizeof(PAGED_SPARSE_ARRAY_PAGE*)=%zu, uint64_t max_size=%" PRIu64 ", uint64_t page_size=%" PRIu64 ", PAGED_SPARSE_ARRAY_ITEM_DISPOSE_FUNC(" MU_TOSTRING(T) ") item_dispose_func=%p, const PAGED_SPARSE_ARRAY_OPTIONS* options=%p (%" PRI_PAGED_SPARSE_ARRAY_OPTIONS ")", \ + page_count, sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)*), max_size, page_size, item_dispose_func, options, PAGED_SPARSE_ARRAY_OPTIONS_VALUES(*options)); \ } \ else \ { \ @@ -206,25 +296,57 @@ PAGED_SPARSE_ARRAY_LL(T) PAGED_SPARSE_ARRAY_LL_CREATE(C)(uint64_t max_size, uint result->page_size = page_size; \ result->page_count = page_count; \ result->item_dispose_func = item_dispose_func; \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_049: [ PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall store a copy of *options in the structure. ]*/ \ + result->options = *options; \ /* Codes_SRS_PAGED_SPARSE_ARRAY_88_005: [ PAGED_SPARSE_ARRAY_CREATE(T) shall set all page pointers to NULL. ]*/ \ for (uint64_t i = 0; i < page_count; i++) \ { \ result->pages[i] = NULL; \ } \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_008: [ PAGED_SPARSE_ARRAY_CREATE(T) shall succeed and return a non-NULL value. ]*/ \ - goto all_ok; \ + int lock_init_result; \ + if (!options->thread_safe) \ + { \ + /* do nothing, the lock is not used */ \ + lock_init_result = 0; \ + } \ + else \ + { \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_050: [ If options->thread_safe is true, PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall call srw_lock_ll_init to initialize the lock. ]*/ \ + lock_init_result = srw_lock_ll_init(&result->lock); \ + } \ + if (lock_init_result != 0) \ + { \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_051: [ If srw_lock_ll_init fails, PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall free the paged sparse array and return NULL. ]*/ \ + LogError("failure in srw_lock_ll_init(&result->lock)=%d for PAGED_SPARSE_ARRAY(" MU_TOSTRING(T) "), uint64_t max_size=%" PRIu64 ", uint64_t page_size=%" PRIu64 ", uint64_t page_count=%" PRIu64 ", const PAGED_SPARSE_ARRAY_OPTIONS* options=%p (%" PRI_PAGED_SPARSE_ARRAY_OPTIONS ")", \ + lock_init_result, max_size, page_size, page_count, options, PAGED_SPARSE_ARRAY_OPTIONS_VALUES(*options)); \ + THANDLE_FREE(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(C))(result); \ + } \ + else \ + { \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_008: [ PAGED_SPARSE_ARRAY_CREATE(T) shall succeed and return a non-NULL value. ]*/ \ + goto all_ok; \ + } \ } \ } \ result = NULL; \ all_ok: \ return result; \ +} \ +PAGED_SPARSE_ARRAY_LL(T) PAGED_SPARSE_ARRAY_LL_CREATE(C)(uint64_t max_size, uint64_t page_size, PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T) item_dispose_func) \ +{ \ + PAGED_SPARSE_ARRAY_OPTIONS default_options; \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_046: [ PAGED_SPARSE_ARRAY_CREATE(T) shall create the paged sparse array with thread_safe set to false, auto_free_empty_pages set to true and zero_new_pages set to false. ]*/ \ + default_options.thread_safe = false; \ + default_options.auto_free_empty_pages = true; \ + default_options.zero_new_pages = false; \ + return PAGED_SPARSE_ARRAY_LL_CREATE_INTERNAL_NAME(C)(max_size, page_size, item_dispose_func, &default_options); \ } /*internal helper to allocate a page*/ #define PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_, C) #define PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_DEFINE(C, T) \ -static PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)* PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_NAME(C)(uint64_t page_size) \ +static PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)* PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_NAME(C)(uint64_t page_size, bool zero_new_pages) \ { \ PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)* page; \ /* Allocate page struct + items array + bitmap in a single allocation */ \ @@ -233,7 +355,8 @@ static PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)* PAGED_SPARSE_ARRAY_LL_ALLOCATE_P page = malloc_flex(sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)), items_and_bitmap_size, 1); \ if (page == NULL) \ { \ - LogError("failure in malloc_flex for page, page_size=%" PRIu64 ", sizeof(" MU_TOSTRING(T) ")=%zu, bitmap_size=%zu", page_size, sizeof(T), bitmap_size); \ + LogError("failure in malloc_flex(sizeof(PAGED_SPARSE_ARRAY_PAGE(" MU_TOSTRING(T) "))=%zu, items_and_bitmap_size=%zu, 1) for page, page_size=%" PRIu64 ", sizeof(" MU_TOSTRING(T) ")=%zu, bitmap_size=%zu, zero_new_pages=%" PRI_BOOL "", \ + sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)), items_and_bitmap_size, page_size, sizeof(T), bitmap_size, MU_BOOL_VALUE(zero_new_pages)); \ } \ else \ { \ @@ -241,11 +364,135 @@ static PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)* PAGED_SPARSE_ARRAY_LL_ALLOCATE_P uint8_t* bitmap = PAGED_SPARSE_ARRAY_GET_BITMAP(page, page_size); \ (void)memset(bitmap, 0, bitmap_size); \ page->allocated_count = 0; \ + if (!zero_new_pages) \ + { \ + /* do nothing, items are left uninitialized */ \ + } \ + else \ + { \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_053: [ If zero_new_pages is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall set all the items of a newly allocated page to zero. ]*/ \ + (void)memset((void*)page->items, 0, (size_t)page_size * sizeof(T)); \ + } \ } \ return page; \ -} +} \ +MU_SUPPRESS_WARNING(4505) /*warning C4505: 'function': unreferenced function with internal linkage has been removed*/ \ +static PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_INTERNAL_NAME(C)(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* array, uint64_t page_index) \ +{ \ + /* when thread_safe is true, the caller holds the lock in exclusive mode */ \ + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT result; \ + PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)* page = array->pages[page_index]; \ + if (page == NULL) \ + { \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_070: [ If the page is not allocated, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_ALLOCATED. ]*/ \ + result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_ALLOCATED; \ + } \ + else \ + { \ + int64_t allocated_count; \ + if (!array->options.thread_safe) \ + { \ + allocated_count = (int64_t)page->allocated_count; \ + } \ + else \ + { \ + allocated_count = interlocked_add_64(PAGED_SPARSE_ARRAY_ALLOCATED_COUNT_ATOMIC(page), 0); \ + } \ + if (allocated_count != 0) \ + { \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_071: [ If the allocated count of the page is not 0, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY. ]*/ \ + result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY; \ + } \ + else \ + { \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_072: [ Otherwise, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall free the page, set the page pointer to NULL and return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_FREED. ]*/ \ + free((void*)page); \ + array->pages[page_index] = NULL; \ + result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_FREED; \ + } \ + } \ + return result; \ +} \ +MU_UNSUPPRESS_WARNING(4505) \ #define PAGED_SPARSE_ARRAY_LL_ALLOCATE_DEFINE(C, T) \ +static PAGED_SPARSE_ARRAY_ALLOCATE_RESULT PAGED_SPARSE_ARRAY_LL_ALLOCATE_THREAD_SAFE_INTERNAL_NAME(C)(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* array, uint64_t index, T** allocated_ptr) \ +{ \ + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result; \ + bool is_lock_exclusive; \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_013: [ PAGED_SPARSE_ARRAY_ALLOCATE(T) shall compute the page index as index / page_size. ]*/ \ + uint64_t page_index = index / array->page_size; \ + uint64_t index_in_page = index % array->page_size; \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_054: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall acquire the lock in shared mode. ]*/ \ + srw_lock_ll_acquire_shared(&array->lock); \ + PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)* page = array->pages[page_index]; \ + if (page != NULL) \ + { \ + is_lock_exclusive = false; \ + } \ + else \ + { \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_055: [ If thread_safe is true and the page is not allocated, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall release the shared lock and acquire the lock in exclusive mode. ]*/ \ + srw_lock_ll_release_shared(&array->lock); \ + srw_lock_ll_acquire_exclusive(&array->lock); \ + is_lock_exclusive = true; \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_056: [ After acquiring the lock in exclusive mode, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall check again whether the page is allocated, since another thread may have allocated it in the meantime. ]*/ \ + page = array->pages[page_index]; \ + if (page != NULL) \ + { \ + /* do nothing, another thread allocated the page while the lock was not held */ \ + } \ + else \ + { \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_014: [ If the page is not allocated, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall call malloc_flex to allocate memory for the page structure containing page_size elements of type T plus a uint8_t allocation bitmap of (page_size + 7) / 8 bytes, and initialize all bitmap bits to 0 (not allocated). ]*/ \ + page = PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_NAME(C)(array->page_size, array->options.zero_new_pages); \ + if (page == NULL) \ + { \ + LogError("failure allocating page for PAGED_SPARSE_ARRAY(" MU_TOSTRING(T) ") array=%p, uint64_t index=%" PRIu64 ", page_index=%" PRIu64 ", page_size=%" PRIu64 ", page_count=%" PRIu64 ", options=%" PRI_PAGED_SPARSE_ARRAY_OPTIONS "", \ + array, index, page_index, array->page_size, array->page_count, PAGED_SPARSE_ARRAY_OPTIONS_VALUES(array->options)); \ + } \ + else \ + { \ + array->pages[page_index] = page; \ + } \ + } \ + } \ + \ + if (page == NULL) \ + { \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_018: [ If malloc_flex fails, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall fail and return PAGED_SPARSE_ARRAY_ALLOCATE_ERROR. ]*/ \ + result = PAGED_SPARSE_ARRAY_ALLOCATE_ERROR; \ + } \ + else \ + { \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_057: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall mark the element at index as allocated and increment the allocated count of the page using interlocked operations. ]*/ \ + if (PAGED_SPARSE_ARRAY_TEST_AND_SET_ALLOCATED_ATOMIC(PAGED_SPARSE_ARRAY_GET_BITMAP(page, array->page_size), index_in_page)) \ + { \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_015: [ If the element at index is already allocated, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall fail and return PAGED_SPARSE_ARRAY_ALLOCATE_ALREADY_ALLOCATED. ]*/ \ + result = PAGED_SPARSE_ARRAY_ALLOCATE_ALREADY_ALLOCATED; \ + LogError("Element is already allocated in PAGED_SPARSE_ARRAY(" MU_TOSTRING(T) ") array=%p, uint64_t index=%" PRIu64 ", page_index=%" PRIu64 ", index_in_page=%" PRIu64 ", lock held exclusive=%" PRI_BOOL ", result=%" PRI_MU_ENUM "", \ + array, index, page_index, index_in_page, MU_BOOL_VALUE(is_lock_exclusive), MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, result)); \ + } \ + else \ + { \ + (void)interlocked_increment_64(PAGED_SPARSE_ARRAY_ALLOCATED_COUNT_ATOMIC(page)); \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_017: [ PAGED_SPARSE_ARRAY_ALLOCATE(T) shall store a pointer to the element at index in allocated_ptr and return PAGED_SPARSE_ARRAY_ALLOCATE_OK. ]*/ \ + *allocated_ptr = &page->items[index_in_page]; \ + result = PAGED_SPARSE_ARRAY_ALLOCATE_OK; \ + } \ + } \ + \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_058: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall release the lock (shared or exclusive, whichever is held) before returning. ]*/ \ + if (is_lock_exclusive) \ + { \ + srw_lock_ll_release_exclusive(&array->lock); \ + } \ + else \ + { \ + srw_lock_ll_release_shared(&array->lock); \ + } \ + return result; \ +} \ PAGED_SPARSE_ARRAY_ALLOCATE_RESULT PAGED_SPARSE_ARRAY_LL_ALLOCATE(C)(PAGED_SPARSE_ARRAY_LL(T) paged_sparse_array, uint64_t index, T** allocated_ptr) \ { \ PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result; \ @@ -265,6 +512,10 @@ PAGED_SPARSE_ARRAY_ALLOCATE_RESULT PAGED_SPARSE_ARRAY_LL_ALLOCATE(C)(PAGED_SPARS LogError("Invalid arguments: uint64_t index=%" PRIu64 " out of bound, max_size=%" PRIu64 "", index, paged_sparse_array->max_size); \ result = PAGED_SPARSE_ARRAY_ALLOCATE_INVALID_ARGS; \ } \ + else if (paged_sparse_array->options.thread_safe) \ + { \ + result = PAGED_SPARSE_ARRAY_LL_ALLOCATE_THREAD_SAFE_INTERNAL_NAME(C)(THANDLE_GET_T(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(C))(paged_sparse_array), index, allocated_ptr); \ + } \ else \ { \ /* Codes_SRS_PAGED_SPARSE_ARRAY_88_013: [ PAGED_SPARSE_ARRAY_ALLOCATE(T) shall compute the page index as index / page_size. ]*/ \ @@ -274,7 +525,7 @@ PAGED_SPARSE_ARRAY_ALLOCATE_RESULT PAGED_SPARSE_ARRAY_LL_ALLOCATE(C)(PAGED_SPARS /* Codes_SRS_PAGED_SPARSE_ARRAY_88_014: [ If the page is not allocated, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall call malloc_flex to allocate memory for the page containing page_size elements and an allocation bitmap, and initialize all elements as not allocated. ]*/ \ if ( \ (array->pages[page_index] == NULL) && \ - ((array->pages[page_index] = PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_NAME(C)(paged_sparse_array->page_size)) == NULL) \ + ((array->pages[page_index] = PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_NAME(C)(paged_sparse_array->page_size, paged_sparse_array->options.zero_new_pages)) == NULL) \ ) \ { \ /* Codes_SRS_PAGED_SPARSE_ARRAY_88_018: [ If page allocation fails, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall fail and return PAGED_SPARSE_ARRAY_ALLOCATE_ERROR. ]*/ \ @@ -305,6 +556,66 @@ PAGED_SPARSE_ARRAY_ALLOCATE_RESULT PAGED_SPARSE_ARRAY_LL_ALLOCATE(C)(PAGED_SPARS } #define PAGED_SPARSE_ARRAY_LL_RELEASE_DEFINE(C, T) \ +static void PAGED_SPARSE_ARRAY_LL_RELEASE_THREAD_SAFE_INTERNAL_NAME(C)(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* array, uint64_t index) \ +{ \ + bool try_free_page; \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_021: [ PAGED_SPARSE_ARRAY_RELEASE(T) shall compute the page index as index / page_size. ]*/ \ + uint64_t page_index = index / array->page_size; \ + uint64_t index_in_page = index % array->page_size; \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_059: [ If thread_safe is true, PAGED_SPARSE_ARRAY_RELEASE(T) shall acquire the lock in shared mode. ]*/ \ + srw_lock_ll_acquire_shared(&array->lock); \ + PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)* page = array->pages[page_index]; \ + if (page == NULL) \ + { \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_022: [ If the page is not allocated, PAGED_SPARSE_ARRAY_RELEASE(T) shall return. ]*/ \ + try_free_page = false; \ + } \ + else \ + { \ + uint8_t* bitmap = PAGED_SPARSE_ARRAY_GET_BITMAP(page, array->page_size); \ + if (!PAGED_SPARSE_ARRAY_IS_ALLOCATED_ATOMIC(bitmap, index_in_page)) \ + { \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_023: [ If the element at index is not allocated, PAGED_SPARSE_ARRAY_RELEASE(T) shall return. ]*/ \ + try_free_page = false; \ + } \ + else \ + { \ + if (array->item_dispose_func == NULL) \ + { \ + /* do nothing */ \ + } \ + else \ + { \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_044: [ If item_dispose_func is not NULL, PAGED_SPARSE_ARRAY_RELEASE(T) shall call item_dispose_func for the element being released. ]*/ \ + array->item_dispose_func(&page->items[index_in_page]); \ + } \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_060: [ If thread_safe is true, PAGED_SPARSE_ARRAY_RELEASE(T) shall mark the element at index as not allocated using interlocked operations and, only if the element was still marked as allocated, decrement the allocated count of the page using interlocked operations. ]*/ \ + if (!PAGED_SPARSE_ARRAY_TEST_AND_CLEAR_ALLOCATED_ATOMIC(bitmap, index_in_page)) \ + { \ + /* the element was released concurrently by another caller, which already decremented the allocated count */ \ + try_free_page = false; \ + } \ + else \ + { \ + try_free_page = (interlocked_decrement_64(PAGED_SPARSE_ARRAY_ALLOCATED_COUNT_ATOMIC(page)) == 0) && array->options.auto_free_empty_pages; \ + } \ + } \ + } \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_061: [ If thread_safe is true, PAGED_SPARSE_ARRAY_RELEASE(T) shall release the shared lock. ]*/ \ + srw_lock_ll_release_shared(&array->lock); \ + \ + if (!try_free_page) \ + { \ + /* do nothing */ \ + } \ + else \ + { \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_062: [ If thread_safe is true, auto_free_empty_pages is true and the allocated count of the page became 0, PAGED_SPARSE_ARRAY_RELEASE(T) shall acquire the lock in exclusive mode, free the page only if it is still allocated and its allocated count is still 0, and release the exclusive lock. ]*/ \ + srw_lock_ll_acquire_exclusive(&array->lock); \ + (void)PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_INTERNAL_NAME(C)(array, page_index); \ + srw_lock_ll_release_exclusive(&array->lock); \ + } \ +} \ void PAGED_SPARSE_ARRAY_LL_RELEASE(C)(PAGED_SPARSE_ARRAY_LL(T) paged_sparse_array, uint64_t index) \ { \ /* Codes_SRS_PAGED_SPARSE_ARRAY_88_019: [ If paged_sparse_array is NULL, PAGED_SPARSE_ARRAY_RELEASE(T) shall return. ]*/ \ @@ -317,6 +628,10 @@ void PAGED_SPARSE_ARRAY_LL_RELEASE(C)(PAGED_SPARSE_ARRAY_LL(T) paged_sparse_arra { \ LogError("Invalid arguments: uint64_t index=%" PRIu64 " out of bound, max_size=%" PRIu64 "", index, paged_sparse_array->max_size); \ } \ + else if (paged_sparse_array->options.thread_safe) \ + { \ + PAGED_SPARSE_ARRAY_LL_RELEASE_THREAD_SAFE_INTERNAL_NAME(C)(THANDLE_GET_T(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(C))(paged_sparse_array), index); \ + } \ else \ { \ /* Codes_SRS_PAGED_SPARSE_ARRAY_88_021: [ PAGED_SPARSE_ARRAY_RELEASE(T) shall compute the page index as index / page_size. ]*/ \ @@ -347,19 +662,59 @@ void PAGED_SPARSE_ARRAY_LL_RELEASE(C)(PAGED_SPARSE_ARRAY_LL(T) paged_sparse_arra PAGED_SPARSE_ARRAY_CLEAR_ALLOCATED(PAGED_SPARSE_ARRAY_GET_BITMAP(array->pages[page_index], paged_sparse_array->page_size), index_in_page); \ array->pages[page_index]->allocated_count--; \ \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_025: [ If all elements in the page are now not allocated, PAGED_SPARSE_ARRAY_RELEASE(T) shall free the page and set the page pointer to NULL. ]*/ \ - if (array->pages[page_index]->allocated_count == 0) \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_025: [ If auto_free_empty_pages is true and all elements in the page are now not allocated, PAGED_SPARSE_ARRAY_RELEASE(T) shall free the page and set the page pointer to NULL. ]*/ \ + if ( \ + (array->options.auto_free_empty_pages) && \ + (array->pages[page_index]->allocated_count == 0) \ + ) \ { \ /* bitmap is embedded in same allocation as page, so only one free needed */ \ free((void*)array->pages[page_index]); \ array->pages[page_index] = NULL; \ } \ + else \ + { \ + /* do nothing, the page is kept */ \ + } \ } \ } \ } \ } #define PAGED_SPARSE_ARRAY_LL_GET_DEFINE(C, T) \ +static PAGED_SPARSE_ARRAY_GET_RESULT PAGED_SPARSE_ARRAY_LL_GET_THREAD_SAFE_INTERNAL_NAME(C)(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* array, uint64_t index, T** item) \ +{ \ + PAGED_SPARSE_ARRAY_GET_RESULT result; \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_036: [ PAGED_SPARSE_ARRAY_GET(T) shall compute the page index as index / page_size. ]*/ \ + uint64_t page_index = index / array->page_size; \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_063: [ If thread_safe is true, PAGED_SPARSE_ARRAY_GET(T) shall acquire the lock in shared mode. ]*/ \ + srw_lock_ll_acquire_shared(&array->lock); \ + PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)* page = array->pages[page_index]; \ + if (page == NULL) \ + { \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_037: [ If the page is not allocated, PAGED_SPARSE_ARRAY_GET(T) shall return PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED. ]*/ \ + result = PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED; \ + } \ + else \ + { \ + uint64_t index_in_page = index % array->page_size; \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_064: [ If thread_safe is true, PAGED_SPARSE_ARRAY_GET(T) shall check whether the element at index is allocated using interlocked operations. ]*/ \ + if (!PAGED_SPARSE_ARRAY_IS_ALLOCATED_ATOMIC(PAGED_SPARSE_ARRAY_GET_BITMAP(page, array->page_size), index_in_page)) \ + { \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_038: [ If the element at index is not allocated, PAGED_SPARSE_ARRAY_GET(T) shall return PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED. ]*/ \ + result = PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED; \ + } \ + else \ + { \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_039: [ PAGED_SPARSE_ARRAY_GET(T) shall store in item a pointer to the element at index and return PAGED_SPARSE_ARRAY_GET_OK. ]*/ \ + *item = &page->items[index_in_page]; \ + result = PAGED_SPARSE_ARRAY_GET_OK; \ + } \ + } \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_065: [ If thread_safe is true, PAGED_SPARSE_ARRAY_GET(T) shall release the shared lock before returning. ]*/ \ + srw_lock_ll_release_shared(&array->lock); \ + return result; \ +} \ PAGED_SPARSE_ARRAY_GET_RESULT PAGED_SPARSE_ARRAY_LL_GET(C)(PAGED_SPARSE_ARRAY_LL(T) paged_sparse_array, uint64_t index, T** item) \ { \ PAGED_SPARSE_ARRAY_GET_RESULT result; \ @@ -374,6 +729,10 @@ PAGED_SPARSE_ARRAY_GET_RESULT PAGED_SPARSE_ARRAY_LL_GET(C)(PAGED_SPARSE_ARRAY_LL LogError("Invalid arguments: PAGED_SPARSE_ARRAY(" MU_TOSTRING(T) ") paged_sparse_array=%p, item=%p, uint64_t index=%" PRIu64, paged_sparse_array, item, index); \ result = PAGED_SPARSE_ARRAY_GET_INVALID_ARGS; \ } \ + else if (paged_sparse_array->options.thread_safe) \ + { \ + result = PAGED_SPARSE_ARRAY_LL_GET_THREAD_SAFE_INTERNAL_NAME(C)(THANDLE_GET_T(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(C))(paged_sparse_array), index, item); \ + } \ else \ { \ /* Codes_SRS_PAGED_SPARSE_ARRAY_88_036: [ PAGED_SPARSE_ARRAY_GET(T) shall compute the page index as index / page_size. ]*/ \ @@ -402,4 +761,65 @@ PAGED_SPARSE_ARRAY_GET_RESULT PAGED_SPARSE_ARRAY_LL_GET(C)(PAGED_SPARSE_ARRAY_LL } \ return result; \ } + +#define PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS_DEFINE(C, T) \ +/*hint: have PAGED_SPARSE_ARRAY_LL_CREATE_DEFINE(C, T) before PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS_DEFINE(C, T)*/ \ +PAGED_SPARSE_ARRAY_LL(T) PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS(C)(uint64_t max_size, uint64_t page_size, PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T) item_dispose_func, const PAGED_SPARSE_ARRAY_OPTIONS* options) \ +{ \ + PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* result; \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_047: [ If options is NULL, PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall fail and return NULL. ]*/ \ + if (options == NULL) \ + { \ + LogError("Invalid arguments: PAGED_SPARSE_ARRAY(" MU_TOSTRING(T) "), uint64_t max_size=%" PRIu64 ", uint64_t page_size=%" PRIu64 ", PAGED_SPARSE_ARRAY_ITEM_DISPOSE_FUNC(" MU_TOSTRING(T) ") item_dispose_func=%p, const PAGED_SPARSE_ARRAY_OPTIONS* options=%p", \ + max_size, page_size, item_dispose_func, options); \ + result = NULL; \ + } \ + else \ + { \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_048: [ PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall validate max_size and page_size, and allocate and initialize the paged sparse array, in the same way as PAGED_SPARSE_ARRAY_CREATE(T). ]*/ \ + result = PAGED_SPARSE_ARRAY_LL_CREATE_INTERNAL_NAME(C)(max_size, page_size, item_dispose_func, options); \ + } \ + return result; \ +} + +#define PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_DEFINE(C, T) \ +/*hint: have PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_DEFINE(C, T) before PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_DEFINE(C, T)*/ \ +PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY(C)(PAGED_SPARSE_ARRAY_LL(T) paged_sparse_array, uint64_t index) \ +{ \ + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT result; \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_066: [ If paged_sparse_array is NULL, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall fail and return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS. ]*/ \ + if (paged_sparse_array == NULL) \ + { \ + result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS; \ + LogError("Invalid arguments: PAGED_SPARSE_ARRAY(" MU_TOSTRING(T) ") paged_sparse_array=%p, uint64_t index=%" PRIu64 ", result=%" PRI_MU_ENUM "", \ + paged_sparse_array, index, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, result)); \ + } \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_067: [ If index is greater than or equal to max_size, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall fail and return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS. ]*/ \ + else if (index >= paged_sparse_array->max_size) \ + { \ + result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS; \ + LogError("Invalid arguments: PAGED_SPARSE_ARRAY(" MU_TOSTRING(T) ") paged_sparse_array=%p, uint64_t index=%" PRIu64 " out of bound, max_size=%" PRIu64 ", page_size=%" PRIu64 ", result=%" PRI_MU_ENUM "", \ + paged_sparse_array, index, paged_sparse_array->max_size, paged_sparse_array->page_size, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, result)); \ + } \ + else \ + { \ + PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* array = THANDLE_GET_T(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(C))(paged_sparse_array); \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_069: [ PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall compute the page index as index / page_size. ]*/ \ + uint64_t page_index = index / array->page_size; \ + if (!array->options.thread_safe) \ + { \ + result = PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_INTERNAL_NAME(C)(array, page_index); \ + } \ + else \ + { \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_068: [ If thread_safe is true, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall acquire the lock in exclusive mode. ]*/ \ + srw_lock_ll_acquire_exclusive(&array->lock); \ + result = PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_INTERNAL_NAME(C)(array, page_index); \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_073: [ If thread_safe is true, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall release the exclusive lock before returning. ]*/ \ + srw_lock_ll_release_exclusive(&array->lock); \ + } \ + } \ + return result; \ +} + #endif /*PAGED_SPARSE_ARRAY_LL_H*/ diff --git a/src/paged_sparse_array.c b/src/paged_sparse_array.c index 24f129e4..76882ba6 100644 --- a/src/paged_sparse_array.c +++ b/src/paged_sparse_array.c @@ -8,3 +8,5 @@ MU_DEFINE_ENUM_STRINGS(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_RESULT_VALUES) MU_DEFINE_ENUM_STRINGS(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_RESULT_VALUES) + +MU_DEFINE_ENUM_STRINGS(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT_VALUES) diff --git a/tests/paged_sparse_array_int/paged_sparse_array_int.c b/tests/paged_sparse_array_int/paged_sparse_array_int.c index f82c6ab4..575baf7b 100644 --- a/tests/paged_sparse_array_int/paged_sparse_array_int.c +++ b/tests/paged_sparse_array_int/paged_sparse_array_int.c @@ -55,6 +55,21 @@ THANDLE_TYPE_DEFINE(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(TEST_ELEMENT_WITH_MALLOC)); PAGED_SPARSE_ARRAY_TYPE_DECLARE(TEST_ELEMENT_WITH_MALLOC); PAGED_SPARSE_ARRAY_TYPE_DEFINE(TEST_ELEMENT_WITH_MALLOC); +/* + * RING_ELEMENT - element type used by the producer/consumer ring buffer test with a thread-safe array + */ +typedef struct RING_ELEMENT_TAG +{ + volatile_atomic int64_t published_sequence_plus_one; +} RING_ELEMENT; + +PAGED_SPARSE_ARRAY_DEFINE_STRUCT_TYPE(RING_ELEMENT); +THANDLE_TYPE_DECLARE(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(RING_ELEMENT)); +THANDLE_TYPE_DEFINE(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(RING_ELEMENT)); + +PAGED_SPARSE_ARRAY_TYPE_DECLARE(RING_ELEMENT); +PAGED_SPARSE_ARRAY_TYPE_DEFINE(RING_ELEMENT); + #define CHAOS_TEST_ACTION_VALUES \ CHAOS_TEST_ACTION_ALLOCATE, \ CHAOS_TEST_ACTION_RELEASE, \ @@ -65,6 +80,492 @@ MU_DEFINE_ENUM_STRINGS(CHAOS_TEST_ACTION, CHAOS_TEST_ACTION_VALUES) TEST_DEFINE_ENUM_TYPE(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_RESULT_VALUES) TEST_DEFINE_ENUM_TYPE(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_RESULT_VALUES) +TEST_DEFINE_ENUM_TYPE(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT_VALUES) + +/* + * Multi-threaded tests for arrays created with thread_safe set to true. + * Asserts cannot be used from worker threads, so worker threads count failures and the test thread asserts on the count. + */ +#define CONCURRENT_THREAD_COUNT 8 +#define CONCURRENT_OPERATIONS_PER_THREAD 20000 +#define CONCURRENT_MAX_SIZE 256 +#define CONCURRENT_PAGE_SIZE 16 +#define CONCURRENT_MAX_HELD_ELEMENTS_PER_THREAD 4 + +static volatile_atomic int64_t g_dispose_count; + +static void count_dispose(TEST_ELEMENT* item) +{ + (void)item; + (void)interlocked_increment_64(&g_dispose_count); +} + +static uint64_t test_next_random(uint64_t* state) +{ + /* 64-bit LCG, each thread has its own state so no locking is needed */ + *state = *state * 6364136223846793005ULL + 1442695040888963407ULL; + return *state >> 33; +} + +typedef struct CONCURRENT_TEST_CONTEXT_TAG +{ + const PAGED_SPARSE_ARRAY_TYPEDEF_NAME(TEST_ELEMENT)* psa; + bool call_free_page_if_empty; + volatile_atomic int32_t owners[CONCURRENT_MAX_SIZE]; + volatile_atomic int64_t successful_allocations; + volatile_atomic int32_t failures; +} CONCURRENT_TEST_CONTEXT; + +typedef struct CONCURRENT_THREAD_CONTEXT_TAG +{ + CONCURRENT_TEST_CONTEXT* test_context; + int32_t thread_id; +} CONCURRENT_THREAD_CONTEXT; + +typedef struct HELD_ELEMENT_TAG +{ + uint64_t index; + TEST_ELEMENT* element; + int64_t expected_value; +} HELD_ELEMENT; + +static void concurrent_record_failure(CONCURRENT_TEST_CONTEXT* test_context) +{ + (void)interlocked_increment(&test_context->failures); +} + +static void concurrent_release_held_element(CONCURRENT_TEST_CONTEXT* test_context, int32_t thread_id, const HELD_ELEMENT* held) +{ + /* the element is owned by this thread, so its value must not have been changed and its page must not have been freed */ + if (held->element->value != held->expected_value) + { + LogError("thread_id=%" PRId32 ": held element value changed, index=%" PRIu64 ", page_index=%" PRIu64 ", element=%p, value=%" PRId64 ", expected_value=%" PRId64 "", + thread_id, held->index, held->index / CONCURRENT_PAGE_SIZE, (void*)held->element, held->element->value, held->expected_value); + concurrent_record_failure(test_context); + } + else + { + TEST_ELEMENT* element_from_get = NULL; + PAGED_SPARSE_ARRAY_GET_RESULT get_result = PAGED_SPARSE_ARRAY_GET(TEST_ELEMENT)(test_context->psa, held->index, &element_from_get); + if ( + (get_result != PAGED_SPARSE_ARRAY_GET_OK) || + (element_from_get != held->element) + ) + { + LogError("thread_id=%" PRId32 ": GET of a held element did not return the allocated pointer, index=%" PRIu64 ", page_index=%" PRIu64 ", get_result=%" PRI_MU_ENUM ", element_from_get=%p, allocated element=%p", + thread_id, held->index, held->index / CONCURRENT_PAGE_SIZE, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_GET_RESULT, get_result), (void*)element_from_get, (void*)held->element); + concurrent_record_failure(test_context); + } + else + { + /* do nothing, the element is as expected */ + } + } + + int32_t owner = interlocked_exchange(&test_context->owners[held->index], 0); + if (owner != thread_id) + { + LogError("thread_id=%" PRId32 ": element owner changed while held, index=%" PRIu64 ", page_index=%" PRIu64 ", owner=%" PRId32 "", + thread_id, held->index, held->index / CONCURRENT_PAGE_SIZE, owner); + concurrent_record_failure(test_context); + } + else + { + /* do nothing, ownership was exclusive */ + } + + PAGED_SPARSE_ARRAY_RELEASE(TEST_ELEMENT)(test_context->psa, held->index); +} + +static int concurrent_chaos_thread(void* arg) +{ + CONCURRENT_THREAD_CONTEXT* thread_context = arg; + CONCURRENT_TEST_CONTEXT* test_context = thread_context->test_context; + int32_t thread_id = thread_context->thread_id; + uint64_t random_state = (uint64_t)thread_id; + HELD_ELEMENT held[CONCURRENT_MAX_HELD_ELEMENTS_PER_THREAD]; + uint32_t held_count = 0; + + for (uint32_t operation = 0; operation < CONCURRENT_OPERATIONS_PER_THREAD; operation++) + { + uint64_t random_value = test_next_random(&random_state); + if ( + (held_count < CONCURRENT_MAX_HELD_ELEMENTS_PER_THREAD) && + ((held_count == 0) || ((random_value % 2) == 0)) + ) + { + uint64_t index = test_next_random(&random_state) % CONCURRENT_MAX_SIZE; + TEST_ELEMENT* element; + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT allocate_result = PAGED_SPARSE_ARRAY_ALLOCATE(TEST_ELEMENT)(test_context->psa, index, &element); + if (allocate_result == PAGED_SPARSE_ARRAY_ALLOCATE_ALREADY_ALLOCATED) + { + /* do nothing, the element is owned by another thread (or by this one) */ + } + else if (allocate_result != PAGED_SPARSE_ARRAY_ALLOCATE_OK) + { + LogError("thread_id=%" PRId32 ": ALLOCATE failed, operation=%" PRIu32 ", index=%" PRIu64 ", page_index=%" PRIu64 ", held_count=%" PRIu32 ", allocate_result=%" PRI_MU_ENUM "", + thread_id, operation, index, index / CONCURRENT_PAGE_SIZE, held_count, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, allocate_result)); + concurrent_record_failure(test_context); + } + else + { + (void)interlocked_increment_64(&test_context->successful_allocations); + int32_t previous_owner = interlocked_compare_exchange(&test_context->owners[index], thread_id, 0); + if (previous_owner != 0) + { + LogError("thread_id=%" PRId32 ": ALLOCATE succeeded for an element owned by another thread, operation=%" PRIu32 ", index=%" PRIu64 ", page_index=%" PRIu64 ", previous_owner=%" PRId32 ", allocate_result=%" PRI_MU_ENUM "", + thread_id, operation, index, index / CONCURRENT_PAGE_SIZE, previous_owner, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, allocate_result)); + concurrent_record_failure(test_context); + } + else + { + /* do nothing, this thread is now the only owner */ + } + held[held_count].index = index; + held[held_count].element = element; + held[held_count].expected_value = ((int64_t)thread_id << 32) | operation; + element->value = held[held_count].expected_value; + held_count++; + } + } + else + { + uint32_t held_position = (uint32_t)(random_value % held_count); + concurrent_release_held_element(test_context, thread_id, &held[held_position]); + held_count--; + held[held_position] = held[held_count]; + } + + /* elements of other threads are not read, only the result of GET is checked */ + uint64_t other_index = test_next_random(&random_state) % CONCURRENT_MAX_SIZE; + TEST_ELEMENT* other_element; + PAGED_SPARSE_ARRAY_GET_RESULT get_result = PAGED_SPARSE_ARRAY_GET(TEST_ELEMENT)(test_context->psa, other_index, &other_element); + if ( + (get_result != PAGED_SPARSE_ARRAY_GET_OK) && + (get_result != PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED) + ) + { + LogError("thread_id=%" PRId32 ": GET returned an unexpected result, operation=%" PRIu32 ", index=%" PRIu64 ", page_index=%" PRIu64 ", get_result=%" PRI_MU_ENUM "", + thread_id, operation, other_index, other_index / CONCURRENT_PAGE_SIZE, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_GET_RESULT, get_result)); + concurrent_record_failure(test_context); + } + else + { + /* do nothing, both results are possible */ + } + + if (!test_context->call_free_page_if_empty) + { + /* do nothing */ + } + else + { + uint64_t index = test_next_random(&random_state) % CONCURRENT_MAX_SIZE; + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT free_result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(TEST_ELEMENT)(test_context->psa, index); + if (free_result == PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS) + { + LogError("thread_id=%" PRId32 ": FREE_PAGE_IF_EMPTY returned an unexpected result, operation=%" PRIu32 ", index=%" PRIu64 ", page_index=%" PRIu64 ", free_result=%" PRI_MU_ENUM "", + thread_id, operation, index, index / CONCURRENT_PAGE_SIZE, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, free_result)); + concurrent_record_failure(test_context); + } + else + { + /* do nothing, FREED, NOT_ALLOCATED and NOT_EMPTY are all possible */ + } + } + } + + while (held_count > 0) + { + held_count--; + concurrent_release_held_element(test_context, thread_id, &held[held_count]); + } + + return 0; +} + +static void run_concurrent_chaos_test(bool auto_free_empty_pages, bool call_free_page_if_empty) +{ + //arrange + PAGED_SPARSE_ARRAY_OPTIONS options; + options.thread_safe = true; + options.auto_free_empty_pages = auto_free_empty_pages; + options.zero_new_pages = false; + PAGED_SPARSE_ARRAY(TEST_ELEMENT) psa = PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(TEST_ELEMENT)(CONCURRENT_MAX_SIZE, CONCURRENT_PAGE_SIZE, count_dispose, &options); + ASSERT_IS_NOT_NULL(psa); + + CONCURRENT_TEST_CONTEXT* test_context = malloc(sizeof(CONCURRENT_TEST_CONTEXT)); + ASSERT_IS_NOT_NULL(test_context); + test_context->psa = psa; + test_context->call_free_page_if_empty = call_free_page_if_empty; + for (uint32_t i = 0; i < CONCURRENT_MAX_SIZE; i++) + { + (void)interlocked_exchange(&test_context->owners[i], 0); + } + (void)interlocked_exchange_64(&test_context->successful_allocations, 0); + (void)interlocked_exchange(&test_context->failures, 0); + (void)interlocked_exchange_64(&g_dispose_count, 0); + + CONCURRENT_THREAD_CONTEXT thread_contexts[CONCURRENT_THREAD_COUNT]; + THREAD_HANDLE threads[CONCURRENT_THREAD_COUNT]; + int32_t threads_created = 0; + bool thread_api_failed = false; + + //act + /* no ASSERT until all the started threads are joined, since they use stack variables of this function */ + for (int32_t i = 0; i < CONCURRENT_THREAD_COUNT; i++) + { + thread_contexts[i].test_context = test_context; + thread_contexts[i].thread_id = i + 1; /*0 means "no owner"*/ + if (ThreadAPI_Create(&threads[i], concurrent_chaos_thread, &thread_contexts[i]) != THREADAPI_OK) + { + thread_api_failed = true; + break; + } + else + { + threads_created++; + } + } + for (int32_t i = 0; i < threads_created; i++) + { + int thread_result; + if ( + (ThreadAPI_Join(threads[i], &thread_result) != THREADAPI_OK) || + (thread_result != 0) + ) + { + thread_api_failed = true; + } + else + { + /* do nothing */ + } + } + + //assert + ASSERT_IS_FALSE(thread_api_failed); + ASSERT_ARE_EQUAL(int32_t, 0, interlocked_add(&test_context->failures, 0)); + ASSERT_IS_TRUE(interlocked_add_64(&test_context->successful_allocations, 0) > 0); + ASSERT_ARE_EQUAL(int64_t, interlocked_add_64(&test_context->successful_allocations, 0), interlocked_add_64(&g_dispose_count, 0)); + + if (auto_free_empty_pages) + { + /* do nothing, every page was freed when its last element was released */ + } + else + { + for (uint64_t page_index = 0; page_index < psa->page_count; page_index++) + { + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT free_result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(TEST_ELEMENT)(psa, page_index * CONCURRENT_PAGE_SIZE); + ASSERT_ARE_NOT_EQUAL(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY, free_result); + ASSERT_ARE_NOT_EQUAL(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS, free_result); + } + } + for (uint64_t page_index = 0; page_index < psa->page_count; page_index++) + { + ASSERT_IS_NULL(psa->pages[page_index]); + } + + //cleanup + free(test_context); + PAGED_SPARSE_ARRAY_ASSIGN(TEST_ELEMENT)(&psa, NULL); +} + +/* + * Producer/consumer ring buffer over a thread-safe array, the same usage pattern as an ordered writer: + * producers allocate sequence % RING_MAX_SIZE (at most RING_MAX_SIZE sequences ahead of the tail), + * a single consumer releases the elements in sequence order and asks the array to free a page when the tail leaves it. + * A page can then still hold elements of the next lap (allocated by producers), in which case it is not freed. + */ +#define RING_PRODUCER_COUNT 4 +#define RING_TOTAL_SEQUENCES 200000 +#define RING_MAX_SIZE 2048 +#define RING_PAGE_SIZE 256 + +typedef struct RING_TEST_CONTEXT_TAG +{ + const PAGED_SPARSE_ARRAY_TYPEDEF_NAME(RING_ELEMENT)* psa; + volatile_atomic int64_t next_sequence; + volatile_atomic int64_t tail; + volatile_atomic int32_t failures; /*once not 0, the producers and the consumer stop instead of waiting for each other*/ +} RING_TEST_CONTEXT; + +static bool ring_has_failed(RING_TEST_CONTEXT* test_context) +{ + return interlocked_add(&test_context->failures, 0) != 0; +} + +static int ring_producer_thread(void* arg) +{ + RING_TEST_CONTEXT* test_context = arg; + int64_t sequence; + while ( + ((sequence = interlocked_increment_64(&test_context->next_sequence) - 1) < RING_TOTAL_SEQUENCES) && + (!ring_has_failed(test_context)) + ) + { + /* wait until the sequence is within the window */ + while ( + (sequence - interlocked_add_64(&test_context->tail, 0) >= RING_MAX_SIZE) && + (!ring_has_failed(test_context)) + ) + { + ThreadAPI_Sleep(0); + } + + if (ring_has_failed(test_context)) + { + /* do nothing, the loop ends */ + } + else + { + RING_ELEMENT* element; + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT allocate_result = PAGED_SPARSE_ARRAY_ALLOCATE(RING_ELEMENT)(test_context->psa, (uint64_t)sequence % RING_MAX_SIZE, &element); + if (allocate_result != PAGED_SPARSE_ARRAY_ALLOCATE_OK) + { + uint64_t index = (uint64_t)sequence % RING_MAX_SIZE; + LogError("producer ALLOCATE failed, sequence=%" PRId64 ", index=%" PRIu64 ", page_index=%" PRIu64 ", tail=%" PRId64 ", allocate_result=%" PRI_MU_ENUM "", + sequence, index, index / RING_PAGE_SIZE, interlocked_add_64(&test_context->tail, 0), MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, allocate_result)); + (void)interlocked_increment(&test_context->failures); + } + else + { + (void)interlocked_exchange_64(&element->published_sequence_plus_one, sequence + 1); + } + } + } + return 0; +} + +static void run_ring_buffer_test(void) +{ + //arrange + PAGED_SPARSE_ARRAY_OPTIONS options; + options.thread_safe = true; + options.auto_free_empty_pages = false; + options.zero_new_pages = true; + PAGED_SPARSE_ARRAY(RING_ELEMENT) psa = PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(RING_ELEMENT)(RING_MAX_SIZE, RING_PAGE_SIZE, NULL, &options); + ASSERT_IS_NOT_NULL(psa); + + RING_TEST_CONTEXT test_context; + test_context.psa = psa; + (void)interlocked_exchange_64(&test_context.next_sequence, 0); + (void)interlocked_exchange_64(&test_context.tail, 0); + (void)interlocked_exchange(&test_context.failures, 0); + + THREAD_HANDLE producers[RING_PRODUCER_COUNT]; + uint32_t producers_created = 0; + bool thread_api_failed = false; + uint32_t pages_freed = 0; + uint32_t pages_kept_not_empty = 0; + + //act + /* no ASSERT until all the started producers are joined, since they use stack variables of this function */ + for (uint32_t i = 0; i < RING_PRODUCER_COUNT; i++) + { + if (ThreadAPI_Create(&producers[i], ring_producer_thread, &test_context) != THREADAPI_OK) + { + thread_api_failed = true; + (void)interlocked_increment(&test_context.failures); + break; + } + else + { + producers_created++; + } + } + + for (int64_t tail = 0; (tail < RING_TOTAL_SEQUENCES) && (!ring_has_failed(&test_context)); tail++) + { + uint64_t index = (uint64_t)tail % RING_MAX_SIZE; + RING_ELEMENT* element; + + /* wait until a producer allocated the element and published this sequence in it */ + while ( + (!ring_has_failed(&test_context)) && + ( + (PAGED_SPARSE_ARRAY_GET(RING_ELEMENT)(psa, index, &element) != PAGED_SPARSE_ARRAY_GET_OK) || + (interlocked_add_64(&element->published_sequence_plus_one, 0) != tail + 1) + ) + ) + { + ThreadAPI_Sleep(0); + } + + if (ring_has_failed(&test_context)) + { + /* do nothing, the loop ends */ + } + else + { + PAGED_SPARSE_ARRAY_RELEASE(RING_ELEMENT)(psa, index); + (void)interlocked_exchange_64(&test_context.tail, tail + 1); + + if ( + ((index + 1) % RING_PAGE_SIZE != 0) && + (index + 1 != RING_MAX_SIZE) + ) + { + /* do nothing, the tail did not leave the page */ + } + else + { + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT free_result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(RING_ELEMENT)(psa, index); + if (free_result == PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_FREED) + { + pages_freed++; + } + else if (free_result == PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY) + { + /* producers already allocated elements of the next lap in this page */ + pages_kept_not_empty++; + } + else + { + LogError("consumer got an unexpected FREE_PAGE_IF_EMPTY result, tail=%" PRId64 ", index=%" PRIu64 ", page_index=%" PRIu64 ", free_result=%" PRI_MU_ENUM "", + tail, index, index / RING_PAGE_SIZE, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, free_result)); + (void)interlocked_increment(&test_context.failures); + } + } + } + } + + for (uint32_t i = 0; i < producers_created; i++) + { + int thread_result; + if ( + (ThreadAPI_Join(producers[i], &thread_result) != THREADAPI_OK) || + (thread_result != 0) + ) + { + thread_api_failed = true; + } + else + { + /* do nothing */ + } + } + + //assert + LogInfo("ring buffer test: producers=%" PRIu32 "/%" PRIu32 ", sequences=%" PRIu32 ", consumed=%" PRId64 ", max_size=%" PRIu32 ", page_size=%" PRIu32 ", pages_freed=%" PRIu32 ", pages_kept_not_empty=%" PRIu32 ", failures=%" PRId32 ", thread_api_failed=%" PRI_BOOL "", + producers_created, (uint32_t)RING_PRODUCER_COUNT, (uint32_t)RING_TOTAL_SEQUENCES, interlocked_add_64(&test_context.tail, 0), (uint32_t)RING_MAX_SIZE, (uint32_t)RING_PAGE_SIZE, + pages_freed, pages_kept_not_empty, interlocked_add(&test_context.failures, 0), MU_BOOL_VALUE(thread_api_failed)); + ASSERT_IS_FALSE(thread_api_failed); + ASSERT_ARE_EQUAL(int32_t, 0, interlocked_add(&test_context.failures, 0)); + ASSERT_ARE_EQUAL(uint32_t, RING_TOTAL_SEQUENCES / RING_PAGE_SIZE, pages_freed + pages_kept_not_empty); + + /* every element was released, so every page that is still allocated is empty */ + for (uint64_t page_index = 0; page_index < psa->page_count; page_index++) + { + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT free_result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(RING_ELEMENT)(psa, page_index * RING_PAGE_SIZE); + ASSERT_ARE_NOT_EQUAL(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY, free_result); + ASSERT_IS_NULL(psa->pages[page_index]); + } + + //cleanup + PAGED_SPARSE_ARRAY_ASSIGN(RING_ELEMENT)(&psa, NULL); +} BEGIN_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) @@ -512,4 +1013,23 @@ TEST_FUNCTION(PAGED_SPARSE_ARRAY_with_malloc_element_chaos) PAGED_SPARSE_ARRAY_ASSIGN(TEST_ELEMENT_WITH_MALLOC)(&psa, NULL); } +/* + * Tests for arrays created with options + */ + +TEST_FUNCTION(PAGED_SPARSE_ARRAY_thread_safe_chaos_with_auto_free_empty_pages) +{ + run_concurrent_chaos_test(true, false); +} + +TEST_FUNCTION(PAGED_SPARSE_ARRAY_thread_safe_chaos_with_free_page_if_empty) +{ + run_concurrent_chaos_test(false, true); +} + +TEST_FUNCTION(PAGED_SPARSE_ARRAY_thread_safe_ring_buffer_with_free_page_if_empty_when_tail_leaves_a_page) +{ + run_ring_buffer_test(); +} + END_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) diff --git a/tests/paged_sparse_array_ut/paged_sparse_array_ut.c b/tests/paged_sparse_array_ut/paged_sparse_array_ut.c index b112c568..b9f1f072 100644 --- a/tests/paged_sparse_array_ut/paged_sparse_array_ut.c +++ b/tests/paged_sparse_array_ut/paged_sparse_array_ut.c @@ -44,12 +44,95 @@ MU_DEFINE_ENUM_STRINGS(UMOCK_C_ERROR_CODE, UMOCK_C_ERROR_CODE_VALUES) TEST_DEFINE_ENUM_TYPE(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_RESULT_VALUES) TEST_DEFINE_ENUM_TYPE(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_RESULT_VALUES) +TEST_DEFINE_ENUM_TYPE(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT_VALUES) + +#define TEST_MAX_SIZE 100 +#define TEST_PAGE_SIZE 16 +#define TEST_PAGE_COUNT 7 /*(TEST_MAX_SIZE + TEST_PAGE_SIZE - 1) / TEST_PAGE_SIZE*/ +#define TEST_INDEX_IN_FIRST_PAGE 5 +#define TEST_OTHER_INDEX_IN_FIRST_PAGE 10 +#define TEST_ITEM_VALUE 42 +#define TEST_GARBAGE_BYTE 0xFF +#define TEST_GARBAGE_ITEM_VALUE UINT32_MAX + +#define TEST_PSA_LOCK(psa) ((SRW_LOCK_LL*)&(psa)->lock) static void on_umock_c_error(UMOCK_C_ERROR_CODE error_code) { ASSERT_FAIL("umock_c reported error :%" PRI_MU_ENUM "", MU_ENUM_VALUE(UMOCK_C_ERROR_CODE, error_code)); } +static PAGED_SPARSE_ARRAY_OPTIONS test_make_options(bool thread_safe, bool auto_free_empty_pages, bool zero_new_pages) +{ + PAGED_SPARSE_ARRAY_OPTIONS options; + options.thread_safe = thread_safe; + options.auto_free_empty_pages = auto_free_empty_pages; + options.zero_new_pages = zero_new_pages; + return options; +} + +static PAGED_SPARSE_ARRAY(uint32_t) test_create_with_options(bool thread_safe, bool auto_free_empty_pages, bool zero_new_pages, PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(uint32_t) item_dispose_func) +{ + PAGED_SPARSE_ARRAY_OPTIONS options = test_make_options(thread_safe, auto_free_empty_pages, zero_new_pages); + PAGED_SPARSE_ARRAY(uint32_t) psa = PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(uint32_t)(TEST_MAX_SIZE, TEST_PAGE_SIZE, item_dispose_func, &options); + ASSERT_IS_NOT_NULL(psa); + umock_c_reset_all_calls(); + return psa; +} + +static uint32_t* test_allocate(PAGED_SPARSE_ARRAY(uint32_t) psa, uint64_t index) +{ + uint32_t* ptr = NULL; + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, index, &ptr)); + ASSERT_IS_NOT_NULL(ptr); + umock_c_reset_all_calls(); + return ptr; +} + +static bool test_is_allocated_in_bitmap(PAGED_SPARSE_ARRAY(uint32_t) psa, uint64_t index) +{ + PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)* page = psa->pages[index / TEST_PAGE_SIZE]; + ASSERT_IS_NOT_NULL(page); + return PAGED_SPARSE_ARRAY_IS_ALLOCATED(PAGED_SPARSE_ARRAY_GET_BITMAP(page, TEST_PAGE_SIZE), index % TEST_PAGE_SIZE) != 0; +} + +/*used by tests that simulate another thread calling into the array while the lock is not held*/ +static struct +{ + const PAGED_SPARSE_ARRAY_TYPEDEF_NAME(uint32_t)* psa; + uint64_t index_to_allocate; + bool was_called; +} g_concurrent_allocate; + +static void hook_srw_lock_ll_acquire_exclusive_allocates_concurrently(SRW_LOCK_LL* srw_lock_ll) +{ + if (g_concurrent_allocate.was_called) + { + /* do nothing, only one concurrent allocation is simulated */ + } + else + { + uint32_t* other_ptr; + g_concurrent_allocate.was_called = true; + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(g_concurrent_allocate.psa, g_concurrent_allocate.index_to_allocate, &other_ptr)); + } + real_srw_lock_ll_acquire_exclusive(srw_lock_ll); +} + +static void setup_concurrent_allocate_on_acquire_exclusive(PAGED_SPARSE_ARRAY(uint32_t) psa, uint64_t index_to_allocate) +{ + g_concurrent_allocate.psa = psa; + g_concurrent_allocate.index_to_allocate = index_to_allocate; + g_concurrent_allocate.was_called = false; + REGISTER_GLOBAL_MOCK_HOOK(srw_lock_ll_acquire_exclusive, hook_srw_lock_ll_acquire_exclusive_allocates_concurrently); +} + +static void cleanup_concurrent_allocate_on_acquire_exclusive(void) +{ + REGISTER_GLOBAL_MOCK_HOOK(srw_lock_ll_acquire_exclusive, real_srw_lock_ll_acquire_exclusive); + g_concurrent_allocate.psa = NULL; +} + BEGIN_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) @@ -60,6 +143,10 @@ TEST_SUITE_INITIALIZE(it_does_something) umock_c_init(on_umock_c_error); REGISTER_GBALLOC_HL_GLOBAL_MOCK_HOOK(); + REGISTER_SRW_LOCK_LL_GLOBAL_MOCK_HOOK(); + + REGISTER_GLOBAL_MOCK_FAIL_RETURN(malloc_flex, NULL); + REGISTER_GLOBAL_MOCK_FAIL_RETURN(srw_lock_ll_init, MU_FAILURE); } TEST_SUITE_CLEANUP(TestClassCleanup) @@ -211,6 +298,152 @@ TEST_FUNCTION(PAGED_SPARSE_ARRAY_CREATE_with_struct_type_succeeds) PAGED_SPARSE_ARRAY_ASSIGN(B_TEST)(&psa, NULL); } +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_046: [ PAGED_SPARSE_ARRAY_CREATE(T) shall create the paged sparse array with thread_safe set to false, auto_free_empty_pages set to true and zero_new_pages set to false. ]*/ +TEST_FUNCTION(PAGED_SPARSE_ARRAY_CREATE_uses_default_options_and_does_not_create_a_lock) +{ + //arrange + STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, TEST_PAGE_COUNT, sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)*))); + + //act + PAGED_SPARSE_ARRAY(uint32_t) psa = PAGED_SPARSE_ARRAY_CREATE(uint32_t)(TEST_MAX_SIZE, TEST_PAGE_SIZE, NULL); + + //assert + ASSERT_IS_NOT_NULL(psa); + ASSERT_IS_FALSE(psa->options.thread_safe); + ASSERT_IS_TRUE(psa->options.auto_free_empty_pages); + ASSERT_IS_FALSE(psa->options.zero_new_pages); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/*PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)*/ + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_047: [ If options is NULL, PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall fail and return NULL. ]*/ +TEST_FUNCTION(when_options_is_NULL_then_PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS_fails) +{ + //arrange + + //act + PAGED_SPARSE_ARRAY(uint32_t) psa = PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(uint32_t)(TEST_MAX_SIZE, TEST_PAGE_SIZE, NULL, NULL); + + //assert + ASSERT_IS_NULL(psa); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_048: [ PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall validate max_size and page_size, and allocate and initialize the paged sparse array, in the same way as PAGED_SPARSE_ARRAY_CREATE(T). ]*/ +TEST_FUNCTION(when_max_size_is_zero_then_PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS_fails) +{ + //arrange + PAGED_SPARSE_ARRAY_OPTIONS options = test_make_options(true, true, false); + + //act + PAGED_SPARSE_ARRAY(uint32_t) psa = PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(uint32_t)(0, TEST_PAGE_SIZE, NULL, &options); + + //assert + ASSERT_IS_NULL(psa); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_048: [ PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall validate max_size and page_size, and allocate and initialize the paged sparse array, in the same way as PAGED_SPARSE_ARRAY_CREATE(T). ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_049: [ PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall store a copy of *options in the structure. ]*/ +TEST_FUNCTION(PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS_without_thread_safe_succeeds_and_does_not_create_a_lock) +{ + //arrange + PAGED_SPARSE_ARRAY_OPTIONS options = test_make_options(false, false, true); + STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, TEST_PAGE_COUNT, sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)*))); + + //act + PAGED_SPARSE_ARRAY(uint32_t) psa = PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(uint32_t)(TEST_MAX_SIZE, TEST_PAGE_SIZE, NULL, &options); + + //assert + ASSERT_IS_NOT_NULL(psa); + ASSERT_ARE_EQUAL(uint64_t, TEST_MAX_SIZE, psa->max_size); + ASSERT_ARE_EQUAL(uint64_t, TEST_PAGE_SIZE, psa->page_size); + ASSERT_ARE_EQUAL(uint64_t, TEST_PAGE_COUNT, psa->page_count); + for (uint64_t i = 0; i < psa->page_count; i++) + { + ASSERT_IS_NULL(psa->pages[i]); + } + ASSERT_IS_FALSE(psa->options.thread_safe); + ASSERT_IS_FALSE(psa->options.auto_free_empty_pages); + ASSERT_IS_TRUE(psa->options.zero_new_pages); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_049: [ PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall store a copy of *options in the structure. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_050: [ If options->thread_safe is true, PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall call srw_lock_ll_init to initialize the lock. ]*/ +TEST_FUNCTION(PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS_with_thread_safe_succeeds_and_initializes_the_lock) +{ + //arrange + PAGED_SPARSE_ARRAY_OPTIONS options = test_make_options(true, true, false); + STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, TEST_PAGE_COUNT, sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)*))); + STRICT_EXPECTED_CALL(srw_lock_ll_init(IGNORED_ARG)); + + //act + PAGED_SPARSE_ARRAY(uint32_t) psa = PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(uint32_t)(TEST_MAX_SIZE, TEST_PAGE_SIZE, NULL, &options); + + //assert + ASSERT_IS_NOT_NULL(psa); + ASSERT_IS_TRUE(psa->options.thread_safe); + ASSERT_IS_TRUE(psa->options.auto_free_empty_pages); + ASSERT_IS_FALSE(psa->options.zero_new_pages); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_051: [ If srw_lock_ll_init fails, PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall free the paged sparse array and return NULL. ]*/ +TEST_FUNCTION(when_srw_lock_ll_init_fails_then_PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS_frees_the_array_and_fails) +{ + //arrange + PAGED_SPARSE_ARRAY_OPTIONS options = test_make_options(true, true, false); + STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, TEST_PAGE_COUNT, sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)*))); + STRICT_EXPECTED_CALL(srw_lock_ll_init(IGNORED_ARG)) + .SetReturn(MU_FAILURE); + STRICT_EXPECTED_CALL(free(IGNORED_ARG)); + + //act + PAGED_SPARSE_ARRAY(uint32_t) psa = PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(uint32_t)(TEST_MAX_SIZE, TEST_PAGE_SIZE, NULL, &options); + + //assert + ASSERT_IS_NULL(psa); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_048: [ PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall validate max_size and page_size, and allocate and initialize the paged sparse array, in the same way as PAGED_SPARSE_ARRAY_CREATE(T). ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_051: [ If srw_lock_ll_init fails, PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall free the paged sparse array and return NULL. ]*/ +TEST_FUNCTION(when_underlying_calls_fail_then_PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS_fails) +{ + //arrange + PAGED_SPARSE_ARRAY_OPTIONS options = test_make_options(true, true, false); + STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, TEST_PAGE_COUNT, sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)*))); + STRICT_EXPECTED_CALL(srw_lock_ll_init(IGNORED_ARG)); + + umock_c_negative_tests_snapshot(); + + for (size_t i = 0; i < umock_c_negative_tests_call_count(); i++) + { + if (umock_c_negative_tests_can_call_fail(i)) + { + umock_c_negative_tests_reset(); + umock_c_negative_tests_fail_call(i); + + //act + PAGED_SPARSE_ARRAY(uint32_t) psa = PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(uint32_t)(TEST_MAX_SIZE, TEST_PAGE_SIZE, NULL, &options); + + //assert + ASSERT_IS_NULL(psa, "On failed call %zu", i); + } + } +} + /*PAGED_SPARSE_ARRAY_DISPOSE(T)*/ /* Tests_SRS_PAGED_SPARSE_ARRAY_88_009: [ If paged_sparse_array is NULL, PAGED_SPARSE_ARRAY_DISPOSE(T) shall return. ]*/ @@ -269,6 +502,25 @@ TEST_FUNCTION(PAGED_SPARSE_ARRAY_DISPOSE_frees_all_allocated_pages) //clean } +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_052: [ If thread_safe is true, PAGED_SPARSE_ARRAY_DISPOSE(T) shall call srw_lock_ll_deinit. ]*/ +TEST_FUNCTION(PAGED_SPARSE_ARRAY_DISPOSE_with_thread_safe_frees_pages_and_deinitializes_the_lock) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + + STRICT_EXPECTED_CALL(free(IGNORED_ARG)); // page 0 + STRICT_EXPECTED_CALL(srw_lock_ll_deinit(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(free(IGNORED_ARG)); // THANDLE wrapper + + //act + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); + + //assert + ASSERT_IS_NULL(psa); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); +} + /*PAGED_SPARSE_ARRAY_ALLOCATE(T)*/ /* Tests_SRS_PAGED_SPARSE_ARRAY_88_013: [ PAGED_SPARSE_ARRAY_ALLOCATE(T) shall compute the page index as index / page_size. ]*/ @@ -477,6 +729,214 @@ TEST_FUNCTION(PAGED_SPARSE_ARRAY_ALLOCATE_fails_when_page_malloc_fails) PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); } +static PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)* test_allocate_page_memory_filled_with_garbage(void) +{ + size_t items_and_bitmap_size = TEST_PAGE_SIZE * sizeof(uint32_t) + PAGED_SPARSE_ARRAY_BITMAP_SIZE(TEST_PAGE_SIZE); + PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)* page_memory = real_gballoc_hl_malloc_flex(sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)), items_and_bitmap_size, 1); + ASSERT_IS_NOT_NULL(page_memory); + (void)memset(page_memory, TEST_GARBAGE_BYTE, sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)) + items_and_bitmap_size); + return page_memory; +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_053: [ If zero_new_pages is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall set all the items of a newly allocated page to zero. ]*/ +TEST_FUNCTION(PAGED_SPARSE_ARRAY_ALLOCATE_with_zero_new_pages_sets_all_items_of_the_new_page_to_zero) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(false, true, true, NULL); + PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)* page_memory = test_allocate_page_memory_filled_with_garbage(); + STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, IGNORED_ARG, 1)) + .SetReturn(page_memory); + + //act + uint32_t* ptr; + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &ptr); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, result); + ASSERT_ARE_EQUAL(void_ptr, page_memory, psa->pages[0]); + for (uint64_t i = 0; i < TEST_PAGE_SIZE; i++) + { + ASSERT_ARE_EQUAL(uint32_t, 0, psa->pages[0]->items[i]); + } + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_053: [ If zero_new_pages is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall set all the items of a newly allocated page to zero. ]*/ +TEST_FUNCTION(PAGED_SPARSE_ARRAY_ALLOCATE_without_zero_new_pages_leaves_the_items_of_the_new_page_untouched) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = PAGED_SPARSE_ARRAY_CREATE(uint32_t)(TEST_MAX_SIZE, TEST_PAGE_SIZE, NULL); + ASSERT_IS_NOT_NULL(psa); + PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)* page_memory = test_allocate_page_memory_filled_with_garbage(); + umock_c_reset_all_calls(); + STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, IGNORED_ARG, 1)) + .SetReturn(page_memory); + + //act + uint32_t* ptr; + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &ptr); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, result); + ASSERT_ARE_EQUAL(void_ptr, page_memory, psa->pages[0]); + for (uint64_t i = 0; i < TEST_PAGE_SIZE; i++) + { + ASSERT_ARE_EQUAL(uint32_t, TEST_GARBAGE_ITEM_VALUE, psa->pages[0]->items[i]); + } + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_054: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall acquire the lock in shared mode. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_055: [ If thread_safe is true and the page is not allocated, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall release the shared lock and acquire the lock in exclusive mode. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_056: [ After acquiring the lock in exclusive mode, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall check again whether the page is allocated, since another thread may have allocated it in the meantime. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_057: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall mark the element at index as allocated and increment the allocated count of the page using interlocked operations. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_058: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall release the lock (shared or exclusive, whichever is held) before returning. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_014: [ If the page is not allocated, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall call malloc_flex to allocate memory for the page structure containing page_size elements of type T plus a uint8_t allocation bitmap of (page_size + 7) / 8 bytes, and initialize all bitmap bits to 0 (not allocated). ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_017: [ PAGED_SPARSE_ARRAY_ALLOCATE(T) shall store a pointer to the element at index in *allocated_ptr and return PAGED_SPARSE_ARRAY_ALLOCATE_OK. ]*/ +TEST_FUNCTION(PAGED_SPARSE_ARRAY_ALLOCATE_with_thread_safe_allocates_the_page_under_the_exclusive_lock) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, IGNORED_ARG, 1)); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PSA_LOCK(psa))); + + //act + uint32_t* ptr; + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &ptr); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, result); + ASSERT_IS_NOT_NULL(psa->pages[0]); + ASSERT_ARE_EQUAL(void_ptr, &psa->pages[0]->items[TEST_INDEX_IN_FIRST_PAGE], ptr); + ASSERT_IS_TRUE(test_is_allocated_in_bitmap(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(uint64_t, 1, psa->pages[0]->allocated_count); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_054: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall acquire the lock in shared mode. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_057: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall mark the element at index as allocated and increment the allocated count of the page using interlocked operations. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_058: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall release the lock (shared or exclusive, whichever is held) before returning. ]*/ +TEST_FUNCTION(PAGED_SPARSE_ARRAY_ALLOCATE_with_thread_safe_in_an_allocated_page_only_takes_the_shared_lock) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); + + //act + uint32_t* ptr; + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE, &ptr); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, result); + ASSERT_ARE_EQUAL(void_ptr, &psa->pages[0]->items[TEST_OTHER_INDEX_IN_FIRST_PAGE], ptr); + ASSERT_IS_TRUE(test_is_allocated_in_bitmap(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(uint64_t, 2, psa->pages[0]->allocated_count); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_015: [ If the element at index is already allocated, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall fail and return PAGED_SPARSE_ARRAY_ALLOCATE_ALREADY_ALLOCATED. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_058: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall release the lock (shared or exclusive, whichever is held) before returning. ]*/ +TEST_FUNCTION(when_element_is_already_allocated_then_PAGED_SPARSE_ARRAY_ALLOCATE_with_thread_safe_fails) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); + + //act + uint32_t* ptr; + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &ptr); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_ALREADY_ALLOCATED, result); + ASSERT_ARE_EQUAL(uint64_t, 1, psa->pages[0]->allocated_count); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_018: [ If malloc_flex fails, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall fail and return PAGED_SPARSE_ARRAY_ALLOCATE_ERROR. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_058: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall release the lock (shared or exclusive, whichever is held) before returning. ]*/ +TEST_FUNCTION(when_malloc_flex_fails_then_PAGED_SPARSE_ARRAY_ALLOCATE_with_thread_safe_fails) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, IGNORED_ARG, 1)) + .SetReturn(NULL); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PSA_LOCK(psa))); + + //act + uint32_t* ptr; + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &ptr); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_ERROR, result); + ASSERT_IS_NULL(psa->pages[0]); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_056: [ After acquiring the lock in exclusive mode, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall check again whether the page is allocated, since another thread may have allocated it in the meantime. ]*/ +TEST_FUNCTION(when_another_thread_allocates_the_page_before_the_exclusive_lock_then_PAGED_SPARSE_ARRAY_ALLOCATE_with_thread_safe_uses_that_page) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); + setup_concurrent_allocate_on_acquire_exclusive(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE); + + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PSA_LOCK(psa))); + // the "other thread" allocates the page + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, IGNORED_ARG, 1)); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PSA_LOCK(psa))); + // no second page allocation + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PSA_LOCK(psa))); + + //act + uint32_t* ptr; + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &ptr); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, result); + ASSERT_IS_TRUE(g_concurrent_allocate.was_called); + ASSERT_ARE_EQUAL(void_ptr, &psa->pages[0]->items[TEST_INDEX_IN_FIRST_PAGE], ptr); + ASSERT_IS_TRUE(test_is_allocated_in_bitmap(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_IS_TRUE(test_is_allocated_in_bitmap(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(uint64_t, 2, psa->pages[0]->allocated_count); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + cleanup_concurrent_allocate_on_acquire_exclusive(); + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + /*helper for item_dispose_func tests*/ MOCK_FUNCTION_WITH_CODE(, void, test_item_dispose_func, uint32_t*, item) @@ -537,7 +997,7 @@ TEST_FUNCTION(PAGED_SPARSE_ARRAY_RELEASE_with_NULL_item_dispose_func_does_not_ca /* Tests_SRS_PAGED_SPARSE_ARRAY_88_021: [ PAGED_SPARSE_ARRAY_RELEASE(T) shall compute the page index as index / page_size. ]*/ /* Tests_SRS_PAGED_SPARSE_ARRAY_88_024: [ PAGED_SPARSE_ARRAY_RELEASE(T) shall mark the element at index as not allocated. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_025: [ If all elements in the page are now not allocated, PAGED_SPARSE_ARRAY_RELEASE(T) shall free the page and set the page pointer to NULL. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_025: [ If auto_free_empty_pages is true and all elements in the page are now not allocated, PAGED_SPARSE_ARRAY_RELEASE(T) shall free the page and set the page pointer to NULL. ]*/ TEST_FUNCTION(PAGED_SPARSE_ARRAY_RELEASE_succeeds_and_frees_page_when_last_element) { //arrange @@ -658,6 +1118,195 @@ TEST_FUNCTION(PAGED_SPARSE_ARRAY_RELEASE_with_element_not_allocated_returns) PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); } +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_025: [ If auto_free_empty_pages is true and all elements in the page are now not allocated, PAGED_SPARSE_ARRAY_RELEASE(T) shall free the page and set the page pointer to NULL. ]*/ +TEST_FUNCTION(PAGED_SPARSE_ARRAY_RELEASE_with_auto_free_empty_pages_false_keeps_the_page_when_last_element) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(false, false, false, NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + + //act + PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_IS_NOT_NULL(psa->pages[0]); + ASSERT_IS_FALSE(test_is_allocated_in_bitmap(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(uint64_t, 0, psa->pages[0]->allocated_count); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_059: [ If thread_safe is true, PAGED_SPARSE_ARRAY_RELEASE(T) shall acquire the lock in shared mode. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_060: [ If thread_safe is true, PAGED_SPARSE_ARRAY_RELEASE(T) shall mark the element at index as not allocated using interlocked operations and, only if the element was still marked as allocated, decrement the allocated count of the page using interlocked operations. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_061: [ If thread_safe is true, PAGED_SPARSE_ARRAY_RELEASE(T) shall release the shared lock. ]*/ +TEST_FUNCTION(PAGED_SPARSE_ARRAY_RELEASE_with_thread_safe_keeps_the_page_when_other_elements_are_allocated) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + (void)test_allocate(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); + + //act + PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_IS_NOT_NULL(psa->pages[0]); + ASSERT_IS_FALSE(test_is_allocated_in_bitmap(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_IS_TRUE(test_is_allocated_in_bitmap(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(uint64_t, 1, psa->pages[0]->allocated_count); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_062: [ If thread_safe is true, auto_free_empty_pages is true and the allocated count of the page became 0, PAGED_SPARSE_ARRAY_RELEASE(T) shall acquire the lock in exclusive mode, free the page only if it is still allocated and its allocated count is still 0, and release the exclusive lock. ]*/ +TEST_FUNCTION(PAGED_SPARSE_ARRAY_RELEASE_with_thread_safe_frees_the_page_under_the_exclusive_lock_when_last_element) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(free(IGNORED_ARG)); // page + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PSA_LOCK(psa))); + + //act + PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_IS_NULL(psa->pages[0]); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_062: [ If thread_safe is true, auto_free_empty_pages is true and the allocated count of the page became 0, PAGED_SPARSE_ARRAY_RELEASE(T) shall acquire the lock in exclusive mode, free the page only if it is still allocated and its allocated count is still 0, and release the exclusive lock. ]*/ +TEST_FUNCTION(PAGED_SPARSE_ARRAY_RELEASE_with_thread_safe_and_auto_free_empty_pages_false_keeps_the_page_when_last_element) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, false, false, NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); + + //act + PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_IS_NOT_NULL(psa->pages[0]); + ASSERT_ARE_EQUAL(uint64_t, 0, psa->pages[0]->allocated_count); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_062: [ If thread_safe is true, auto_free_empty_pages is true and the allocated count of the page became 0, PAGED_SPARSE_ARRAY_RELEASE(T) shall acquire the lock in exclusive mode, free the page only if it is still allocated and its allocated count is still 0, and release the exclusive lock. ]*/ +TEST_FUNCTION(when_another_thread_allocates_in_the_page_before_the_exclusive_lock_then_PAGED_SPARSE_ARRAY_RELEASE_with_thread_safe_keeps_the_page) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + setup_concurrent_allocate_on_acquire_exclusive(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE); + + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PSA_LOCK(psa))); + // the "other thread" allocates an element in the (still allocated) page + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); + // no free of the page + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PSA_LOCK(psa))); + + //act + PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_IS_TRUE(g_concurrent_allocate.was_called); + ASSERT_IS_NOT_NULL(psa->pages[0]); + ASSERT_IS_FALSE(test_is_allocated_in_bitmap(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_IS_TRUE(test_is_allocated_in_bitmap(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(uint64_t, 1, psa->pages[0]->allocated_count); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + cleanup_concurrent_allocate_on_acquire_exclusive(); + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_044: [ If item_dispose_func is not NULL, PAGED_SPARSE_ARRAY_RELEASE(T) shall call item_dispose_func for the element being released. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_059: [ If thread_safe is true, PAGED_SPARSE_ARRAY_RELEASE(T) shall acquire the lock in shared mode. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_061: [ If thread_safe is true, PAGED_SPARSE_ARRAY_RELEASE(T) shall release the shared lock. ]*/ +TEST_FUNCTION(PAGED_SPARSE_ARRAY_RELEASE_with_thread_safe_calls_item_dispose_func_while_holding_the_shared_lock) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, test_item_dispose_func); + uint32_t* ptr = test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + (void)test_allocate(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(test_item_dispose_func(ptr)); + STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); + + //act + PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + umock_c_reset_all_calls(); + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_022: [ If the page is not allocated, PAGED_SPARSE_ARRAY_RELEASE(T) shall return. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_061: [ If thread_safe is true, PAGED_SPARSE_ARRAY_RELEASE(T) shall release the shared lock. ]*/ +TEST_FUNCTION(PAGED_SPARSE_ARRAY_RELEASE_with_thread_safe_and_page_not_allocated_returns) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, test_item_dispose_func); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); + + //act + PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_023: [ If the element at index is not allocated, PAGED_SPARSE_ARRAY_RELEASE(T) shall return. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_061: [ If thread_safe is true, PAGED_SPARSE_ARRAY_RELEASE(T) shall release the shared lock. ]*/ +TEST_FUNCTION(PAGED_SPARSE_ARRAY_RELEASE_with_thread_safe_and_element_not_allocated_returns) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, test_item_dispose_func); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); + + //act + PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_IS_TRUE(test_is_allocated_in_bitmap(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(uint64_t, 1, psa->pages[0]->allocated_count); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + umock_c_reset_all_calls(); + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + /*PAGED_SPARSE_ARRAY_GET(T)*/ /* Tests_SRS_PAGED_SPARSE_ARRAY_88_036: [ PAGED_SPARSE_ARRAY_GET(T) shall compute the page index as index / page_size. ]*/ @@ -786,6 +1435,219 @@ TEST_FUNCTION(PAGED_SPARSE_ARRAY_GET_fails_when_element_not_allocated) PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); } +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_063: [ If thread_safe is true, PAGED_SPARSE_ARRAY_GET(T) shall acquire the lock in shared mode. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_064: [ If thread_safe is true, PAGED_SPARSE_ARRAY_GET(T) shall check whether the element at index is allocated using interlocked operations. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_065: [ If thread_safe is true, PAGED_SPARSE_ARRAY_GET(T) shall release the shared lock before returning. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_039: [ PAGED_SPARSE_ARRAY_GET(T) shall store in item a pointer to the element at index and return PAGED_SPARSE_ARRAY_GET_OK. ]*/ +TEST_FUNCTION(PAGED_SPARSE_ARRAY_GET_with_thread_safe_succeeds_under_the_shared_lock) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); + uint32_t* allocated_ptr = test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + *allocated_ptr = TEST_ITEM_VALUE; + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); + + //act + uint32_t* item = NULL; + PAGED_SPARSE_ARRAY_GET_RESULT result = PAGED_SPARSE_ARRAY_GET(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &item); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_OK, result); + ASSERT_ARE_EQUAL(void_ptr, allocated_ptr, item); + ASSERT_ARE_EQUAL(uint32_t, TEST_ITEM_VALUE, *item); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_037: [ If the page is not allocated, PAGED_SPARSE_ARRAY_GET(T) shall return PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_065: [ If thread_safe is true, PAGED_SPARSE_ARRAY_GET(T) shall release the shared lock before returning. ]*/ +TEST_FUNCTION(PAGED_SPARSE_ARRAY_GET_with_thread_safe_and_page_not_allocated_returns_NOT_ALLOCATED) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); + + //act + uint32_t* item = NULL; + PAGED_SPARSE_ARRAY_GET_RESULT result = PAGED_SPARSE_ARRAY_GET(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &item); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_038: [ If the element at index is not allocated, PAGED_SPARSE_ARRAY_GET(T) shall return PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_064: [ If thread_safe is true, PAGED_SPARSE_ARRAY_GET(T) shall check whether the element at index is allocated using interlocked operations. ]*/ +TEST_FUNCTION(PAGED_SPARSE_ARRAY_GET_with_thread_safe_and_element_not_allocated_returns_NOT_ALLOCATED) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); + + //act + uint32_t* item = NULL; + PAGED_SPARSE_ARRAY_GET_RESULT result = PAGED_SPARSE_ARRAY_GET(uint32_t)(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE, &item); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/*PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)*/ + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_066: [ If paged_sparse_array is NULL, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall fail and return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS. ]*/ +TEST_FUNCTION(when_paged_sparse_array_is_NULL_then_PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_fails) +{ + //arrange + + //act + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(uint32_t)(NULL, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_067: [ If index is greater than or equal to max_size, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall fail and return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS. ]*/ +TEST_FUNCTION(when_index_is_max_size_then_PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_fails) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, false, false, NULL); + + //act + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(uint32_t)(psa, TEST_MAX_SIZE); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_069: [ PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall compute the page index as index / page_size. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_070: [ If the page is not allocated, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_ALLOCATED. ]*/ +TEST_FUNCTION(when_page_is_not_allocated_then_PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_returns_NOT_ALLOCATED) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(false, false, false, NULL); + + //act + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_ALLOCATED, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_071: [ If the allocated count of the page is not 0, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY. ]*/ +TEST_FUNCTION(when_page_is_not_empty_then_PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_returns_NOT_EMPTY) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(false, false, false, NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + + //act + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(uint32_t)(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY, result); + ASSERT_IS_NOT_NULL(psa->pages[0]); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_069: [ PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall compute the page index as index / page_size. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_072: [ Otherwise, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall free the page, set the page pointer to NULL and return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_FREED. ]*/ +TEST_FUNCTION(when_page_is_empty_then_PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_frees_it) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(false, false, false, NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + ASSERT_IS_NOT_NULL(psa->pages[0]); + umock_c_reset_all_calls(); + STRICT_EXPECTED_CALL(free(IGNORED_ARG)); // page + + //act + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(uint32_t)(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_FREED, result); + ASSERT_IS_NULL(psa->pages[0]); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_068: [ If thread_safe is true, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall acquire the lock in exclusive mode. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_072: [ Otherwise, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall free the page, set the page pointer to NULL and return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_FREED. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_073: [ If thread_safe is true, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall release the exclusive lock before returning. ]*/ +TEST_FUNCTION(when_page_is_empty_then_PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_with_thread_safe_frees_it_under_the_exclusive_lock) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, false, false, NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + ASSERT_IS_NOT_NULL(psa->pages[0]); + umock_c_reset_all_calls(); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(free(IGNORED_ARG)); // page + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PSA_LOCK(psa))); + + //act + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_FREED, result); + ASSERT_IS_NULL(psa->pages[0]); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_068: [ If thread_safe is true, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall acquire the lock in exclusive mode. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_071: [ If the allocated count of the page is not 0, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_073: [ If thread_safe is true, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall release the exclusive lock before returning. ]*/ +TEST_FUNCTION(when_page_is_not_empty_then_PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_with_thread_safe_returns_NOT_EMPTY) +{ + //arrange + PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, false, false, NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PSA_LOCK(psa))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PSA_LOCK(psa))); + + //act + PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY, result); + ASSERT_IS_NOT_NULL(psa->pages[0]); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + /* PAGED_SPARSE_ARRAY_CREATE */ /*Tests_SRS_PAGED_SPARSE_ARRAY_88_008: [ PAGED_SPARSE_ARRAY_CREATE(T) shall succeed and return a non-NULL value. ]*/ diff --git a/tests/paged_sparse_array_ut/paged_sparse_array_ut_pch.h b/tests/paged_sparse_array_ut/paged_sparse_array_ut_pch.h index 81b9eb8a..7d89adc1 100644 --- a/tests/paged_sparse_array_ut/paged_sparse_array_ut_pch.h +++ b/tests/paged_sparse_array_ut/paged_sparse_array_ut_pch.h @@ -22,9 +22,11 @@ #include "umock_c/umock_c_ENABLE_MOCKS.h" // ============================== ENABLE_MOCKS #include "c_pal/gballoc_hl.h" #include "c_pal/gballoc_hl_redirect.h" +#include "c_pal/srw_lock_ll.h" #include "umock_c/umock_c_DISABLE_MOCKS.h" // ============================== DISABLE_MOCKS #include "real_gballoc_hl.h" +#include "real_srw_lock_ll.h" #include "c_pal/thandle.h" #include "c_util/paged_sparse_array.h" From 7863b58284a73a9622bf2940eadab992940eea6a Mon Sep 17 00:00:00 2001 From: samrach9 Date: Fri, 6 Nov 2026 16:10:13 -0500 Subject: [PATCH 2/2] Move thread safety into new THREAD_SAFE_PAGED_SPARSE_ARRAY module Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CMakeLists.txt | 3 + devdoc/paged_sparse_array_requirements.md | 143 +- ...ad_safe_paged_sparse_array_requirements.md | 412 +++++ inc/c_util/paged_sparse_array.h | 12 - inc/c_util/paged_sparse_array_ll.h | 442 +---- inc/c_util/thread_safe_paged_sparse_array.h | 63 + .../thread_safe_paged_sparse_array_ll.h | 712 ++++++++ src/paged_sparse_array.c | 2 - src/thread_safe_paged_sparse_array.c | 8 + tests/CMakeLists.txt | 2 + .../paged_sparse_array_int.c | 520 ------ .../paged_sparse_array_ut.c | 864 +-------- .../paged_sparse_array_ut_pch.h | 2 - .../CMakeLists.txt | 16 + .../thread_safe_paged_sparse_array_int.c | 633 +++++++ .../CMakeLists.txt | 19 + .../thread_safe_paged_sparse_array_ut.c | 1565 +++++++++++++++++ .../thread_safe_paged_sparse_array_ut_pch.h | 37 + 18 files changed, 3489 insertions(+), 1966 deletions(-) create mode 100644 devdoc/thread_safe_paged_sparse_array_requirements.md create mode 100644 inc/c_util/thread_safe_paged_sparse_array.h create mode 100644 inc/c_util/thread_safe_paged_sparse_array_ll.h create mode 100644 src/thread_safe_paged_sparse_array.c create mode 100644 tests/thread_safe_paged_sparse_array_int/CMakeLists.txt create mode 100644 tests/thread_safe_paged_sparse_array_int/thread_safe_paged_sparse_array_int.c create mode 100644 tests/thread_safe_paged_sparse_array_ut/CMakeLists.txt create mode 100644 tests/thread_safe_paged_sparse_array_ut/thread_safe_paged_sparse_array_ut.c create mode 100644 tests/thread_safe_paged_sparse_array_ut/thread_safe_paged_sparse_array_ut_pch.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7cc32631..1538d9c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -163,6 +163,7 @@ set(c_util_c_files ./src/memory_data.c ./src/object_lifetime_tracker.c ./src/paged_sparse_array.c + ./src/thread_safe_paged_sparse_array.c ./src/rc_ptr.c ./src/rc_string.c ./src/rc_string_array.c @@ -227,6 +228,8 @@ set(c_util_h_files ./inc/c_util/sync_wrapper.h ./inc/c_util/paged_sparse_array.h ./inc/c_util/paged_sparse_array_ll.h + ./inc/c_util/thread_safe_paged_sparse_array.h + ./inc/c_util/thread_safe_paged_sparse_array_ll.h ./inc/c_util/tarray_ll.h ./inc/c_util/tarray.h ./inc/c_util/tcall_dispatcher.h diff --git a/devdoc/paged_sparse_array_requirements.md b/devdoc/paged_sparse_array_requirements.md index 1243ab43..028cc25f 100644 --- a/devdoc/paged_sparse_array_requirements.md +++ b/devdoc/paged_sparse_array_requirements.md @@ -2,9 +2,7 @@ ## Overview -`PAGED_SPARSE_ARRAY` is a module that provides a templatized paged sparse array data structure. The array has a maximum size and allocates memory in pages (groups of elements) on demand. By default, when all elements in a page are released, the page is automatically freed. - -The behavior can be customized at creation time with `PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)` (see [Options](#options)), which allows making the array thread-safe, keeping empty pages until the user frees them, and zeroing newly allocated pages. +`PAGED_SPARSE_ARRAY` is a module that provides a templatized paged sparse array data structure. The array has a maximum size and allocates memory in pages (groups of elements) on demand. When all elements in a page are released, the page is automatically freed. `PAGED_SPARSE_ARRAY` is a kind of `THANDLE`, all of the `THANDLE`'s API apply to `PAGED_SPARSE_ARRAY`. The following macros are provided with the same semantics as those of `THANDLE`'s: - `PAGED_SPARSE_ARRAY_INITIALIZE(T)` @@ -14,25 +12,12 @@ The behavior can be customized at creation time with `PAGED_SPARSE_ARRAY_CREATE_ ## Design -The array is organized into pages. Each page contains `page_size` elements. The total number of elements that can be stored is `max_size`. Pages are allocated lazily when an element is first allocated within that page. By default, when all elements in a page are released, the page is automatically freed. +The array is organized into pages. Each page contains `page_size` elements. The total number of elements that can be stored is `max_size`. Pages are allocated lazily when an element is first allocated within that page. When all elements in a page are released, the page is automatically freed. Each element has an allocation state (allocated or not allocated). The user can: - Allocate an index for usage (fails if already allocated) -- Release an index (by default, when all indexes in a page are released, the page is freed) +- Release an index (when all indexes in a page are released, the page is freed) - Get an index (fails if not allocated) -- Free the page that contains an index, if no element in that page is allocated - -### Options - -`PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)` takes a `PAGED_SPARSE_ARRAY_OPTIONS` structure with the following fields: - -| Option | Meaning | Value used by `PAGED_SPARSE_ARRAY_CREATE(T)` | -|---|---|---| -| `thread_safe` | `ALLOCATE`, `RELEASE`, `GET` and `FREE_PAGE_IF_EMPTY` may be called concurrently from multiple threads (see [Threading Model](#threading-model)). | `false` | -| `auto_free_empty_pages` | `RELEASE` frees a page when the last allocated element in it is released. When `false`, empty pages are kept until `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)` is called or the array is disposed. This avoids freeing and re-allocating a page when its elements are released and allocated again in quick succession. | `true` | -| `zero_new_pages` | All the items of a newly allocated page are set to zero. Items are not zeroed when an element is released and allocated again in a page that was kept. | `false` | - -`PAGED_SPARSE_ARRAY_CREATE(T)` keeps the behavior that existed before options were introduced: it does not create a lock and does not call any lock or interlocked APIs. ### Custom Item Dispose Function @@ -46,20 +31,9 @@ If no custom dispose function is needed, `NULL` can be passed to `PAGED_SPARSE_A ### Threading Model -`PAGED_SPARSE_ARRAY` is a `THANDLE`, which means that the ownership of the array (reference counting, assignment, move) is thread-safe. - -When the array is created with `PAGED_SPARSE_ARRAY_CREATE(T)` (or with `thread_safe` set to `false`), the operations on the array contents (ALLOCATE, RELEASE, GET, FREE_PAGE_IF_EMPTY) are **not thread-safe**. If multiple threads need to access the same `PAGED_SPARSE_ARRAY` instance, the caller must provide external synchronization (e.g., using a mutex or SRW lock) to protect against concurrent modifications. - -When the array is created with `thread_safe` set to `true`, ALLOCATE, RELEASE, GET and FREE_PAGE_IF_EMPTY may be called concurrently from any number of threads: -- The array owns an `SRW_LOCK_LL`. ALLOCATE, RELEASE and GET hold it in shared mode, so they run concurrently with each other. -- The lock is held in exclusive mode only to allocate a page or to free a page (once per `page_size` elements in the common case). -- The allocation bitmap and the per-page allocated count are changed with interlocked operations while the lock is held in shared mode. -- A page is freed only while the lock is held in exclusive mode and only if no element in it is allocated. Therefore a pointer obtained from ALLOCATE or GET for an `index` remains valid until that `index` is released. +`PAGED_SPARSE_ARRAY` is a `THANDLE`, which means that the ownership of the array (reference counting, assignment, move) is thread-safe. However, the operations on the array contents (ALLOCATE, RELEASE, GET) are **not thread-safe**. -The rules for callers of a thread-safe array are: -- Only the user that allocated an `index` shall release it, and an `index` shall not be released more than once per allocation. -- The array only protects its own bookkeeping. Synchronizing access to the contents of an element (e.g., one thread writing an element while another thread reads it after GET) is the responsibility of the caller. -- `item_dispose_func` is called while the lock is held. It shall not call back into the same array, otherwise it can deadlock (SRW locks are not re-entrant). +If multiple threads need to access the same `PAGED_SPARSE_ARRAY` instance, the caller must provide external synchronization (e.g., using a mutex or SRW lock) to protect against concurrent modifications. ### Page Structure @@ -146,38 +120,15 @@ MU_DEFINE_ENUM(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_R PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED MU_DEFINE_ENUM(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_RESULT_VALUES) - -/*result codes for PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY*/ -#define PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT_VALUES \ - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_FREED, \ - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS, \ - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_ALLOCATED, \ - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY - -MU_DEFINE_ENUM(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT_VALUES) - -/*options for PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS*/ -typedef struct PAGED_SPARSE_ARRAY_OPTIONS_TAG -{ - bool thread_safe; - bool auto_free_empty_pages; - bool zero_new_pages; -} PAGED_SPARSE_ARRAY_OPTIONS; - -/*format and values to log a PAGED_SPARSE_ARRAY_OPTIONS, e.g. LogInfo("options=%" PRI_PAGED_SPARSE_ARRAY_OPTIONS "", PAGED_SPARSE_ARRAY_OPTIONS_VALUES(options))*/ -#define PRI_PAGED_SPARSE_ARRAY_OPTIONS "s{ thread_safe=%" PRI_BOOL ", auto_free_empty_pages=%" PRI_BOOL ", zero_new_pages=%" PRI_BOOL " }" -#define PAGED_SPARSE_ARRAY_OPTIONS_VALUES(options) "", MU_BOOL_VALUE((options).thread_safe), MU_BOOL_VALUE((options).auto_free_empty_pages), MU_BOOL_VALUE((options).zero_new_pages) ``` The macros expand to these useful APIs: ```c PAGED_SPARSE_ARRAY(T) PAGED_SPARSE_ARRAY_CREATE(T)(uint64_t max_size, uint64_t page_size, PAGED_SPARSE_ARRAY_ITEM_DISPOSE_FUNC(T) item_dispose_func); -PAGED_SPARSE_ARRAY(T) PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)(uint64_t max_size, uint64_t page_size, PAGED_SPARSE_ARRAY_ITEM_DISPOSE_FUNC(T) item_dispose_func, const PAGED_SPARSE_ARRAY_OPTIONS* options); PAGED_SPARSE_ARRAY_ALLOCATE_RESULT PAGED_SPARSE_ARRAY_ALLOCATE(T)(PAGED_SPARSE_ARRAY(T) paged_sparse_array, uint64_t index, T** allocated_ptr); void PAGED_SPARSE_ARRAY_RELEASE(T)(PAGED_SPARSE_ARRAY(T) paged_sparse_array, uint64_t index); PAGED_SPARSE_ARRAY_GET_RESULT PAGED_SPARSE_ARRAY_GET(T)(PAGED_SPARSE_ARRAY(T) paged_sparse_array, uint64_t index, T** item); -PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)(PAGED_SPARSE_ARRAY(T) paged_sparse_array, uint64_t index); ``` ### PAGED_SPARSE_ARRAY(T) @@ -246,26 +197,6 @@ PAGED_SPARSE_ARRAY(T) PAGED_SPARSE_ARRAY_CREATE(T)(uint64_t max_size, uint64_t p **SRS_PAGED_SPARSE_ARRAY_88_008: [** `PAGED_SPARSE_ARRAY_CREATE(T)` shall succeed and return a non-`NULL` value. **]** -**SRS_PAGED_SPARSE_ARRAY_88_046: [** `PAGED_SPARSE_ARRAY_CREATE(T)` shall create the paged sparse array with `thread_safe` set to `false`, `auto_free_empty_pages` set to `true` and `zero_new_pages` set to `false`. **]** - -### PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) - -```c -PAGED_SPARSE_ARRAY(T) PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)(uint64_t max_size, uint64_t page_size, PAGED_SPARSE_ARRAY_ITEM_DISPOSE_FUNC(T) item_dispose_func, const PAGED_SPARSE_ARRAY_OPTIONS* options); -``` - -`PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)` creates a new paged sparse array with the specified maximum size, page size and options (see [Options](#options)). - -**SRS_PAGED_SPARSE_ARRAY_88_047: [** If `options` is `NULL`, `PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)` shall fail and return `NULL`. **]** - -**SRS_PAGED_SPARSE_ARRAY_88_048: [** `PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)` shall validate `max_size` and `page_size`, and allocate and initialize the paged sparse array, in the same way as `PAGED_SPARSE_ARRAY_CREATE(T)`. **]** - -**SRS_PAGED_SPARSE_ARRAY_88_049: [** `PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)` shall store a copy of `*options` in the structure. **]** - -**SRS_PAGED_SPARSE_ARRAY_88_050: [** If `options->thread_safe` is `true`, `PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)` shall call `srw_lock_ll_init` to initialize the lock. **]** - -**SRS_PAGED_SPARSE_ARRAY_88_051: [** If `srw_lock_ll_init` fails, `PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)` shall free the paged sparse array and return `NULL`. **]** - ### PAGED_SPARSE_ARRAY_DISPOSE(T) ```c @@ -280,8 +211,6 @@ static void PAGED_SPARSE_ARRAY_DISPOSE(T)(PAGED_SPARSE_ARRAY(T) paged_sparse_arr **SRS_PAGED_SPARSE_ARRAY_88_010: [** `PAGED_SPARSE_ARRAY_DISPOSE(T)` shall free all pages that are non-`NULL`. **]** -**SRS_PAGED_SPARSE_ARRAY_88_052: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_DISPOSE(T)` shall call `srw_lock_ll_deinit`. **]** - ### PAGED_SPARSE_ARRAY_ALLOCATE(T) ```c @@ -308,20 +237,6 @@ PAGED_SPARSE_ARRAY_ALLOCATE_RESULT PAGED_SPARSE_ARRAY_ALLOCATE(T)(PAGED_SPARSE_A **SRS_PAGED_SPARSE_ARRAY_88_018: [** If `malloc_flex` fails, `PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall fail and return `PAGED_SPARSE_ARRAY_ALLOCATE_ERROR`. **]** -**SRS_PAGED_SPARSE_ARRAY_88_053: [** If `zero_new_pages` is `true`, `PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall set all the items of a newly allocated page to zero. **]** - -The following requirements apply when `thread_safe` is `true`. The common case (the page is already allocated) only holds the lock in shared mode. The lock is held in exclusive mode only when the page has to be allocated. - -**SRS_PAGED_SPARSE_ARRAY_88_054: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall acquire the lock in shared mode. **]** - -**SRS_PAGED_SPARSE_ARRAY_88_055: [** If `thread_safe` is `true` and the page is not allocated, `PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall release the shared lock and acquire the lock in exclusive mode. **]** - -**SRS_PAGED_SPARSE_ARRAY_88_056: [** After acquiring the lock in exclusive mode, `PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall check again whether the page is allocated, since another thread may have allocated it in the meantime. **]** - -**SRS_PAGED_SPARSE_ARRAY_88_057: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall mark the element at `index` as allocated and increment the allocated count of the page using interlocked operations. **]** - -**SRS_PAGED_SPARSE_ARRAY_88_058: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall release the lock (shared or exclusive, whichever is held) before returning. **]** - ### PAGED_SPARSE_ARRAY_RELEASE(T) ```c @@ -331,7 +246,7 @@ void PAGED_SPARSE_ARRAY_RELEASE(T)(PAGED_SPARSE_ARRAY(T) paged_sparse_array, uin `PAGED_SPARSE_ARRAY_RELEASE(T)` releases the element at the specified index. -> **Note:** If a custom `item_dispose_func` was provided at creation, it will be called for the element being released before marking it as unallocated. This allows automatic cleanup of elements during individual releases as well as during final disposal of the array. When `thread_safe` is `true`, `item_dispose_func` is called while the lock is held in shared mode, so it shall not call back into the same array. +> **Note:** If a custom `item_dispose_func` was provided to `PAGED_SPARSE_ARRAY_CREATE(T)`, it will be called for the element being released before marking it as unallocated. This allows automatic cleanup of elements during individual releases as well as during final disposal of the array. **SRS_PAGED_SPARSE_ARRAY_88_019: [** If `paged_sparse_array` is `NULL`, `PAGED_SPARSE_ARRAY_RELEASE(T)` shall return. **]** @@ -347,17 +262,7 @@ void PAGED_SPARSE_ARRAY_RELEASE(T)(PAGED_SPARSE_ARRAY(T) paged_sparse_array, uin **SRS_PAGED_SPARSE_ARRAY_88_024: [** `PAGED_SPARSE_ARRAY_RELEASE(T)` shall mark the element at `index` as not allocated. **]** -**SRS_PAGED_SPARSE_ARRAY_88_025: [** If `auto_free_empty_pages` is `true` and all elements in the page are now not allocated, `PAGED_SPARSE_ARRAY_RELEASE(T)` shall free the page and set the page pointer to `NULL`. **]** - -The following requirements apply when `thread_safe` is `true`. - -**SRS_PAGED_SPARSE_ARRAY_88_059: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_RELEASE(T)` shall acquire the lock in shared mode. **]** - -**SRS_PAGED_SPARSE_ARRAY_88_060: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_RELEASE(T)` shall mark the element at `index` as not allocated using interlocked operations and, only if the element was still marked as allocated, decrement the allocated count of the page using interlocked operations. **]** - -**SRS_PAGED_SPARSE_ARRAY_88_061: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_RELEASE(T)` shall release the shared lock. **]** - -**SRS_PAGED_SPARSE_ARRAY_88_062: [** If `thread_safe` is `true`, `auto_free_empty_pages` is `true` and the allocated count of the page became 0, `PAGED_SPARSE_ARRAY_RELEASE(T)` shall acquire the lock in exclusive mode, free the page only if it is still allocated and its allocated count is still 0, and release the exclusive lock. **]** +**SRS_PAGED_SPARSE_ARRAY_88_025: [** If all elements in the page are now not allocated, `PAGED_SPARSE_ARRAY_RELEASE(T)` shall free the page and set the page pointer to `NULL`. **]** ### PAGED_SPARSE_ARRAY_GET(T) @@ -380,37 +285,3 @@ PAGED_SPARSE_ARRAY_GET_RESULT PAGED_SPARSE_ARRAY_GET(T)(PAGED_SPARSE_ARRAY(T) pa **SRS_PAGED_SPARSE_ARRAY_88_038: [** If the element at `index` is not allocated, `PAGED_SPARSE_ARRAY_GET(T)` shall return `PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED`. **]** **SRS_PAGED_SPARSE_ARRAY_88_039: [** `PAGED_SPARSE_ARRAY_GET(T)` shall store in `item` a pointer to the element at `index` and return `PAGED_SPARSE_ARRAY_GET_OK`. **]** - -The following requirements apply when `thread_safe` is `true`. The pointer returned in `item` remains valid until the element at `index` is released. - -**SRS_PAGED_SPARSE_ARRAY_88_063: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_GET(T)` shall acquire the lock in shared mode. **]** - -**SRS_PAGED_SPARSE_ARRAY_88_064: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_GET(T)` shall check whether the element at `index` is allocated using interlocked operations. **]** - -**SRS_PAGED_SPARSE_ARRAY_88_065: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_GET(T)` shall release the shared lock before returning. **]** - -### PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) - -```c -PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)(PAGED_SPARSE_ARRAY(T) paged_sparse_array, uint64_t index); -``` - -`PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)` frees the page that contains the element at `index`, if no element in that page is allocated. `index` can be the index of any element in the page. - -This is intended for arrays created with `auto_free_empty_pages` set to `false`, where the user decides when an empty page is freed (for example, when a consumer is done with all the indexes in a page). It can also be used with `auto_free_empty_pages` set to `true`, although in that case empty pages are already freed by `PAGED_SPARSE_ARRAY_RELEASE(T)`. - -**SRS_PAGED_SPARSE_ARRAY_88_066: [** If `paged_sparse_array` is `NULL`, `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)` shall fail and return `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS`. **]** - -**SRS_PAGED_SPARSE_ARRAY_88_067: [** If `index` is greater than or equal to `max_size`, `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)` shall fail and return `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS`. **]** - -**SRS_PAGED_SPARSE_ARRAY_88_068: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)` shall acquire the lock in exclusive mode. **]** - -**SRS_PAGED_SPARSE_ARRAY_88_069: [** `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)` shall compute the page index as `index / page_size`. **]** - -**SRS_PAGED_SPARSE_ARRAY_88_070: [** If the page is not allocated, `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)` shall return `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_ALLOCATED`. **]** - -**SRS_PAGED_SPARSE_ARRAY_88_071: [** If the allocated count of the page is not 0, `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)` shall return `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY`. **]** - -**SRS_PAGED_SPARSE_ARRAY_88_072: [** Otherwise, `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)` shall free the page, set the page pointer to `NULL` and return `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_FREED`. **]** - -**SRS_PAGED_SPARSE_ARRAY_88_073: [** If `thread_safe` is `true`, `PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)` shall release the exclusive lock before returning. **]** diff --git a/devdoc/thread_safe_paged_sparse_array_requirements.md b/devdoc/thread_safe_paged_sparse_array_requirements.md new file mode 100644 index 00000000..058b0c61 --- /dev/null +++ b/devdoc/thread_safe_paged_sparse_array_requirements.md @@ -0,0 +1,412 @@ +# `thread_safe_paged_sparse_array` requirements + +## Overview + +`THREAD_SAFE_PAGED_SPARSE_ARRAY` is a module that provides a templatized paged sparse array whose `ALLOCATE`, `RELEASE`, `GET` and `FREE_EMPTY_PAGES` can be called concurrently from multiple threads. + +It is the thread-safe counterpart of [`PAGED_SPARSE_ARRAY`](paged_sparse_array_requirements.md): the array has a maximum size and allocates memory in pages (groups of elements) on demand. It uses the result codes of `PAGED_SPARSE_ARRAY` (`PAGED_SPARSE_ARRAY_ALLOCATE_RESULT` and `PAGED_SPARSE_ARRAY_GET_RESULT`) and adds: +- a page slot for each page, with a use count and a lock (see [Page Slots](#page-slots) and [Threading Model](#threading-model)). `ALLOCATE`, `RELEASE` and `GET` do not acquire any lock when the page is allocated, the lock of a page is acquired only to allocate or to free that page, +- `THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T)`, which frees the empty pages that contain the indexes of a range, +- 2 options selected at compile time (see [Options](#options)). + +`THREAD_SAFE_PAGED_SPARSE_ARRAY` is a kind of `THANDLE`, all of the `THANDLE`'s API apply to `THREAD_SAFE_PAGED_SPARSE_ARRAY`. The following macros are provided with the same semantics as those of `THANDLE`'s: +- `THREAD_SAFE_PAGED_SPARSE_ARRAY_INITIALIZE(T)` +- `THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(T)` +- `THREAD_SAFE_PAGED_SPARSE_ARRAY_MOVE(T)` +- `THREAD_SAFE_PAGED_SPARSE_ARRAY_INITIALIZE_MOVE(T)` + +## Design + +The array is organized into pages. Each page contains `page_size` elements. The total number of elements that can be stored is `max_size`. Pages are allocated lazily when an element is first allocated within that page. + +Each element has an allocation state (allocated or not allocated). The user can: +- Allocate an index for usage (fails if already allocated) +- Release an index (by default, when all indexes in a page are released, the page is freed) +- Get an index (fails if not allocated) +- Free the pages that contain the indexes of a range, if no element in those pages is allocated + +### Page Structure + +Each page is a single memory allocation containing: +- An array of `page_size` elements of type `T` (the items) +- An allocation bitmap: a `uint8_t` array where each bit tracks whether the corresponding element is allocated (1) or not (0) + +The bitmap size is `(page_size + 7) / 8` bytes. The bitmap is stored immediately after the items in the same allocation. + +Elements are **not stored contiguously in memory**, pointer arithmetic between elements shall not be used. Always use the provided API functions to access elements by index. + +### Page Slots + +The array has one page slot for each page. The page slots are allocated with the array and exist as long as the array exists, also when their page is not allocated. A page slot contains: +- `page`: a pointer to the page (see [Page Structure](#page-structure)), `NULL` when the page is not allocated +- `use_count`: the count of allocated elements in the page plus the count of calls that use the page at that moment. While the page is being freed, `THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING` (262) is added to it. +- `lock`: an `SRW_LOCK_LL` that is acquired in exclusive mode only to allocate or to free the page + +`ALLOCATE`, `RELEASE` and `GET` begin to use a page before they access it (`THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T)`) and end the use when they no longer access it (`THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T)`). When `ALLOCATE` succeeds, the use of the call is not ended, it becomes the use of the allocated element and it is ended by `RELEASE`. A page is freed (`THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T)`) only if its use count is 0. + +### Visual Representation + +The following diagram shows a `THREAD_SAFE_PAGED_SPARSE_ARRAY` with `page_size=4` and `max_size=12` while a thread is in a call to `GET` for index 9: + +``` +THREAD_SAFE_PAGED_SPARSE_ARRAY (max_size=12, page_size=4, page_count=3) +│ +├── page_slots[0]: use_count=3 (3 allocated elements), lock +│ └── page ──► PAGE 0 +│ ├── items[0]: [DATA] ◄─ index 0 +│ ├── items[1]: [DATA] ◄─ index 1 +│ ├── items[2]: [ ] ◄─ index 2 (unallocated) +│ ├── items[3]: [DATA] ◄─ index 3 +│ └── bitmap: 0b1011 (slots 0,1,3 allocated) +│ +├── page_slots[1]: use_count=0, lock +│ └── page ──► NULL (page not yet allocated, indices 4-7) +│ +└── page_slots[2]: use_count=2 (1 allocated element + the call to GET), lock + └── page ──► PAGE 2 + ├── items[0]: [DATA] ◄─ index 8 + ├── items[1]: [ ] ◄─ index 9 (unallocated) + ├── items[2]: [ ] ◄─ index 10 (unallocated) + ├── items[3]: [ ] ◄─ index 11 (unallocated) + └── bitmap: 0b0001 (only slot 0 allocated) +``` + +### Options + +2 options are selected at compile time, each one by a name: + +| Option | Value | Meaning | +|---|---|---| +| `empty_pages` | `AUTO_FREE_EMPTY_PAGES` (default) | A page is freed when its use count becomes 0: no element in it is allocated and no call uses it. | +| `empty_pages` | `KEEP_EMPTY_PAGES` | Empty pages are kept until `FREE_EMPTY_PAGES` is called or the array is disposed. This avoids freeing and re-allocating a page when its elements are released and allocated again in quick succession (e.g. when the array is used as a ring buffer). | +| `new_pages` | `DO_NOT_ZERO_NEW_PAGES` (default) | The items of a newly allocated page are left uninitialized. | +| `new_pages` | `ZERO_NEW_PAGES` | All the items of a newly allocated page are set to zero. Items are not zeroed when an element is released and allocated again in a page that was kept. | + +`THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE(T)` uses the default values. Other values are selected with `THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE_WITH_OPTIONS(T, empty_pages, new_pages)`, e.g. `THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE_WITH_OPTIONS(T, KEEP_EMPTY_PAGES, ZERO_NEW_PAGES)`. At the low level (`thread_safe_paged_sparse_array_ll.h`), each `THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_*_DEFINE(C, T)` macro has a `_WITH_OPTIONS(C, T, empty_pages, new_pages)` variant. `new_pages` changes only the allocation of a page and `empty_pages` changes only the end of the use of a page, the other functions only log the options. All the functions of a type shall be defined with the same options: `THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_DEFINE_WITH_OPTIONS(C, T, empty_pages, new_pages)` (and `THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_DEFINE(C, T)`) defines all of them at once. + +The value of each option is pasted to a prefix that is specific to that option (`THREAD_SAFE_PAGED_SPARSE_ARRAY_EMPTY_PAGES_OPTION_` or `THREAD_SAFE_PAGED_SPARSE_ARRAY_NEW_PAGES_OPTION_`), and only the names above are defined (as `1` or `0`). Therefore a misspelled name, a number, or the 2 options given in the wrong order expand to an undeclared identifier and do not compile. + +Since the options are compile-time constants, only the code for the selected options is compiled. + +### Custom Item Dispose Function + +The custom item dispose function is passed to `THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T)` and is called: +- By `THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T)` when an element is released +- By `THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE(T)` for all elements that are still allocated at the time of disposal + +If no custom dispose function is needed, `NULL` can be passed to `THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T)`. + +### Threading Model + +`ALLOCATE`, `RELEASE`, `GET` and `FREE_EMPTY_PAGES` may be called concurrently from any number of threads: +- `ALLOCATE`, `RELEASE` and `GET` do not acquire any lock when the page is allocated. They change the use count of the page and the allocation bitmap (see [Page Slots](#page-slots)) with interlocked operations. +- The lock of a page is acquired in exclusive mode only to allocate that page (by `ALLOCATE`, when the page is not allocated) and to free that page (when its use count became 0 and `empty_pages` is `AUTO_FREE_EMPTY_PAGES`, or by `FREE_EMPTY_PAGES`). Calls that use other pages, and calls that do not allocate or free the page, do not wait for it. +- A page is freed only after its use count was changed from 0 to `THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING` with an interlocked compare-exchange: no element in the page is allocated and no call uses it. A call that begins to use the page while it is being freed considers the page not allocated. The memory of the page is freed after the lock of the page is released. +- Therefore a pointer obtained from `ALLOCATE` or `GET` for an `index` remains valid until that `index` is released. +- Since the use count also counts the calls that use a page, `FREE_EMPTY_PAGES` does not free an empty page that another call uses at the same time. When `empty_pages` is `AUTO_FREE_EMPTY_PAGES`, the call that ends the last use of an empty page frees it. + +The rules for callers are: +- Only the user that allocated an `index` shall release it, and an `index` shall not be released more than once per allocation. +- The array only protects its own bookkeeping. Synchronizing access to the contents of an element (e.g., one thread writing an element while another thread reads it after `GET`) is the responsibility of the caller. +- `item_dispose_func` is called without holding any lock. + +## Exposed API + +```c +/*to be used as the type of handle that wraps T*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY(T) + +/*custom item dispose function type*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T) MU_C2(THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC_, T) +typedef void (*THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T))(T* item); + +/*to be used in a header file*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DECLARE(T) + +/*to be used in a .c file*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE(T) +/*empty_pages is AUTO_FREE_EMPTY_PAGES or KEEP_EMPTY_PAGES, new_pages is DO_NOT_ZERO_NEW_PAGES or ZERO_NEW_PAGES*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE_WITH_OPTIONS(T, empty_pages, new_pages) + +/*result codes for THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT_VALUES \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_OK, \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_INVALID_ARGS + +MU_DEFINE_ENUM(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT_VALUES) +``` + +The macros expand to these useful APIs: + +```c +THREAD_SAFE_PAGED_SPARSE_ARRAY(T) THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T)(uint64_t max_size, uint64_t page_size, THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T) item_dispose_func); +PAGED_SPARSE_ARRAY_ALLOCATE_RESULT THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)(THREAD_SAFE_PAGED_SPARSE_ARRAY(T) paged_sparse_array, uint64_t index, T** allocated_ptr); +void THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T)(THREAD_SAFE_PAGED_SPARSE_ARRAY(T) paged_sparse_array, uint64_t index); +PAGED_SPARSE_ARRAY_GET_RESULT THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T)(THREAD_SAFE_PAGED_SPARSE_ARRAY(T) paged_sparse_array, uint64_t index, T** item); +THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T)(THREAD_SAFE_PAGED_SPARSE_ARRAY(T) paged_sparse_array, uint64_t start_index, uint64_t end_index); +``` + +### THREAD_SAFE_PAGED_SPARSE_ARRAY(T) + +```c +#define THREAD_SAFE_PAGED_SPARSE_ARRAY(T) +``` + +`THREAD_SAFE_PAGED_SPARSE_ARRAY(T)` is a `THANDLE`(`THREAD_SAFE_PAGED_SPARSE_ARRAY_STRUCT_T`), where `THREAD_SAFE_PAGED_SPARSE_ARRAY_STRUCT_T` is a structure that holds the paged sparse array data and the page slots. + +### THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DECLARE(T) + +```c +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DECLARE(T) +``` + +`THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DECLARE(T)` is a macro to be used in a header declaration. + +It introduces the APIs (as MOCKABLE_FUNCTIONS) that can be called for a `THREAD_SAFE_PAGED_SPARSE_ARRAY`. + +Example usage: + +```c +THREAD_SAFE_PAGED_SPARSE_ARRAY_DEFINE_STRUCT_TYPE(int32_t) +THANDLE_TYPE_DECLARE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(int32_t)); +THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DECLARE(int32_t); +``` + +### THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE(T) + +```c +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE(T) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE_WITH_OPTIONS(T, empty_pages, new_pages) +``` + +`THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE(T)` is a macro to be used in a .c file to define all the needed functions for `THREAD_SAFE_PAGED_SPARSE_ARRAY(T)`, with `AUTO_FREE_EMPTY_PAGES` and `DO_NOT_ZERO_NEW_PAGES`. + +`THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE_WITH_OPTIONS(T, empty_pages, new_pages)` does the same with the given options (see [Options](#options)). + +Example usage: + +```c +THANDLE_TYPE_DEFINE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(int32_t)); +THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE(int32_t); + +/*or, to keep empty pages until FREE_EMPTY_PAGES is called and to zero new pages*/ +THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE_WITH_OPTIONS(int32_t, KEEP_EMPTY_PAGES, ZERO_NEW_PAGES); +``` + +### THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) + +```c +THREAD_SAFE_PAGED_SPARSE_ARRAY(T) THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T)(uint64_t max_size, uint64_t page_size, THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T) item_dispose_func); +``` + +`THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T)` creates a new thread safe paged sparse array with the specified maximum size and page size. + +`item_dispose_func` is an optional custom dispose function (see [Custom Item Dispose Function](#custom-item-dispose-function)). It may be `NULL` if no cleanup is needed. + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_001: [** If `max_size` is zero, `THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T)` shall fail and return `NULL`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_002: [** If `page_size` is zero, `THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T)` shall fail and return `NULL`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_003: [** If `max_size + page_size - 1` would overflow, `THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T)` shall fail and return `NULL`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_004: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T)` shall compute the number of pages as `(max_size + page_size - 1) / page_size`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_005: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T)` shall call `THANDLE_MALLOC_FLEX` to allocate memory for the paged sparse array with one page slot for each page. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_006: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T)` shall store `max_size`, `page_size`, and `item_dispose_func` in the structure. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_007: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T)` shall set the page pointer of each page slot to `NULL` and its use count to 0. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_008: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T)` shall call `srw_lock_ll_init` to initialize the lock of each page slot. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_009: [** If `srw_lock_ll_init` fails, `THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T)` shall call `srw_lock_ll_deinit` for the locks that were initialized, free the paged sparse array and return `NULL`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_010: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T)` shall succeed and return a non-`NULL` value. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_011: [** If there are any other errors, `THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T)` shall fail and return `NULL`. **]** + +### THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE(T) + +```c +static void THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE(T)(THREAD_SAFE_PAGED_SPARSE_ARRAY(T) paged_sparse_array); +``` + +`THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE(T)` is the internal dispose function called by `THANDLE` when all references to the paged sparse array are released. Since no other thread uses the array at that time, it does not acquire any lock. + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_012: [** If `paged_sparse_array` is `NULL`, `THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE(T)` shall return. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_013: [** If `item_dispose_func` is not `NULL`, `THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE(T)` shall call `item_dispose_func` for each element that is still allocated. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_014: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE(T)` shall free all pages that are non-`NULL`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_015: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE(T)` shall call `srw_lock_ll_deinit` for the lock of each page slot. **]** + +### THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T) + +```c +static T* THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T)(THREAD_SAFE_PAGED_SPARSE_ARRAY_STRUCT_T* array, uint64_t page_index); +``` + +`THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T)` is an internal function that `ALLOCATE`, `RELEASE` and `GET` call before they access the page with index `page_index`. It returns the page, or `NULL` if the page is not allocated or is being freed. In both cases the use is ended by calling `THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T)`. + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_016: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T)` shall increment the use count of the page slot using interlocked operations. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_017: [** If the use count is greater than or equal to `THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING`, `THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T)` shall return `NULL`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_018: [** Otherwise, `THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T)` shall return the page pointer of the page slot, read using interlocked operations. **]** + +### THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) + +```c +static void THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T)(THREAD_SAFE_PAGED_SPARSE_ARRAY_STRUCT_T* array, uint64_t page_index, int64_t ended_use_count); +``` + +`THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T)` is an internal function that ends `ended_use_count` uses of the page with index `page_index`: the use of a call, plus the use of an element when `RELEASE` releases it. + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_019: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T)` shall subtract `ended_use_count` from the use count of the page slot using interlocked operations. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_020: [** If `empty_pages` is `AUTO_FREE_EMPTY_PAGES`, the use count became 0 and the page is allocated, `THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T)` shall call `THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T)`. **]** + +### THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) + +```c +static void THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T)(THREAD_SAFE_PAGED_SPARSE_ARRAY_STRUCT_T* array, uint64_t page_index); +``` + +`THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T)` is an internal function that frees the page with index `page_index` if it is allocated, no element in it is allocated and no call uses it. + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_021: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T)` shall acquire the lock of the page slot in exclusive mode. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_022: [** If the page is not allocated, `THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T)` shall release the lock of the page slot and return. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_023: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T)` shall change the use count of the page slot from 0 to `THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING` using interlocked operations. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_024: [** If the use count was not 0, `THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T)` shall release the lock of the page slot and return. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_025: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T)` shall set the page pointer of the page slot to `NULL` using interlocked operations. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_026: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T)` shall subtract `THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING` from the use count of the page slot using interlocked operations. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_027: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T)` shall release the lock of the page slot. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_028: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T)` shall free the page. **]** + +### THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) + +```c +PAGED_SPARSE_ARRAY_ALLOCATE_RESULT THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)(THREAD_SAFE_PAGED_SPARSE_ARRAY(T) paged_sparse_array, uint64_t index, T** allocated_ptr); +``` + +`THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)` allocates the element at `index`. When the page is allocated, no lock is acquired. The lock of the page slot is acquired only when the page has to be allocated. When `ALLOCATE` succeeds, its use of the page is not ended: it becomes the use of the allocated element, which `RELEASE` ends. + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_029: [** If `paged_sparse_array` is `NULL`, `THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall fail and return `PAGED_SPARSE_ARRAY_ALLOCATE_INVALID_ARGS`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_030: [** If `allocated_ptr` is `NULL`, `THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall fail and return `PAGED_SPARSE_ARRAY_ALLOCATE_INVALID_ARGS`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_031: [** If `index` is greater than or equal to `max_size`, `THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall fail and return `PAGED_SPARSE_ARRAY_ALLOCATE_INVALID_ARGS`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_032: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall compute the page index as `index / page_size`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_033: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall call `THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T)`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_034: [** If the page is not allocated: **]** + + - **SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_035: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall call `THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T)` with `ended_use_count` 1. **]** + + - **SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_036: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall acquire the lock of the page slot in exclusive mode. **]** + + - **SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_037: [** If the page is still not allocated: **]** + + - **SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_038: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall call `malloc_flex` to allocate memory for `page_size` elements followed by the allocation bitmap. **]** + + - **SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_039: [** If `malloc_flex` fails, `THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall release the lock of the page slot and return `PAGED_SPARSE_ARRAY_ALLOCATE_ERROR`. **]** + + - **SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_040: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall set all the bits of the allocation bitmap to 0. **]** + + - **SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_041: [** If `new_pages` is `ZERO_NEW_PAGES`, `THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall set all the items of the page to zero. **]** + + - **SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_042: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall store the page pointer in the page slot using interlocked operations. **]** + + - **SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_043: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall increment the use count of the page slot using interlocked operations. **]** + + - **SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_044: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall release the lock of the page slot. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_045: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall mark the element at `index` as allocated using interlocked operations. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_046: [** If the element at `index` was already marked as allocated, `THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall call `THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T)` with `ended_use_count` 1 and return `PAGED_SPARSE_ARRAY_ALLOCATE_ALREADY_ALLOCATED`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_047: [** Otherwise, `THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)` shall store a pointer to the element at `index` in `allocated_ptr` and return `PAGED_SPARSE_ARRAY_ALLOCATE_OK`. **]** + +### THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) + +```c +void THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T)(THREAD_SAFE_PAGED_SPARSE_ARRAY(T) paged_sparse_array, uint64_t index); +``` + +`THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T)` releases the element at `index`. It does not acquire any lock, except when it ends the last use of an empty page and `empty_pages` is `AUTO_FREE_EMPTY_PAGES`: then the lock of the page slot is acquired to free the page. + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_048: [** If `paged_sparse_array` is `NULL`, `THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T)` shall return. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_049: [** If `index` is greater than or equal to `max_size`, `THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T)` shall return. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_050: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T)` shall compute the page index as `index / page_size`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_051: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T)` shall call `THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T)`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_052: [** If the page is not allocated, `THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T)` shall call `THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T)` with `ended_use_count` 1 and return. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_053: [** If the element at `index` is not allocated, `THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T)` shall call `THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T)` with `ended_use_count` 1 and return. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_054: [** If `item_dispose_func` is not `NULL`, `THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T)` shall call `item_dispose_func` for the element being released before marking the element as not allocated. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_055: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T)` shall mark the element at `index` as not allocated using interlocked operations. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_056: [** If the element was still marked as allocated, `THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T)` shall call `THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T)` with `ended_use_count` 2. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_057: [** Otherwise, `THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T)` shall call `THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T)` with `ended_use_count` 1. **]** + +### THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T) + +```c +PAGED_SPARSE_ARRAY_GET_RESULT THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T)(THREAD_SAFE_PAGED_SPARSE_ARRAY(T) paged_sparse_array, uint64_t index, T** item); +``` + +`THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T)` retrieves a pointer to the element at `index`. The pointer returned in `item` remains valid until the element at `index` is released. + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_058: [** If `paged_sparse_array` is `NULL`, `THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T)` shall fail and return `PAGED_SPARSE_ARRAY_GET_INVALID_ARGS`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_059: [** If `item` is `NULL`, `THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T)` shall fail and return `PAGED_SPARSE_ARRAY_GET_INVALID_ARGS`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_060: [** If `index` is greater than or equal to `max_size`, `THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T)` shall fail and return `PAGED_SPARSE_ARRAY_GET_INVALID_ARGS`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_061: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T)` shall compute the page index as `index / page_size`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_062: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T)` shall call `THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T)`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_063: [** If the page is not allocated, `THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T)` shall call `THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T)` with `ended_use_count` 1 and return `PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_064: [** If the element at `index` is not allocated, `THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T)` shall call `THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T)` with `ended_use_count` 1 and return `PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_065: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T)` shall store in `item` a pointer to the element at `index`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_066: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T)` shall call `THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T)` with `ended_use_count` 1 and return `PAGED_SPARSE_ARRAY_GET_OK`. **]** + +### THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T) + +```c +THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T)(THREAD_SAFE_PAGED_SPARSE_ARRAY(T) paged_sparse_array, uint64_t start_index, uint64_t end_index); +``` + +`THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T)` frees each page that contains at least one index from `start_index` to `end_index` (both included), if no element in that page is allocated and no call uses that page. Pages that are not allocated or not empty are skipped. A single page is freed by passing any index of the page as both `start_index` and `end_index`. + +It is used with `KEEP_EMPTY_PAGES` to decide when empty pages are freed (e.g. when the oldest elements of a ring buffer were released), and it can also be used with `AUTO_FREE_EMPTY_PAGES`. + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_067: [** If `paged_sparse_array` is `NULL`, `THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T)` shall fail and return `THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_INVALID_ARGS`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_068: [** If `start_index` is greater than `end_index`, `THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T)` shall fail and return `THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_INVALID_ARGS`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_069: [** If `end_index` is greater than or equal to `max_size`, `THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T)` shall fail and return `THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_INVALID_ARGS`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_070: [** For each page from `start_index / page_size` to `end_index / page_size`, `THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T)` shall call `THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T)`. **]** + +**SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_071: [** `THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T)` shall succeed and return `THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_OK`. **]** diff --git a/inc/c_util/paged_sparse_array.h b/inc/c_util/paged_sparse_array.h index 7a4edff8..9aeeb87d 100644 --- a/inc/c_util/paged_sparse_array.h +++ b/inc/c_util/paged_sparse_array.h @@ -13,9 +13,6 @@ #define PAGED_SPARSE_ARRAY_CREATE_DECLARE(T) PAGED_SPARSE_ARRAY_LL_CREATE_DECLARE(T, T) #define PAGED_SPARSE_ARRAY_CREATE_DEFINE(T) PAGED_SPARSE_ARRAY_LL_CREATE_DEFINE(T, T) -#define PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS_DECLARE(T) PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS_DECLARE(T, T) -#define PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS_DEFINE(T) PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS_DEFINE(T, T) - #define PAGED_SPARSE_ARRAY_DISPOSE_DEFINE(T) PAGED_SPARSE_ARRAY_LL_DISPOSE_DEFINE(T, T) #define PAGED_SPARSE_ARRAY_ALLOCATE_PAGE_INTERNAL_DEFINE(T) PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_DEFINE(T, T) @@ -29,15 +26,10 @@ #define PAGED_SPARSE_ARRAY_GET_DECLARE(T) PAGED_SPARSE_ARRAY_LL_GET_DECLARE(T, T) #define PAGED_SPARSE_ARRAY_GET_DEFINE(T) PAGED_SPARSE_ARRAY_LL_GET_DEFINE(T, T) -#define PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_DECLARE(T) PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_DECLARE(T, T) -#define PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_DEFINE(T) PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_DEFINE(T, T) - #define PAGED_SPARSE_ARRAY_CREATE(C) PAGED_SPARSE_ARRAY_LL_CREATE(C) -#define PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(C) PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS(C) #define PAGED_SPARSE_ARRAY_ALLOCATE(C) PAGED_SPARSE_ARRAY_LL_ALLOCATE(C) #define PAGED_SPARSE_ARRAY_RELEASE(C) PAGED_SPARSE_ARRAY_LL_RELEASE(C) #define PAGED_SPARSE_ARRAY_GET(C) PAGED_SPARSE_ARRAY_LL_GET(C) -#define PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(C) PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY(C) #define PAGED_SPARSE_ARRAY_INITIALIZE(T) PAGED_SPARSE_ARRAY_LL_INITIALIZE(T) #define PAGED_SPARSE_ARRAY_ASSIGN(T) PAGED_SPARSE_ARRAY_LL_ASSIGN(T) @@ -49,21 +41,17 @@ /*hint: have PAGED_SPARSE_ARRAY_DEFINE_STRUCT_TYPE(T) before PAGED_SPARSE_ARRAY_TYPE_DECLARE */ \ /*hint: have THANDLE_TYPE_DECLARE(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)) before PAGED_SPARSE_ARRAY_TYPE_DECLARE */ \ PAGED_SPARSE_ARRAY_CREATE_DECLARE(T) \ - PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS_DECLARE(T) \ PAGED_SPARSE_ARRAY_ALLOCATE_DECLARE(T) \ PAGED_SPARSE_ARRAY_RELEASE_DECLARE(T) \ PAGED_SPARSE_ARRAY_GET_DECLARE(T) \ - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_DECLARE(T) \ #define PAGED_SPARSE_ARRAY_TYPE_DEFINE(T) \ /*hint: have THANDLE_TYPE_DEFINE(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)) before PAGED_SPARSE_ARRAY_TYPE_DEFINE */ \ PAGED_SPARSE_ARRAY_DISPOSE_DEFINE(T) \ PAGED_SPARSE_ARRAY_ALLOCATE_PAGE_INTERNAL_DEFINE(T) \ PAGED_SPARSE_ARRAY_CREATE_DEFINE(T) \ - PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS_DEFINE(T) \ PAGED_SPARSE_ARRAY_ALLOCATE_DEFINE(T) \ PAGED_SPARSE_ARRAY_RELEASE_DEFINE(T) \ PAGED_SPARSE_ARRAY_GET_DEFINE(T) \ - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_DEFINE(T) \ #endif /*PAGED_SPARSE_ARRAY_H*/ diff --git a/inc/c_util/paged_sparse_array_ll.h b/inc/c_util/paged_sparse_array_ll.h index 11501051..9d8644f1 100644 --- a/inc/c_util/paged_sparse_array_ll.h +++ b/inc/c_util/paged_sparse_array_ll.h @@ -7,12 +7,9 @@ #ifdef __cplusplus #include #include -#include #else // __cplusplus #include -#include #include -#include #endif // __cplusplus #include "macro_utils/macro_utils.h" @@ -21,8 +18,6 @@ #include "c_pal/gballoc_hl.h" #include "c_pal/gballoc_hl_redirect.h" -#include "c_pal/interlocked.h" -#include "c_pal/srw_lock_ll.h" #include "c_pal/thandle_ll.h" #include "umock_c/umock_c_prod.h" @@ -44,27 +39,6 @@ MU_DEFINE_ENUM(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_R MU_DEFINE_ENUM(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_RESULT_VALUES) -/*result codes for PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY*/ -#define PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT_VALUES \ - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_FREED, \ - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS, \ - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_ALLOCATED, \ - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY - -MU_DEFINE_ENUM(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT_VALUES) - -/*options for PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS*/ -typedef struct PAGED_SPARSE_ARRAY_OPTIONS_TAG -{ - bool thread_safe; /*ALLOCATE, RELEASE, GET and FREE_PAGE_IF_EMPTY can be called concurrently*/ - bool auto_free_empty_pages; /*RELEASE frees a page when the last allocated element in it is released*/ - bool zero_new_pages; /*all the items of a newly allocated page are set to zero*/ -} PAGED_SPARSE_ARRAY_OPTIONS; - -/*format and values to log a PAGED_SPARSE_ARRAY_OPTIONS, e.g. LogInfo("options=%" PRI_PAGED_SPARSE_ARRAY_OPTIONS "", PAGED_SPARSE_ARRAY_OPTIONS_VALUES(options))*/ -#define PRI_PAGED_SPARSE_ARRAY_OPTIONS "s{ thread_safe=%" PRI_BOOL ", auto_free_empty_pages=%" PRI_BOOL ", zero_new_pages=%" PRI_BOOL " }" -#define PAGED_SPARSE_ARRAY_OPTIONS_VALUES(options) "", MU_BOOL_VALUE((options).thread_safe), MU_BOOL_VALUE((options).auto_free_empty_pages), MU_BOOL_VALUE((options).zero_new_pages) - /*PAGED_SPARSE_ARRAY is backed by a THANDLE build on the structure below*/ #define PAGED_SPARSE_ARRAY_STRUCT_TYPE_NAME_TAG(T) MU_C2(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T), _TAG) @@ -83,7 +57,6 @@ typedef struct PAGED_SPARSE_ARRAY_OPTIONS_TAG /*define the item dispose function type*/ \ PAGED_SPARSE_ARRAY_DEFINE_ITEM_DISPOSE_FUNC_TYPE(T); \ /*define the page structure first - bitmap is embedded after items[] in the same allocation*/ \ -/*when thread_safe is true, allocated_count and the bitmap bytes are only accessed with interlocked operations*/ \ typedef struct PAGED_SPARSE_ARRAY_PAGE_STRUCT_TYPE_NAME_TAG(T) \ { \ uint64_t allocated_count; \ @@ -97,8 +70,6 @@ struct PAGED_SPARSE_ARRAY_STRUCT_TYPE_NAME_TAG(T) uint64_t page_size; \ uint64_t page_count; \ PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T) item_dispose_func; \ - PAGED_SPARSE_ARRAY_OPTIONS options; \ - SRW_LOCK_LL lock; /*only initialized and used when options.thread_safe is true*/ \ PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)* pages[]; \ }; \ @@ -117,13 +88,6 @@ struct PAGED_SPARSE_ARRAY_STRUCT_TYPE_NAME_TAG(T) #define PAGED_SPARSE_ARRAY_LL_CREATE_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_CREATE_, C) #define PAGED_SPARSE_ARRAY_LL_CREATE(C) PAGED_SPARSE_ARRAY_LL_CREATE_NAME(C) -/*introduces a new name for a function that returns a PAGED_SPARSE_ARRAY_LL(T) created with options*/ -#define PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS_, C) -#define PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS(C) PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS_NAME(C) - -/*introduces a name for the internal function used by both CREATE and CREATE_WITH_OPTIONS*/ -#define PAGED_SPARSE_ARRAY_LL_CREATE_INTERNAL_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_CREATE_INTERNAL_, C) - /*introduces a name for the dispose function that is called when PAGED_SPARSE_ARRAY ref count goes to 0*/ #define PAGED_SPARSE_ARRAY_LL_DISPOSE_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_DISPOSE_, C) @@ -131,38 +95,18 @@ struct PAGED_SPARSE_ARRAY_STRUCT_TYPE_NAME_TAG(T) #define PAGED_SPARSE_ARRAY_LL_ALLOCATE_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_ALLOCATE_, C) #define PAGED_SPARSE_ARRAY_LL_ALLOCATE(C) PAGED_SPARSE_ARRAY_LL_ALLOCATE_NAME(C) -/*introduces a name for the internal allocate function used when thread_safe is true*/ -#define PAGED_SPARSE_ARRAY_LL_ALLOCATE_THREAD_SAFE_INTERNAL_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_ALLOCATE_THREAD_SAFE_INTERNAL_, C) - /*introduces a name for the release function*/ #define PAGED_SPARSE_ARRAY_LL_RELEASE_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_RELEASE_, C) #define PAGED_SPARSE_ARRAY_LL_RELEASE(C) PAGED_SPARSE_ARRAY_LL_RELEASE_NAME(C) -/*introduces a name for the internal release function used when thread_safe is true*/ -#define PAGED_SPARSE_ARRAY_LL_RELEASE_THREAD_SAFE_INTERNAL_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_RELEASE_THREAD_SAFE_INTERNAL_, C) - /*introduces a name for the get function*/ #define PAGED_SPARSE_ARRAY_LL_GET_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_GET_, C) #define PAGED_SPARSE_ARRAY_LL_GET(C) PAGED_SPARSE_ARRAY_LL_GET_NAME(C) -/*introduces a name for the internal get function used when thread_safe is true*/ -#define PAGED_SPARSE_ARRAY_LL_GET_THREAD_SAFE_INTERNAL_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_GET_THREAD_SAFE_INTERNAL_, C) - -/*introduces a name for the free page if empty function*/ -#define PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_, C) -#define PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY(C) PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_NAME(C) - -/*introduces a name for the internal function that frees a page if it is empty, used by RELEASE and FREE_PAGE_IF_EMPTY*/ -#define PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_INTERNAL_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_INTERNAL_, C) - /*introduces a function declaration for paged_sparse_array_create*/ #define PAGED_SPARSE_ARRAY_LL_CREATE_DECLARE(C, T) \ MOCKABLE_FUNCTION(, PAGED_SPARSE_ARRAY_LL(T), PAGED_SPARSE_ARRAY_LL_CREATE(C), uint64_t, max_size, uint64_t, page_size, PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T), item_dispose_func); -/*introduces a function declaration for paged_sparse_array_create_with_options*/ -#define PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS_DECLARE(C, T) \ - MOCKABLE_FUNCTION(, PAGED_SPARSE_ARRAY_LL(T), PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS(C), uint64_t, max_size, uint64_t, page_size, PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T), item_dispose_func, const PAGED_SPARSE_ARRAY_OPTIONS*, options); - /*introduces a function declaration for paged_sparse_array_allocate*/ #define PAGED_SPARSE_ARRAY_LL_ALLOCATE_DECLARE(C, T) \ MOCKABLE_FUNCTION(, PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_LL_ALLOCATE(C), PAGED_SPARSE_ARRAY_LL(T), paged_sparse_array, uint64_t, index, T**, allocated_ptr); @@ -175,10 +119,6 @@ struct PAGED_SPARSE_ARRAY_STRUCT_TYPE_NAME_TAG(T) #define PAGED_SPARSE_ARRAY_LL_GET_DECLARE(C, T) \ MOCKABLE_FUNCTION(, PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_LL_GET(C), PAGED_SPARSE_ARRAY_LL(T), paged_sparse_array, uint64_t, index, T**, item); -/*introduces a function declaration for paged_sparse_array_free_page_if_empty*/ -#define PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_DECLARE(C, T) \ - MOCKABLE_FUNCTION(, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY(C), PAGED_SPARSE_ARRAY_LL(T), paged_sparse_array, uint64_t, index); - /*helper to compute bitmap size in bytes (1 bit per element, rounded up to nearest byte)*/ #define PAGED_SPARSE_ARRAY_BITMAP_SIZE(page_size) (((page_size) + 7) / 8) @@ -197,25 +137,6 @@ struct PAGED_SPARSE_ARRAY_STRUCT_TYPE_NAME_TAG(T) #define PAGED_SPARSE_ARRAY_CLEAR_ALLOCATED(bitmap, index_in_page) \ ((bitmap)[(index_in_page) / 8] &= ~(1 << ((index_in_page) % 8))) -/*the helpers below are used when thread_safe is true, where allocated_count and the bitmap bytes are only accessed with interlocked operations*/ -#define PAGED_SPARSE_ARRAY_ALLOCATED_COUNT_ATOMIC(page) ((volatile_atomic int64_t*)&(page)->allocated_count) - -#define PAGED_SPARSE_ARRAY_BITMAP_BYTE_ATOMIC(bitmap, index_in_page) ((volatile_atomic int8_t*)&(bitmap)[(index_in_page) / 8]) - -#define PAGED_SPARSE_ARRAY_BITMAP_MASK(index_in_page) ((int8_t)(1 << ((index_in_page) % 8))) - -/*helper to atomically check if an element is allocated in the bitmap*/ -#define PAGED_SPARSE_ARRAY_IS_ALLOCATED_ATOMIC(bitmap, index_in_page) \ - ((interlocked_or_8(PAGED_SPARSE_ARRAY_BITMAP_BYTE_ATOMIC(bitmap, index_in_page), 0) & PAGED_SPARSE_ARRAY_BITMAP_MASK(index_in_page)) != 0) - -/*helper to atomically set an element as allocated in the bitmap, evaluates to true if the element was already allocated*/ -#define PAGED_SPARSE_ARRAY_TEST_AND_SET_ALLOCATED_ATOMIC(bitmap, index_in_page) \ - ((interlocked_or_8(PAGED_SPARSE_ARRAY_BITMAP_BYTE_ATOMIC(bitmap, index_in_page), PAGED_SPARSE_ARRAY_BITMAP_MASK(index_in_page)) & PAGED_SPARSE_ARRAY_BITMAP_MASK(index_in_page)) != 0) - -/*helper to atomically clear an element as not allocated in the bitmap, evaluates to true if the element was allocated*/ -#define PAGED_SPARSE_ARRAY_TEST_AND_CLEAR_ALLOCATED_ATOMIC(bitmap, index_in_page) \ - ((interlocked_and_8(PAGED_SPARSE_ARRAY_BITMAP_BYTE_ATOMIC(bitmap, index_in_page), (int8_t)~PAGED_SPARSE_ARRAY_BITMAP_MASK(index_in_page)) & PAGED_SPARSE_ARRAY_BITMAP_MASK(index_in_page)) != 0) - /*introduces a function definition for freeing the allocated resources for a PAGED_SPARSE_ARRAY*/ #define PAGED_SPARSE_ARRAY_LL_DISPOSE_DEFINE(C, T) \ static void PAGED_SPARSE_ARRAY_LL_DISPOSE_NAME(C)(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* paged_sparse_array) \ @@ -249,20 +170,11 @@ static void PAGED_SPARSE_ARRAY_LL_DISPOSE_NAME(C)(PAGED_SPARSE_ARRAY_TYPEDEF_NAM free((void*)paged_sparse_array->pages[i]); \ } \ } \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_052: [ If thread_safe is true, PAGED_SPARSE_ARRAY_DISPOSE(T) shall call srw_lock_ll_deinit. ]*/ \ - if (!paged_sparse_array->options.thread_safe) \ - { \ - /* do nothing, the lock was not initialized */ \ - } \ - else \ - { \ - srw_lock_ll_deinit(&paged_sparse_array->lock); \ - } \ } \ } #define PAGED_SPARSE_ARRAY_LL_CREATE_DEFINE(C, T) \ -static PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* PAGED_SPARSE_ARRAY_LL_CREATE_INTERNAL_NAME(C)(uint64_t max_size, uint64_t page_size, PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T) item_dispose_func, const PAGED_SPARSE_ARRAY_OPTIONS* options) \ +PAGED_SPARSE_ARRAY_LL(T) PAGED_SPARSE_ARRAY_LL_CREATE(C)(uint64_t max_size, uint64_t page_size, PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T) item_dispose_func) \ { \ PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* result; \ if ( \ @@ -274,8 +186,7 @@ static PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* PAGED_SPARSE_ARRAY_LL_CREATE_INTERNAL (max_size > UINT64_MAX - (page_size - 1)) \ ) \ { \ - LogError("Invalid arguments: PAGED_SPARSE_ARRAY(" MU_TOSTRING(T) "), uint64_t max_size=%" PRIu64 ", uint64_t page_size=%" PRIu64 ", PAGED_SPARSE_ARRAY_ITEM_DISPOSE_FUNC(" MU_TOSTRING(T) ") item_dispose_func=%p, const PAGED_SPARSE_ARRAY_OPTIONS* options=%p (%" PRI_PAGED_SPARSE_ARRAY_OPTIONS ")", \ - max_size, page_size, item_dispose_func, options, PAGED_SPARSE_ARRAY_OPTIONS_VALUES(*options)); \ + LogError("Invalid arguments: uint64_t max_size=%" PRIu64 ", uint64_t page_size=%" PRIu64 "", max_size, page_size); \ } \ else \ { \ @@ -286,8 +197,7 @@ static PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* PAGED_SPARSE_ARRAY_LL_CREATE_INTERNAL if (result == NULL) \ { \ /* Codes_SRS_PAGED_SPARSE_ARRAY_88_007: [ If there are any errors, PAGED_SPARSE_ARRAY_CREATE(T) shall fail and return NULL. ]*/ \ - LogError("failure in THANDLE_MALLOC_FLEX(PAGED_SPARSE_ARRAY(" MU_TOSTRING(T) ")) page_count=%" PRIu64 ", sizeof(PAGED_SPARSE_ARRAY_PAGE*)=%zu, uint64_t max_size=%" PRIu64 ", uint64_t page_size=%" PRIu64 ", PAGED_SPARSE_ARRAY_ITEM_DISPOSE_FUNC(" MU_TOSTRING(T) ") item_dispose_func=%p, const PAGED_SPARSE_ARRAY_OPTIONS* options=%p (%" PRI_PAGED_SPARSE_ARRAY_OPTIONS ")", \ - page_count, sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)*), max_size, page_size, item_dispose_func, options, PAGED_SPARSE_ARRAY_OPTIONS_VALUES(*options)); \ + LogError("failure in THANDLE_MALLOC_FLEX page_count=%" PRIu64 ", sizeof(PAGED_SPARSE_ARRAY_PAGE*)=%zu", page_count, sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)*)); \ } \ else \ { \ @@ -296,57 +206,25 @@ static PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* PAGED_SPARSE_ARRAY_LL_CREATE_INTERNAL result->page_size = page_size; \ result->page_count = page_count; \ result->item_dispose_func = item_dispose_func; \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_049: [ PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall store a copy of *options in the structure. ]*/ \ - result->options = *options; \ /* Codes_SRS_PAGED_SPARSE_ARRAY_88_005: [ PAGED_SPARSE_ARRAY_CREATE(T) shall set all page pointers to NULL. ]*/ \ for (uint64_t i = 0; i < page_count; i++) \ { \ result->pages[i] = NULL; \ } \ - int lock_init_result; \ - if (!options->thread_safe) \ - { \ - /* do nothing, the lock is not used */ \ - lock_init_result = 0; \ - } \ - else \ - { \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_050: [ If options->thread_safe is true, PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall call srw_lock_ll_init to initialize the lock. ]*/ \ - lock_init_result = srw_lock_ll_init(&result->lock); \ - } \ - if (lock_init_result != 0) \ - { \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_051: [ If srw_lock_ll_init fails, PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall free the paged sparse array and return NULL. ]*/ \ - LogError("failure in srw_lock_ll_init(&result->lock)=%d for PAGED_SPARSE_ARRAY(" MU_TOSTRING(T) "), uint64_t max_size=%" PRIu64 ", uint64_t page_size=%" PRIu64 ", uint64_t page_count=%" PRIu64 ", const PAGED_SPARSE_ARRAY_OPTIONS* options=%p (%" PRI_PAGED_SPARSE_ARRAY_OPTIONS ")", \ - lock_init_result, max_size, page_size, page_count, options, PAGED_SPARSE_ARRAY_OPTIONS_VALUES(*options)); \ - THANDLE_FREE(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(C))(result); \ - } \ - else \ - { \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_008: [ PAGED_SPARSE_ARRAY_CREATE(T) shall succeed and return a non-NULL value. ]*/ \ - goto all_ok; \ - } \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_008: [ PAGED_SPARSE_ARRAY_CREATE(T) shall succeed and return a non-NULL value. ]*/ \ + goto all_ok; \ } \ } \ result = NULL; \ all_ok: \ return result; \ -} \ -PAGED_SPARSE_ARRAY_LL(T) PAGED_SPARSE_ARRAY_LL_CREATE(C)(uint64_t max_size, uint64_t page_size, PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T) item_dispose_func) \ -{ \ - PAGED_SPARSE_ARRAY_OPTIONS default_options; \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_046: [ PAGED_SPARSE_ARRAY_CREATE(T) shall create the paged sparse array with thread_safe set to false, auto_free_empty_pages set to true and zero_new_pages set to false. ]*/ \ - default_options.thread_safe = false; \ - default_options.auto_free_empty_pages = true; \ - default_options.zero_new_pages = false; \ - return PAGED_SPARSE_ARRAY_LL_CREATE_INTERNAL_NAME(C)(max_size, page_size, item_dispose_func, &default_options); \ } /*internal helper to allocate a page*/ #define PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_NAME(C) MU_C2(PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_, C) #define PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_DEFINE(C, T) \ -static PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)* PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_NAME(C)(uint64_t page_size, bool zero_new_pages) \ +static PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)* PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_NAME(C)(uint64_t page_size) \ { \ PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)* page; \ /* Allocate page struct + items array + bitmap in a single allocation */ \ @@ -355,8 +233,7 @@ static PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)* PAGED_SPARSE_ARRAY_LL_ALLOCATE_P page = malloc_flex(sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)), items_and_bitmap_size, 1); \ if (page == NULL) \ { \ - LogError("failure in malloc_flex(sizeof(PAGED_SPARSE_ARRAY_PAGE(" MU_TOSTRING(T) "))=%zu, items_and_bitmap_size=%zu, 1) for page, page_size=%" PRIu64 ", sizeof(" MU_TOSTRING(T) ")=%zu, bitmap_size=%zu, zero_new_pages=%" PRI_BOOL "", \ - sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)), items_and_bitmap_size, page_size, sizeof(T), bitmap_size, MU_BOOL_VALUE(zero_new_pages)); \ + LogError("failure in malloc_flex for page, page_size=%" PRIu64 ", sizeof(" MU_TOSTRING(T) ")=%zu, bitmap_size=%zu", page_size, sizeof(T), bitmap_size); \ } \ else \ { \ @@ -364,135 +241,11 @@ static PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)* PAGED_SPARSE_ARRAY_LL_ALLOCATE_P uint8_t* bitmap = PAGED_SPARSE_ARRAY_GET_BITMAP(page, page_size); \ (void)memset(bitmap, 0, bitmap_size); \ page->allocated_count = 0; \ - if (!zero_new_pages) \ - { \ - /* do nothing, items are left uninitialized */ \ - } \ - else \ - { \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_053: [ If zero_new_pages is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall set all the items of a newly allocated page to zero. ]*/ \ - (void)memset((void*)page->items, 0, (size_t)page_size * sizeof(T)); \ - } \ } \ return page; \ -} \ -MU_SUPPRESS_WARNING(4505) /*warning C4505: 'function': unreferenced function with internal linkage has been removed*/ \ -static PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_INTERNAL_NAME(C)(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* array, uint64_t page_index) \ -{ \ - /* when thread_safe is true, the caller holds the lock in exclusive mode */ \ - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT result; \ - PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)* page = array->pages[page_index]; \ - if (page == NULL) \ - { \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_070: [ If the page is not allocated, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_ALLOCATED. ]*/ \ - result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_ALLOCATED; \ - } \ - else \ - { \ - int64_t allocated_count; \ - if (!array->options.thread_safe) \ - { \ - allocated_count = (int64_t)page->allocated_count; \ - } \ - else \ - { \ - allocated_count = interlocked_add_64(PAGED_SPARSE_ARRAY_ALLOCATED_COUNT_ATOMIC(page), 0); \ - } \ - if (allocated_count != 0) \ - { \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_071: [ If the allocated count of the page is not 0, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY. ]*/ \ - result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY; \ - } \ - else \ - { \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_072: [ Otherwise, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall free the page, set the page pointer to NULL and return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_FREED. ]*/ \ - free((void*)page); \ - array->pages[page_index] = NULL; \ - result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_FREED; \ - } \ - } \ - return result; \ -} \ -MU_UNSUPPRESS_WARNING(4505) \ +} #define PAGED_SPARSE_ARRAY_LL_ALLOCATE_DEFINE(C, T) \ -static PAGED_SPARSE_ARRAY_ALLOCATE_RESULT PAGED_SPARSE_ARRAY_LL_ALLOCATE_THREAD_SAFE_INTERNAL_NAME(C)(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* array, uint64_t index, T** allocated_ptr) \ -{ \ - PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result; \ - bool is_lock_exclusive; \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_013: [ PAGED_SPARSE_ARRAY_ALLOCATE(T) shall compute the page index as index / page_size. ]*/ \ - uint64_t page_index = index / array->page_size; \ - uint64_t index_in_page = index % array->page_size; \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_054: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall acquire the lock in shared mode. ]*/ \ - srw_lock_ll_acquire_shared(&array->lock); \ - PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)* page = array->pages[page_index]; \ - if (page != NULL) \ - { \ - is_lock_exclusive = false; \ - } \ - else \ - { \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_055: [ If thread_safe is true and the page is not allocated, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall release the shared lock and acquire the lock in exclusive mode. ]*/ \ - srw_lock_ll_release_shared(&array->lock); \ - srw_lock_ll_acquire_exclusive(&array->lock); \ - is_lock_exclusive = true; \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_056: [ After acquiring the lock in exclusive mode, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall check again whether the page is allocated, since another thread may have allocated it in the meantime. ]*/ \ - page = array->pages[page_index]; \ - if (page != NULL) \ - { \ - /* do nothing, another thread allocated the page while the lock was not held */ \ - } \ - else \ - { \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_014: [ If the page is not allocated, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall call malloc_flex to allocate memory for the page structure containing page_size elements of type T plus a uint8_t allocation bitmap of (page_size + 7) / 8 bytes, and initialize all bitmap bits to 0 (not allocated). ]*/ \ - page = PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_NAME(C)(array->page_size, array->options.zero_new_pages); \ - if (page == NULL) \ - { \ - LogError("failure allocating page for PAGED_SPARSE_ARRAY(" MU_TOSTRING(T) ") array=%p, uint64_t index=%" PRIu64 ", page_index=%" PRIu64 ", page_size=%" PRIu64 ", page_count=%" PRIu64 ", options=%" PRI_PAGED_SPARSE_ARRAY_OPTIONS "", \ - array, index, page_index, array->page_size, array->page_count, PAGED_SPARSE_ARRAY_OPTIONS_VALUES(array->options)); \ - } \ - else \ - { \ - array->pages[page_index] = page; \ - } \ - } \ - } \ - \ - if (page == NULL) \ - { \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_018: [ If malloc_flex fails, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall fail and return PAGED_SPARSE_ARRAY_ALLOCATE_ERROR. ]*/ \ - result = PAGED_SPARSE_ARRAY_ALLOCATE_ERROR; \ - } \ - else \ - { \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_057: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall mark the element at index as allocated and increment the allocated count of the page using interlocked operations. ]*/ \ - if (PAGED_SPARSE_ARRAY_TEST_AND_SET_ALLOCATED_ATOMIC(PAGED_SPARSE_ARRAY_GET_BITMAP(page, array->page_size), index_in_page)) \ - { \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_015: [ If the element at index is already allocated, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall fail and return PAGED_SPARSE_ARRAY_ALLOCATE_ALREADY_ALLOCATED. ]*/ \ - result = PAGED_SPARSE_ARRAY_ALLOCATE_ALREADY_ALLOCATED; \ - LogError("Element is already allocated in PAGED_SPARSE_ARRAY(" MU_TOSTRING(T) ") array=%p, uint64_t index=%" PRIu64 ", page_index=%" PRIu64 ", index_in_page=%" PRIu64 ", lock held exclusive=%" PRI_BOOL ", result=%" PRI_MU_ENUM "", \ - array, index, page_index, index_in_page, MU_BOOL_VALUE(is_lock_exclusive), MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, result)); \ - } \ - else \ - { \ - (void)interlocked_increment_64(PAGED_SPARSE_ARRAY_ALLOCATED_COUNT_ATOMIC(page)); \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_017: [ PAGED_SPARSE_ARRAY_ALLOCATE(T) shall store a pointer to the element at index in allocated_ptr and return PAGED_SPARSE_ARRAY_ALLOCATE_OK. ]*/ \ - *allocated_ptr = &page->items[index_in_page]; \ - result = PAGED_SPARSE_ARRAY_ALLOCATE_OK; \ - } \ - } \ - \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_058: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall release the lock (shared or exclusive, whichever is held) before returning. ]*/ \ - if (is_lock_exclusive) \ - { \ - srw_lock_ll_release_exclusive(&array->lock); \ - } \ - else \ - { \ - srw_lock_ll_release_shared(&array->lock); \ - } \ - return result; \ -} \ PAGED_SPARSE_ARRAY_ALLOCATE_RESULT PAGED_SPARSE_ARRAY_LL_ALLOCATE(C)(PAGED_SPARSE_ARRAY_LL(T) paged_sparse_array, uint64_t index, T** allocated_ptr) \ { \ PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result; \ @@ -512,10 +265,6 @@ PAGED_SPARSE_ARRAY_ALLOCATE_RESULT PAGED_SPARSE_ARRAY_LL_ALLOCATE(C)(PAGED_SPARS LogError("Invalid arguments: uint64_t index=%" PRIu64 " out of bound, max_size=%" PRIu64 "", index, paged_sparse_array->max_size); \ result = PAGED_SPARSE_ARRAY_ALLOCATE_INVALID_ARGS; \ } \ - else if (paged_sparse_array->options.thread_safe) \ - { \ - result = PAGED_SPARSE_ARRAY_LL_ALLOCATE_THREAD_SAFE_INTERNAL_NAME(C)(THANDLE_GET_T(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(C))(paged_sparse_array), index, allocated_ptr); \ - } \ else \ { \ /* Codes_SRS_PAGED_SPARSE_ARRAY_88_013: [ PAGED_SPARSE_ARRAY_ALLOCATE(T) shall compute the page index as index / page_size. ]*/ \ @@ -525,7 +274,7 @@ PAGED_SPARSE_ARRAY_ALLOCATE_RESULT PAGED_SPARSE_ARRAY_LL_ALLOCATE(C)(PAGED_SPARS /* Codes_SRS_PAGED_SPARSE_ARRAY_88_014: [ If the page is not allocated, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall call malloc_flex to allocate memory for the page containing page_size elements and an allocation bitmap, and initialize all elements as not allocated. ]*/ \ if ( \ (array->pages[page_index] == NULL) && \ - ((array->pages[page_index] = PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_NAME(C)(paged_sparse_array->page_size, paged_sparse_array->options.zero_new_pages)) == NULL) \ + ((array->pages[page_index] = PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_NAME(C)(paged_sparse_array->page_size)) == NULL) \ ) \ { \ /* Codes_SRS_PAGED_SPARSE_ARRAY_88_018: [ If page allocation fails, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall fail and return PAGED_SPARSE_ARRAY_ALLOCATE_ERROR. ]*/ \ @@ -556,66 +305,6 @@ PAGED_SPARSE_ARRAY_ALLOCATE_RESULT PAGED_SPARSE_ARRAY_LL_ALLOCATE(C)(PAGED_SPARS } #define PAGED_SPARSE_ARRAY_LL_RELEASE_DEFINE(C, T) \ -static void PAGED_SPARSE_ARRAY_LL_RELEASE_THREAD_SAFE_INTERNAL_NAME(C)(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* array, uint64_t index) \ -{ \ - bool try_free_page; \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_021: [ PAGED_SPARSE_ARRAY_RELEASE(T) shall compute the page index as index / page_size. ]*/ \ - uint64_t page_index = index / array->page_size; \ - uint64_t index_in_page = index % array->page_size; \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_059: [ If thread_safe is true, PAGED_SPARSE_ARRAY_RELEASE(T) shall acquire the lock in shared mode. ]*/ \ - srw_lock_ll_acquire_shared(&array->lock); \ - PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)* page = array->pages[page_index]; \ - if (page == NULL) \ - { \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_022: [ If the page is not allocated, PAGED_SPARSE_ARRAY_RELEASE(T) shall return. ]*/ \ - try_free_page = false; \ - } \ - else \ - { \ - uint8_t* bitmap = PAGED_SPARSE_ARRAY_GET_BITMAP(page, array->page_size); \ - if (!PAGED_SPARSE_ARRAY_IS_ALLOCATED_ATOMIC(bitmap, index_in_page)) \ - { \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_023: [ If the element at index is not allocated, PAGED_SPARSE_ARRAY_RELEASE(T) shall return. ]*/ \ - try_free_page = false; \ - } \ - else \ - { \ - if (array->item_dispose_func == NULL) \ - { \ - /* do nothing */ \ - } \ - else \ - { \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_044: [ If item_dispose_func is not NULL, PAGED_SPARSE_ARRAY_RELEASE(T) shall call item_dispose_func for the element being released. ]*/ \ - array->item_dispose_func(&page->items[index_in_page]); \ - } \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_060: [ If thread_safe is true, PAGED_SPARSE_ARRAY_RELEASE(T) shall mark the element at index as not allocated using interlocked operations and, only if the element was still marked as allocated, decrement the allocated count of the page using interlocked operations. ]*/ \ - if (!PAGED_SPARSE_ARRAY_TEST_AND_CLEAR_ALLOCATED_ATOMIC(bitmap, index_in_page)) \ - { \ - /* the element was released concurrently by another caller, which already decremented the allocated count */ \ - try_free_page = false; \ - } \ - else \ - { \ - try_free_page = (interlocked_decrement_64(PAGED_SPARSE_ARRAY_ALLOCATED_COUNT_ATOMIC(page)) == 0) && array->options.auto_free_empty_pages; \ - } \ - } \ - } \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_061: [ If thread_safe is true, PAGED_SPARSE_ARRAY_RELEASE(T) shall release the shared lock. ]*/ \ - srw_lock_ll_release_shared(&array->lock); \ - \ - if (!try_free_page) \ - { \ - /* do nothing */ \ - } \ - else \ - { \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_062: [ If thread_safe is true, auto_free_empty_pages is true and the allocated count of the page became 0, PAGED_SPARSE_ARRAY_RELEASE(T) shall acquire the lock in exclusive mode, free the page only if it is still allocated and its allocated count is still 0, and release the exclusive lock. ]*/ \ - srw_lock_ll_acquire_exclusive(&array->lock); \ - (void)PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_INTERNAL_NAME(C)(array, page_index); \ - srw_lock_ll_release_exclusive(&array->lock); \ - } \ -} \ void PAGED_SPARSE_ARRAY_LL_RELEASE(C)(PAGED_SPARSE_ARRAY_LL(T) paged_sparse_array, uint64_t index) \ { \ /* Codes_SRS_PAGED_SPARSE_ARRAY_88_019: [ If paged_sparse_array is NULL, PAGED_SPARSE_ARRAY_RELEASE(T) shall return. ]*/ \ @@ -628,10 +317,6 @@ void PAGED_SPARSE_ARRAY_LL_RELEASE(C)(PAGED_SPARSE_ARRAY_LL(T) paged_sparse_arra { \ LogError("Invalid arguments: uint64_t index=%" PRIu64 " out of bound, max_size=%" PRIu64 "", index, paged_sparse_array->max_size); \ } \ - else if (paged_sparse_array->options.thread_safe) \ - { \ - PAGED_SPARSE_ARRAY_LL_RELEASE_THREAD_SAFE_INTERNAL_NAME(C)(THANDLE_GET_T(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(C))(paged_sparse_array), index); \ - } \ else \ { \ /* Codes_SRS_PAGED_SPARSE_ARRAY_88_021: [ PAGED_SPARSE_ARRAY_RELEASE(T) shall compute the page index as index / page_size. ]*/ \ @@ -662,59 +347,19 @@ void PAGED_SPARSE_ARRAY_LL_RELEASE(C)(PAGED_SPARSE_ARRAY_LL(T) paged_sparse_arra PAGED_SPARSE_ARRAY_CLEAR_ALLOCATED(PAGED_SPARSE_ARRAY_GET_BITMAP(array->pages[page_index], paged_sparse_array->page_size), index_in_page); \ array->pages[page_index]->allocated_count--; \ \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_025: [ If auto_free_empty_pages is true and all elements in the page are now not allocated, PAGED_SPARSE_ARRAY_RELEASE(T) shall free the page and set the page pointer to NULL. ]*/ \ - if ( \ - (array->options.auto_free_empty_pages) && \ - (array->pages[page_index]->allocated_count == 0) \ - ) \ + /* Codes_SRS_PAGED_SPARSE_ARRAY_88_025: [ If all elements in the page are now not allocated, PAGED_SPARSE_ARRAY_RELEASE(T) shall free the page and set the page pointer to NULL. ]*/ \ + if (array->pages[page_index]->allocated_count == 0) \ { \ /* bitmap is embedded in same allocation as page, so only one free needed */ \ free((void*)array->pages[page_index]); \ array->pages[page_index] = NULL; \ } \ - else \ - { \ - /* do nothing, the page is kept */ \ - } \ } \ } \ } \ } #define PAGED_SPARSE_ARRAY_LL_GET_DEFINE(C, T) \ -static PAGED_SPARSE_ARRAY_GET_RESULT PAGED_SPARSE_ARRAY_LL_GET_THREAD_SAFE_INTERNAL_NAME(C)(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* array, uint64_t index, T** item) \ -{ \ - PAGED_SPARSE_ARRAY_GET_RESULT result; \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_036: [ PAGED_SPARSE_ARRAY_GET(T) shall compute the page index as index / page_size. ]*/ \ - uint64_t page_index = index / array->page_size; \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_063: [ If thread_safe is true, PAGED_SPARSE_ARRAY_GET(T) shall acquire the lock in shared mode. ]*/ \ - srw_lock_ll_acquire_shared(&array->lock); \ - PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(T)* page = array->pages[page_index]; \ - if (page == NULL) \ - { \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_037: [ If the page is not allocated, PAGED_SPARSE_ARRAY_GET(T) shall return PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED. ]*/ \ - result = PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED; \ - } \ - else \ - { \ - uint64_t index_in_page = index % array->page_size; \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_064: [ If thread_safe is true, PAGED_SPARSE_ARRAY_GET(T) shall check whether the element at index is allocated using interlocked operations. ]*/ \ - if (!PAGED_SPARSE_ARRAY_IS_ALLOCATED_ATOMIC(PAGED_SPARSE_ARRAY_GET_BITMAP(page, array->page_size), index_in_page)) \ - { \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_038: [ If the element at index is not allocated, PAGED_SPARSE_ARRAY_GET(T) shall return PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED. ]*/ \ - result = PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED; \ - } \ - else \ - { \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_039: [ PAGED_SPARSE_ARRAY_GET(T) shall store in item a pointer to the element at index and return PAGED_SPARSE_ARRAY_GET_OK. ]*/ \ - *item = &page->items[index_in_page]; \ - result = PAGED_SPARSE_ARRAY_GET_OK; \ - } \ - } \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_065: [ If thread_safe is true, PAGED_SPARSE_ARRAY_GET(T) shall release the shared lock before returning. ]*/ \ - srw_lock_ll_release_shared(&array->lock); \ - return result; \ -} \ PAGED_SPARSE_ARRAY_GET_RESULT PAGED_SPARSE_ARRAY_LL_GET(C)(PAGED_SPARSE_ARRAY_LL(T) paged_sparse_array, uint64_t index, T** item) \ { \ PAGED_SPARSE_ARRAY_GET_RESULT result; \ @@ -729,10 +374,6 @@ PAGED_SPARSE_ARRAY_GET_RESULT PAGED_SPARSE_ARRAY_LL_GET(C)(PAGED_SPARSE_ARRAY_LL LogError("Invalid arguments: PAGED_SPARSE_ARRAY(" MU_TOSTRING(T) ") paged_sparse_array=%p, item=%p, uint64_t index=%" PRIu64, paged_sparse_array, item, index); \ result = PAGED_SPARSE_ARRAY_GET_INVALID_ARGS; \ } \ - else if (paged_sparse_array->options.thread_safe) \ - { \ - result = PAGED_SPARSE_ARRAY_LL_GET_THREAD_SAFE_INTERNAL_NAME(C)(THANDLE_GET_T(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(C))(paged_sparse_array), index, item); \ - } \ else \ { \ /* Codes_SRS_PAGED_SPARSE_ARRAY_88_036: [ PAGED_SPARSE_ARRAY_GET(T) shall compute the page index as index / page_size. ]*/ \ @@ -761,65 +402,4 @@ PAGED_SPARSE_ARRAY_GET_RESULT PAGED_SPARSE_ARRAY_LL_GET(C)(PAGED_SPARSE_ARRAY_LL } \ return result; \ } - -#define PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS_DEFINE(C, T) \ -/*hint: have PAGED_SPARSE_ARRAY_LL_CREATE_DEFINE(C, T) before PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS_DEFINE(C, T)*/ \ -PAGED_SPARSE_ARRAY_LL(T) PAGED_SPARSE_ARRAY_LL_CREATE_WITH_OPTIONS(C)(uint64_t max_size, uint64_t page_size, PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T) item_dispose_func, const PAGED_SPARSE_ARRAY_OPTIONS* options) \ -{ \ - PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* result; \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_047: [ If options is NULL, PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall fail and return NULL. ]*/ \ - if (options == NULL) \ - { \ - LogError("Invalid arguments: PAGED_SPARSE_ARRAY(" MU_TOSTRING(T) "), uint64_t max_size=%" PRIu64 ", uint64_t page_size=%" PRIu64 ", PAGED_SPARSE_ARRAY_ITEM_DISPOSE_FUNC(" MU_TOSTRING(T) ") item_dispose_func=%p, const PAGED_SPARSE_ARRAY_OPTIONS* options=%p", \ - max_size, page_size, item_dispose_func, options); \ - result = NULL; \ - } \ - else \ - { \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_048: [ PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall validate max_size and page_size, and allocate and initialize the paged sparse array, in the same way as PAGED_SPARSE_ARRAY_CREATE(T). ]*/ \ - result = PAGED_SPARSE_ARRAY_LL_CREATE_INTERNAL_NAME(C)(max_size, page_size, item_dispose_func, options); \ - } \ - return result; \ -} - -#define PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_DEFINE(C, T) \ -/*hint: have PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_DEFINE(C, T) before PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_DEFINE(C, T)*/ \ -PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY(C)(PAGED_SPARSE_ARRAY_LL(T) paged_sparse_array, uint64_t index) \ -{ \ - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT result; \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_066: [ If paged_sparse_array is NULL, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall fail and return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS. ]*/ \ - if (paged_sparse_array == NULL) \ - { \ - result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS; \ - LogError("Invalid arguments: PAGED_SPARSE_ARRAY(" MU_TOSTRING(T) ") paged_sparse_array=%p, uint64_t index=%" PRIu64 ", result=%" PRI_MU_ENUM "", \ - paged_sparse_array, index, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, result)); \ - } \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_067: [ If index is greater than or equal to max_size, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall fail and return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS. ]*/ \ - else if (index >= paged_sparse_array->max_size) \ - { \ - result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS; \ - LogError("Invalid arguments: PAGED_SPARSE_ARRAY(" MU_TOSTRING(T) ") paged_sparse_array=%p, uint64_t index=%" PRIu64 " out of bound, max_size=%" PRIu64 ", page_size=%" PRIu64 ", result=%" PRI_MU_ENUM "", \ - paged_sparse_array, index, paged_sparse_array->max_size, paged_sparse_array->page_size, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, result)); \ - } \ - else \ - { \ - PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* array = THANDLE_GET_T(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(C))(paged_sparse_array); \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_069: [ PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall compute the page index as index / page_size. ]*/ \ - uint64_t page_index = index / array->page_size; \ - if (!array->options.thread_safe) \ - { \ - result = PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_INTERNAL_NAME(C)(array, page_index); \ - } \ - else \ - { \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_068: [ If thread_safe is true, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall acquire the lock in exclusive mode. ]*/ \ - srw_lock_ll_acquire_exclusive(&array->lock); \ - result = PAGED_SPARSE_ARRAY_LL_FREE_PAGE_IF_EMPTY_INTERNAL_NAME(C)(array, page_index); \ - /* Codes_SRS_PAGED_SPARSE_ARRAY_88_073: [ If thread_safe is true, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall release the exclusive lock before returning. ]*/ \ - srw_lock_ll_release_exclusive(&array->lock); \ - } \ - } \ - return result; \ -} - #endif /*PAGED_SPARSE_ARRAY_LL_H*/ diff --git a/inc/c_util/thread_safe_paged_sparse_array.h b/inc/c_util/thread_safe_paged_sparse_array.h new file mode 100644 index 00000000..eab998d2 --- /dev/null +++ b/inc/c_util/thread_safe_paged_sparse_array.h @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#ifndef THREAD_SAFE_PAGED_SPARSE_ARRAY_H +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_H + +#include "c_util/thread_safe_paged_sparse_array_ll.h" + +/*THREAD_SAFE_PAGED_SPARSE_ARRAY is-a THANDLE.*/ +/*given a type "T" THREAD_SAFE_PAGED_SPARSE_ARRAY(T) expands to the name of the type. */ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY(T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL(T) + +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE_DECLARE(T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_CREATE_DECLARE(T, T) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE_DEFINE(T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_CREATE_DEFINE(T, T) + +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE_DEFINE(T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_DISPOSE_DEFINE(T, T) + +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_INTERNAL_DEFINE(T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_INTERNAL_DEFINE(T, T) + +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE_DECLARE(T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ALLOCATE_DECLARE(T, T) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE_DEFINE(T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ALLOCATE_DEFINE(T, T) + +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE_DECLARE(T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_RELEASE_DECLARE(T, T) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE_DEFINE(T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_RELEASE_DEFINE(T, T) + +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_GET_DECLARE(T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_GET_DECLARE(T, T) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_GET_DEFINE(T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_GET_DEFINE(T, T) + +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_DECLARE(T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_FREE_EMPTY_PAGES_DECLARE(T, T) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_DEFINE(T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_FREE_EMPTY_PAGES_DEFINE(T, T) + +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(C) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_CREATE(C) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(C) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ALLOCATE(C) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(C) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_RELEASE(C) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(C) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_GET(C) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(C) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_FREE_EMPTY_PAGES(C) + +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_INITIALIZE(T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_INITIALIZE(T) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ASSIGN(T) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_MOVE(T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_MOVE(T) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_INITIALIZE_MOVE(T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_INITIALIZE_MOVE(T) + +/*macro to be used in headers*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DECLARE(T) \ + /*hint: have THREAD_SAFE_PAGED_SPARSE_ARRAY_DEFINE_STRUCT_TYPE(T) before THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DECLARE*/ \ + /*hint: have THANDLE_TYPE_DECLARE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)) before THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DECLARE*/ \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE_DECLARE(T) \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE_DECLARE(T) \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE_DECLARE(T) \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_GET_DECLARE(T) \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_DECLARE(T) \ + +/*macro to be used in .c files, empty_pages is AUTO_FREE_EMPTY_PAGES or KEEP_EMPTY_PAGES, new_pages is DO_NOT_ZERO_NEW_PAGES or ZERO_NEW_PAGES*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE_WITH_OPTIONS(T, empty_pages, new_pages) \ + /*hint: have THANDLE_TYPE_DEFINE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)) before THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE_WITH_OPTIONS*/ \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_DEFINE_WITH_OPTIONS(T, T, empty_pages, new_pages) \ + +/*macro to be used in .c files, uses AUTO_FREE_EMPTY_PAGES and DO_NOT_ZERO_NEW_PAGES*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE(T) \ + /*hint: have THANDLE_TYPE_DEFINE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)) before THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE*/ \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE_WITH_OPTIONS(T, AUTO_FREE_EMPTY_PAGES, DO_NOT_ZERO_NEW_PAGES) \ + +#endif /*THREAD_SAFE_PAGED_SPARSE_ARRAY_H*/ diff --git a/inc/c_util/thread_safe_paged_sparse_array_ll.h b/inc/c_util/thread_safe_paged_sparse_array_ll.h new file mode 100644 index 00000000..6b0157af --- /dev/null +++ b/inc/c_util/thread_safe_paged_sparse_array_ll.h @@ -0,0 +1,712 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#ifndef THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_H +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_H + +#ifdef __cplusplus +#include +#include +#include +#else // __cplusplus +#include +#include +#include +#endif // __cplusplus + +#include "macro_utils/macro_utils.h" + +#include "c_logging/logger.h" + +#include "c_pal/gballoc_hl.h" +#include "c_pal/gballoc_hl_redirect.h" +#include "c_pal/interlocked.h" +#include "c_pal/srw_lock_ll.h" +#include "c_pal/thandle_ll.h" + +#include "c_util/paged_sparse_array_ll.h" + +#include "umock_c/umock_c_prod.h" + +/* + * THREAD_SAFE_PAGED_SPARSE_ARRAY is a paged sparse array (see paged_sparse_array_ll.h) whose ALLOCATE, RELEASE, GET and + * FREE_EMPTY_PAGES can be called concurrently from multiple threads. + * ALLOCATE, RELEASE and GET do not acquire any lock when the page of the element is allocated. While they use a page they + * increment its use count, and a page is freed only when its use count is 0. + * Each page has its own lock, which is acquired only to allocate or to free that page. + * It uses the result codes PAGED_SPARSE_ARRAY_ALLOCATE_RESULT and PAGED_SPARSE_ARRAY_GET_RESULT and the bitmap helpers of paged_sparse_array_ll.h. + * 2 options are selected at compile time, each one by a name: + * - empty_pages: AUTO_FREE_EMPTY_PAGES (a page is freed when no element in it is allocated and no call uses it anymore) + * or KEEP_EMPTY_PAGES (empty pages are kept until FREE_EMPTY_PAGES is called or the array is disposed). + * - new_pages: DO_NOT_ZERO_NEW_PAGES (the items of a newly allocated page are left uninitialized) + * or ZERO_NEW_PAGES (all the items of a newly allocated page are set to zero). + * The _DEFINE_WITH_OPTIONS macros take the 2 options. The _DEFINE macros use AUTO_FREE_EMPTY_PAGES and DO_NOT_ZERO_NEW_PAGES. + * Any other value of an option, including the value of the other option, does not compile. + * new_pages changes only the allocation of a page and empty_pages changes only the end of the use of a page, + * the other functions only log the options. + * All the functions of a type shall be defined with the same options, THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_DEFINE_WITH_OPTIONS(C, T, ...) + * defines all of them at once. + */ + +/*result codes for THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT_VALUES \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_OK, \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_INVALID_ARGS + +MU_DEFINE_ENUM(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT_VALUES) + +/*values of the empty_pages option, the option is given to the _WITH_OPTIONS macros as AUTO_FREE_EMPTY_PAGES or KEEP_EMPTY_PAGES*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_EMPTY_PAGES_OPTION_AUTO_FREE_EMPTY_PAGES 1 +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_EMPTY_PAGES_OPTION_KEEP_EMPTY_PAGES 0 + +/*values of the new_pages option, the option is given to the _WITH_OPTIONS macros as DO_NOT_ZERO_NEW_PAGES or ZERO_NEW_PAGES*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_NEW_PAGES_OPTION_DO_NOT_ZERO_NEW_PAGES 0 +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_NEW_PAGES_OPTION_ZERO_NEW_PAGES 1 + +/*expand to 1 or 0, an option that is not one of the names above expands to an undeclared identifier and does not compile*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_IS_AUTO_FREE_EMPTY_PAGES(empty_pages) MU_C2(THREAD_SAFE_PAGED_SPARSE_ARRAY_EMPTY_PAGES_OPTION_, empty_pages) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_IS_ZERO_NEW_PAGES(new_pages) MU_C2(THREAD_SAFE_PAGED_SPARSE_ARRAY_NEW_PAGES_OPTION_, new_pages) + +/*string literal that describes a thread safe paged sparse array type and its options, used in logs*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_STRING(T, empty_pages, new_pages) \ + "THREAD_SAFE_PAGED_SPARSE_ARRAY(" MU_TOSTRING(T) ") (" MU_TOSTRING(empty_pages) ", " MU_TOSTRING(new_pages) ")" + +/*added to the use count of a page while the page is being freed, it is larger than any count of allocated elements and calls that use the page*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING (INT64_C(1) << 62) + +/*the array has one page slot for each page, the page slots exist as long as the array exists, even when their page is not allocated*/ +typedef struct THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_SLOT_TAG +{ + /*count of allocated elements in the page plus count of calls that use the page, plus THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING while the page is being freed*/ + volatile_atomic int64_t use_count; + /*page_size items of type T followed by the allocation bitmap, NULL when the page is not allocated*/ + void* volatile_atomic page; + /*acquired in exclusive mode only to allocate or to free the page*/ + SRW_LOCK_LL lock; +} THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_SLOT; + +/*the allocation bitmap of a page is stored right after the page_size items of the page*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_BITMAP(page, page_size) ((uint8_t*)&(page)[(page_size)]) + +/*THREAD_SAFE_PAGED_SPARSE_ARRAY is backed by a THANDLE build on the structure below*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_STRUCT_TYPE_NAME_TAG(T) MU_C2(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T), _TAG) + +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T) MU_C2(THREAD_SAFE_PAGED_SPARSE_ARRAY_STRUCT_, T) + +/*custom item dispose function type*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T) MU_C2(THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC_, T) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_DEFINE_ITEM_DISPOSE_FUNC_TYPE(T) typedef void (*THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T))(T* item) + +/*THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T) introduces the base type that holds the thread safe paged sparse array*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_DEFINE_STRUCT_TYPE(T) \ +/*define the item dispose function type*/ \ +THREAD_SAFE_PAGED_SPARSE_ARRAY_DEFINE_ITEM_DISPOSE_FUNC_TYPE(T); \ +/*forward define the typedef of the THREAD_SAFE_PAGED_SPARSE_ARRAY struct so that it can be used for a function pointer definition*/ \ +typedef struct THREAD_SAFE_PAGED_SPARSE_ARRAY_STRUCT_TYPE_NAME_TAG(T) THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T); \ +struct THREAD_SAFE_PAGED_SPARSE_ARRAY_STRUCT_TYPE_NAME_TAG(T) \ +{ \ + uint64_t max_size; \ + uint64_t page_size; \ + uint64_t page_count; \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T) item_dispose_func; \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_SLOT page_slots[]; \ +}; \ + +/*THREAD_SAFE_PAGED_SPARSE_ARRAY is-a THANDLE*/ +/*given a type "T" THREAD_SAFE_PAGED_SPARSE_ARRAY_LL(T) expands to the name of the type. */ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL(T) THANDLE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)) + +/*because THREAD_SAFE_PAGED_SPARSE_ARRAY is a THANDLE, all THANDLE's macro APIs are useable with THREAD_SAFE_PAGED_SPARSE_ARRAY.*/ +/*the below are just shortcuts of THANDLE's public ones*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_INITIALIZE(T) THANDLE_INITIALIZE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ASSIGN(T) THANDLE_ASSIGN(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_MOVE(T) THANDLE_MOVE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_INITIALIZE_MOVE(T) THANDLE_INITIALIZE_MOVE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)) + +/*introduces a new name for a function that returns a THREAD_SAFE_PAGED_SPARSE_ARRAY_LL(T)*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_CREATE_NAME(C) MU_C2(THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_CREATE_, C) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_CREATE(C) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_CREATE_NAME(C) + +/*introduces a name for the dispose function that is called when THREAD_SAFE_PAGED_SPARSE_ARRAY ref count goes to 0*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_DISPOSE_NAME(C) MU_C2(THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_DISPOSE_, C) + +/*introduces a name for the allocate function*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ALLOCATE_NAME(C) MU_C2(THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ALLOCATE_, C) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ALLOCATE(C) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ALLOCATE_NAME(C) + +/*introduces a name for the release function*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_RELEASE_NAME(C) MU_C2(THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_RELEASE_, C) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_RELEASE(C) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_RELEASE_NAME(C) + +/*introduces a name for the get function*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_GET_NAME(C) MU_C2(THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_GET_, C) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_GET(C) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_GET_NAME(C) + +/*introduces a name for the free empty pages function*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_FREE_EMPTY_PAGES_NAME(C) MU_C2(THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_FREE_EMPTY_PAGES_, C) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_FREE_EMPTY_PAGES(C) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_FREE_EMPTY_PAGES_NAME(C) + +/*introduces names for the internal functions that allocate a page, free a page if it is empty and not used, and begin and end the use of a page*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_NAME(C) MU_C2(THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_, C) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TRY_FREE_PAGE_INTERNAL_NAME(C) MU_C2(THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TRY_FREE_PAGE_INTERNAL_, C) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_BEGIN_PAGE_USE_INTERNAL_NAME(C) MU_C2(THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_BEGIN_PAGE_USE_INTERNAL_, C) +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_END_PAGE_USE_INTERNAL_NAME(C) MU_C2(THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_END_PAGE_USE_INTERNAL_, C) + +/*introduces a function declaration for thread_safe_paged_sparse_array_create*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_CREATE_DECLARE(C, T) \ + MOCKABLE_FUNCTION(, THREAD_SAFE_PAGED_SPARSE_ARRAY_LL(T), THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_CREATE(C), uint64_t, max_size, uint64_t, page_size, THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T), item_dispose_func); + +/*introduces a function declaration for thread_safe_paged_sparse_array_allocate*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ALLOCATE_DECLARE(C, T) \ + MOCKABLE_FUNCTION(, PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ALLOCATE(C), THREAD_SAFE_PAGED_SPARSE_ARRAY_LL(T), paged_sparse_array, uint64_t, index, T**, allocated_ptr); + +/*introduces a function declaration for thread_safe_paged_sparse_array_release*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_RELEASE_DECLARE(C, T) \ + MOCKABLE_FUNCTION(, void, THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_RELEASE(C), THREAD_SAFE_PAGED_SPARSE_ARRAY_LL(T), paged_sparse_array, uint64_t, index); + +/*introduces a function declaration for thread_safe_paged_sparse_array_get*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_GET_DECLARE(C, T) \ + MOCKABLE_FUNCTION(, PAGED_SPARSE_ARRAY_GET_RESULT, THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_GET(C), THREAD_SAFE_PAGED_SPARSE_ARRAY_LL(T), paged_sparse_array, uint64_t, index, T**, item); + +/*introduces a function declaration for thread_safe_paged_sparse_array_free_empty_pages*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_FREE_EMPTY_PAGES_DECLARE(C, T) \ + MOCKABLE_FUNCTION(, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT, THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_FREE_EMPTY_PAGES(C), THREAD_SAFE_PAGED_SPARSE_ARRAY_LL(T), paged_sparse_array, uint64_t, start_index, uint64_t, end_index); + +/*the bitmap bytes are read and changed with interlocked operations because elements of the same page are allocated and released concurrently*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_BITMAP_BYTE(bitmap, index_in_page) ((volatile_atomic int8_t*)&(bitmap)[(index_in_page) / 8]) + +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_BITMAP_MASK(index_in_page) ((int8_t)(1 << ((index_in_page) % 8))) + +/*helper to atomically check if an element is allocated in the bitmap*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_IS_ALLOCATED(bitmap, index_in_page) \ + ((interlocked_or_8(THREAD_SAFE_PAGED_SPARSE_ARRAY_BITMAP_BYTE(bitmap, index_in_page), 0) & THREAD_SAFE_PAGED_SPARSE_ARRAY_BITMAP_MASK(index_in_page)) != 0) + +/*helper to atomically set an element as allocated in the bitmap, evaluates to true if the element was already allocated*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_TEST_AND_SET_ALLOCATED(bitmap, index_in_page) \ + ((interlocked_or_8(THREAD_SAFE_PAGED_SPARSE_ARRAY_BITMAP_BYTE(bitmap, index_in_page), THREAD_SAFE_PAGED_SPARSE_ARRAY_BITMAP_MASK(index_in_page)) & THREAD_SAFE_PAGED_SPARSE_ARRAY_BITMAP_MASK(index_in_page)) != 0) + +/*helper to atomically clear an element as not allocated in the bitmap, evaluates to true if the element was allocated*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_TEST_AND_CLEAR_ALLOCATED(bitmap, index_in_page) \ + ((interlocked_and_8(THREAD_SAFE_PAGED_SPARSE_ARRAY_BITMAP_BYTE(bitmap, index_in_page), (int8_t)~THREAD_SAFE_PAGED_SPARSE_ARRAY_BITMAP_MASK(index_in_page)) & THREAD_SAFE_PAGED_SPARSE_ARRAY_BITMAP_MASK(index_in_page)) != 0) + +/*introduces a function definition for freeing the allocated resources for a THREAD_SAFE_PAGED_SPARSE_ARRAY*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_DISPOSE_DEFINE_WITH_OPTIONS(C, T, empty_pages, new_pages) \ +static void THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_DISPOSE_NAME(C)(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* paged_sparse_array) \ +{ \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_012: [ If paged_sparse_array is NULL, THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE(T) shall return. ]*/ \ + if (paged_sparse_array == NULL) \ + { \ + LogError("invalid arguments " THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_STRING(T, empty_pages, new_pages) " paged_sparse_array=%p", \ + paged_sparse_array); \ + } \ + else \ + { \ + /* the reference count reached 0, so no other thread uses the array, the page slots are read without interlocked operations and locks */ \ + for (uint64_t i = 0; i < paged_sparse_array->page_count; i++) \ + { \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_SLOT* page_slot = &paged_sparse_array->page_slots[i]; \ + T* page = (T*)page_slot->page; \ + if (page == NULL) \ + { \ + /* do nothing, the page is not allocated */ \ + } \ + else \ + { \ + if (paged_sparse_array->item_dispose_func == NULL) \ + { \ + /* do nothing */ \ + } \ + else \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_013: [ If item_dispose_func is not NULL, THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE(T) shall call item_dispose_func for each element that is still allocated. ]*/ \ + uint8_t* bitmap = THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_BITMAP(page, paged_sparse_array->page_size); \ + for (uint64_t j = 0; j < paged_sparse_array->page_size; j++) \ + { \ + if (PAGED_SPARSE_ARRAY_IS_ALLOCATED(bitmap, j)) \ + { \ + paged_sparse_array->item_dispose_func(&page[j]); \ + } \ + } \ + } \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_014: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE(T) shall free all pages that are non-NULL. ]*/ \ + free((void*)page); \ + } \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_015: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE(T) shall call srw_lock_ll_deinit for the lock of each page slot. ]*/ \ + srw_lock_ll_deinit(&page_slot->lock); \ + } \ + } \ +} + +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_DISPOSE_DEFINE(C, T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_DISPOSE_DEFINE_WITH_OPTIONS(C, T, AUTO_FREE_EMPTY_PAGES, DO_NOT_ZERO_NEW_PAGES) + +/*introduces the internal functions that allocate a page, free a page if it is empty and not used, and begin and end the use of a page*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_INTERNAL_DEFINE_WITH_OPTIONS(C, T, empty_pages, new_pages) \ +MU_SUPPRESS_WARNING(4505) /*warning C4505: 'function': unreferenced function with internal linkage has been removed*/ \ +static T* THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_NAME(C)(uint64_t page_size) \ +{ \ + size_t bitmap_size = PAGED_SPARSE_ARRAY_BITMAP_SIZE(page_size); \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_038: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall call malloc_flex to allocate memory for page_size elements followed by the allocation bitmap. ]*/ \ + T* page = malloc_flex(bitmap_size, (size_t)page_size, sizeof(T)); \ + if (page == NULL) \ + { \ + LogError("failure in malloc_flex(bitmap_size=%zu, page_size=%" PRIu64 ", sizeof(" MU_TOSTRING(T) ")=%zu) for a page of " THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_STRING(T, empty_pages, new_pages), \ + bitmap_size, page_size, sizeof(T)); \ + } \ + else \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_040: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall set all the bits of the allocation bitmap to 0. ]*/ \ + (void)memset(THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_BITMAP(page, page_size), 0, bitmap_size); \ + /* new_pages is a compile-time constant, the compiler keeps only one of the branches */ \ + if (THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_IS_ZERO_NEW_PAGES(new_pages)) \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_041: [ If new_pages is ZERO_NEW_PAGES, THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall set all the items of the page to zero. ]*/ \ + (void)memset((void*)page, 0, (size_t)page_size * sizeof(T)); \ + } \ + else \ + { \ + /* do nothing, items are left uninitialized */ \ + } \ + } \ + return page; \ +} \ +static void THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TRY_FREE_PAGE_INTERNAL_NAME(C)(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* array, uint64_t page_index) \ +{ \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_SLOT* page_slot = &array->page_slots[page_index]; \ + T* page_to_free; \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_021: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall acquire the lock of the page slot in exclusive mode. ]*/ \ + srw_lock_ll_acquire_exclusive(&page_slot->lock); \ + T* page = (T*)interlocked_compare_exchange_pointer(&page_slot->page, NULL, NULL); \ + if (page == NULL) \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_022: [ If the page is not allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall release the lock of the page slot and return. ]*/ \ + page_to_free = NULL; \ + } \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_023: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall change the use count of the page slot from 0 to THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING using interlocked operations. ]*/ \ + else if (interlocked_compare_exchange_64(&page_slot->use_count, THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING, 0) != 0) \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_024: [ If the use count was not 0, THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall release the lock of the page slot and return. ]*/ \ + page_to_free = NULL; \ + } \ + else \ + { \ + /* the calls that begin to use the page from now on see THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING and do not read the page pointer */ \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_025: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall set the page pointer of the page slot to NULL using interlocked operations. ]*/ \ + (void)interlocked_exchange_pointer(&page_slot->page, NULL); \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_026: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall subtract THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING from the use count of the page slot using interlocked operations. ]*/ \ + (void)interlocked_add_64(&page_slot->use_count, -THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING); \ + page_to_free = page; \ + } \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_027: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall release the lock of the page slot. ]*/ \ + srw_lock_ll_release_exclusive(&page_slot->lock); \ + if (page_to_free == NULL) \ + { \ + /* do nothing, the page is not allocated, has allocated elements or is used by another call */ \ + } \ + else \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_028: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall free the page. ]*/ \ + free((void*)page_to_free); \ + } \ +} \ +static T* THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_BEGIN_PAGE_USE_INTERNAL_NAME(C)(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* array, uint64_t page_index) \ +{ \ + T* result; \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_SLOT* page_slot = &array->page_slots[page_index]; \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_016: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T) shall increment the use count of the page slot using interlocked operations. ]*/ \ + if (interlocked_increment_64(&page_slot->use_count) >= THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING) \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_017: [ If the use count is greater than or equal to THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING, THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T) shall return NULL. ]*/ \ + result = NULL; \ + } \ + else \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_018: [ Otherwise, THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T) shall return the page pointer of the page slot, read using interlocked operations. ]*/ \ + result = (T*)interlocked_compare_exchange_pointer(&page_slot->page, NULL, NULL); \ + } \ + return result; \ +} \ +static void THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_END_PAGE_USE_INTERNAL_NAME(C)(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* array, uint64_t page_index, int64_t ended_use_count) \ +{ \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_SLOT* page_slot = &array->page_slots[page_index]; \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_019: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) shall subtract ended_use_count from the use count of the page slot using interlocked operations. ]*/ \ + int64_t use_count = interlocked_add_64(&page_slot->use_count, -ended_use_count); \ + /* empty_pages is a compile-time constant, the compiler keeps only one of the branches */ \ + if (THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_IS_AUTO_FREE_EMPTY_PAGES(empty_pages)) \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_020: [ If empty_pages is AUTO_FREE_EMPTY_PAGES, the use count became 0 and the page is allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T). ]*/ \ + if ((use_count == 0) && (interlocked_compare_exchange_pointer(&page_slot->page, NULL, NULL) != NULL)) \ + { \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TRY_FREE_PAGE_INTERNAL_NAME(C)(array, page_index); \ + } \ + else \ + { \ + /* do nothing, the page has allocated elements, is used by another call or is not allocated */ \ + } \ + } \ + else \ + { \ + /* do nothing, empty pages are kept until FREE_EMPTY_PAGES is called or the array is disposed */ \ + } \ +} \ +MU_UNSUPPRESS_WARNING(4505) + +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_INTERNAL_DEFINE(C, T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_INTERNAL_DEFINE_WITH_OPTIONS(C, T, AUTO_FREE_EMPTY_PAGES, DO_NOT_ZERO_NEW_PAGES) + +/*introduces a function definition for thread_safe_paged_sparse_array_create*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_CREATE_DEFINE_WITH_OPTIONS(C, T, empty_pages, new_pages) \ +THREAD_SAFE_PAGED_SPARSE_ARRAY_LL(T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_CREATE(C)(uint64_t max_size, uint64_t page_size, THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(T) item_dispose_func) \ +{ \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* result; \ + if ( \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_001: [ If max_size is zero, THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall fail and return NULL. ]*/ \ + (max_size == 0) || \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_002: [ If page_size is zero, THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall fail and return NULL. ]*/ \ + (page_size == 0) || \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_003: [ If max_size + page_size - 1 would overflow, THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall fail and return NULL. ]*/ \ + (max_size > UINT64_MAX - (page_size - 1)) \ + ) \ + { \ + LogError("Invalid arguments: " THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_STRING(T, empty_pages, new_pages) ", uint64_t max_size=%" PRIu64 ", uint64_t page_size=%" PRIu64 ", item_dispose_func=%p", \ + max_size, page_size, item_dispose_func); \ + result = NULL; \ + } \ + else \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_004: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall compute the number of pages as (max_size + page_size - 1) / page_size. ]*/ \ + uint64_t page_count = (max_size + page_size - 1) / page_size; \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_005: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall call THANDLE_MALLOC_FLEX to allocate memory for the paged sparse array with one page slot for each page. ]*/ \ + result = THANDLE_MALLOC_FLEX(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(C))(THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_DISPOSE_NAME(C), page_count, sizeof(THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_SLOT)); \ + if (result == NULL) \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_011: [ If there are any other errors, THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall fail and return NULL. ]*/ \ + LogError("failure in THANDLE_MALLOC_FLEX(" THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_STRING(T, empty_pages, new_pages) ") page_count=%" PRIu64 ", sizeof(THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_SLOT)=%zu, uint64_t max_size=%" PRIu64 ", uint64_t page_size=%" PRIu64 ", item_dispose_func=%p", \ + page_count, sizeof(THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_SLOT), max_size, page_size, item_dispose_func); \ + } \ + else \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_006: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall store max_size, page_size, and item_dispose_func in the structure. ]*/ \ + result->max_size = max_size; \ + result->page_size = page_size; \ + result->page_count = page_count; \ + result->item_dispose_func = item_dispose_func; \ + uint64_t initialized_lock_count; \ + for (initialized_lock_count = 0; initialized_lock_count < page_count; initialized_lock_count++) \ + { \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_SLOT* page_slot = &result->page_slots[initialized_lock_count]; \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_007: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall set the page pointer of each page slot to NULL and its use count to 0. ]*/ \ + (void)interlocked_exchange_pointer(&page_slot->page, NULL); \ + (void)interlocked_exchange_64(&page_slot->use_count, 0); \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_008: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall call srw_lock_ll_init to initialize the lock of each page slot. ]*/ \ + int lock_init_result = srw_lock_ll_init(&page_slot->lock); \ + if (lock_init_result != 0) \ + { \ + LogError("failure in srw_lock_ll_init(&page_slots[%" PRIu64 "].lock)=%d for " THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_STRING(T, empty_pages, new_pages) ", uint64_t max_size=%" PRIu64 ", uint64_t page_size=%" PRIu64 ", page_count=%" PRIu64 ", item_dispose_func=%p", \ + initialized_lock_count, lock_init_result, max_size, page_size, page_count, item_dispose_func); \ + break; \ + } \ + } \ + if (initialized_lock_count < page_count) \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_009: [ If srw_lock_ll_init fails, THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall call srw_lock_ll_deinit for the locks that were initialized, free the paged sparse array and return NULL. ]*/ \ + for (uint64_t i = 0; i < initialized_lock_count; i++) \ + { \ + srw_lock_ll_deinit(&result->page_slots[i].lock); \ + } \ + THANDLE_FREE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(C))(result); \ + result = NULL; \ + } \ + else \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_010: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall succeed and return a non-NULL value. ]*/ \ + } \ + } \ + } \ + return result; \ +} + +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_CREATE_DEFINE(C, T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_CREATE_DEFINE_WITH_OPTIONS(C, T, AUTO_FREE_EMPTY_PAGES, DO_NOT_ZERO_NEW_PAGES) + +/*introduces a function definition for thread_safe_paged_sparse_array_allocate*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ALLOCATE_DEFINE_WITH_OPTIONS(C, T, empty_pages, new_pages) \ +/*hint: have THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_INTERNAL_DEFINE_WITH_OPTIONS(C, T, ...) before THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ALLOCATE_DEFINE_WITH_OPTIONS(C, T, ...)*/ \ +PAGED_SPARSE_ARRAY_ALLOCATE_RESULT THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ALLOCATE(C)(THREAD_SAFE_PAGED_SPARSE_ARRAY_LL(T) paged_sparse_array, uint64_t index, T** allocated_ptr) \ +{ \ + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result; \ + if ( \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_029: [ If paged_sparse_array is NULL, THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall fail and return PAGED_SPARSE_ARRAY_ALLOCATE_INVALID_ARGS. ]*/ \ + (paged_sparse_array == NULL) || \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_030: [ If allocated_ptr is NULL, THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall fail and return PAGED_SPARSE_ARRAY_ALLOCATE_INVALID_ARGS. ]*/ \ + (allocated_ptr == NULL) \ + ) \ + { \ + result = PAGED_SPARSE_ARRAY_ALLOCATE_INVALID_ARGS; \ + LogError("Invalid arguments: " THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_STRING(T, empty_pages, new_pages) " paged_sparse_array=%p, uint64_t index=%" PRIu64 ", " MU_TOSTRING(T) "** allocated_ptr=%p, result=%" PRI_MU_ENUM "", \ + paged_sparse_array, index, allocated_ptr, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, result)); \ + } \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_031: [ If index is greater than or equal to max_size, THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall fail and return PAGED_SPARSE_ARRAY_ALLOCATE_INVALID_ARGS. ]*/ \ + else if (index >= paged_sparse_array->max_size) \ + { \ + result = PAGED_SPARSE_ARRAY_ALLOCATE_INVALID_ARGS; \ + LogError("Invalid arguments: " THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_STRING(T, empty_pages, new_pages) " paged_sparse_array=%p, uint64_t index=%" PRIu64 " out of bound, max_size=%" PRIu64 ", page_size=%" PRIu64 ", " MU_TOSTRING(T) "** allocated_ptr=%p, result=%" PRI_MU_ENUM "", \ + paged_sparse_array, index, paged_sparse_array->max_size, paged_sparse_array->page_size, allocated_ptr, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, result)); \ + } \ + else \ + { \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* array = THANDLE_GET_T(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(C))(paged_sparse_array); \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_032: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall compute the page index as index / page_size. ]*/ \ + uint64_t page_index = index / array->page_size; \ + uint64_t index_in_page = index % array->page_size; \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_SLOT* page_slot = &array->page_slots[page_index]; \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_033: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T). ]*/ \ + T* page = THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_BEGIN_PAGE_USE_INTERNAL_NAME(C)(array, page_index); \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_034: [ If the page is not allocated: ]*/ \ + if (page != NULL) \ + { \ + /* do nothing, the page is allocated and it is not freed while it is used */ \ + } \ + else \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_035: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 1. ]*/ \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_END_PAGE_USE_INTERNAL_NAME(C)(array, page_index, 1); \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_036: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall acquire the lock of the page slot in exclusive mode. ]*/ \ + srw_lock_ll_acquire_exclusive(&page_slot->lock); \ + page = (T*)interlocked_compare_exchange_pointer(&page_slot->page, NULL, NULL); \ + if (page != NULL) \ + { \ + /* do nothing, another thread allocated the page while the lock was not held */ \ + } \ + else \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_037: [ If the page is still not allocated: ]*/ \ + page = THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ALLOCATE_PAGE_INTERNAL_NAME(C)(array->page_size); \ + if (page == NULL) \ + { \ + /* do nothing, the error is logged below */ \ + } \ + else \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_042: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall store the page pointer in the page slot using interlocked operations. ]*/ \ + (void)interlocked_exchange_pointer(&page_slot->page, (void*)page); \ + } \ + } \ + if (page == NULL) \ + { \ + /* do nothing, the error is logged below */ \ + } \ + else \ + { \ + /* the page is not being freed, because freeing a page requires its lock */ \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_043: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall increment the use count of the page slot using interlocked operations. ]*/ \ + (void)interlocked_increment_64(&page_slot->use_count); \ + } \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_044: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall release the lock of the page slot. ]*/ \ + srw_lock_ll_release_exclusive(&page_slot->lock); \ + } \ + if (page == NULL) \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_039: [ If malloc_flex fails, THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall release the lock of the page slot and return PAGED_SPARSE_ARRAY_ALLOCATE_ERROR. ]*/ \ + result = PAGED_SPARSE_ARRAY_ALLOCATE_ERROR; \ + LogError("failure allocating page for " THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_STRING(T, empty_pages, new_pages) " paged_sparse_array=%p, uint64_t index=%" PRIu64 ", page_index=%" PRIu64 ", page_size=%" PRIu64 ", page_count=%" PRIu64 ", result=%" PRI_MU_ENUM "", \ + paged_sparse_array, index, page_index, array->page_size, array->page_count, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, result)); \ + } \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_045: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall mark the element at index as allocated using interlocked operations. ]*/ \ + else if (THREAD_SAFE_PAGED_SPARSE_ARRAY_TEST_AND_SET_ALLOCATED(THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_BITMAP(page, array->page_size), index_in_page)) \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_046: [ If the element at index was already marked as allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 1 and return PAGED_SPARSE_ARRAY_ALLOCATE_ALREADY_ALLOCATED. ]*/ \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_END_PAGE_USE_INTERNAL_NAME(C)(array, page_index, 1); \ + result = PAGED_SPARSE_ARRAY_ALLOCATE_ALREADY_ALLOCATED; \ + LogError("Element is already allocated in " THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_STRING(T, empty_pages, new_pages) " paged_sparse_array=%p, uint64_t index=%" PRIu64 ", page_index=%" PRIu64 ", index_in_page=%" PRIu64 ", result=%" PRI_MU_ENUM "", \ + paged_sparse_array, index, page_index, index_in_page, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, result)); \ + } \ + else \ + { \ + /* the use of the page is not ended, from now on it is the use of the allocated element */ \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_047: [ Otherwise, THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall store a pointer to the element at index in allocated_ptr and return PAGED_SPARSE_ARRAY_ALLOCATE_OK. ]*/ \ + *allocated_ptr = &page[index_in_page]; \ + result = PAGED_SPARSE_ARRAY_ALLOCATE_OK; \ + } \ + } \ + return result; \ +} + +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ALLOCATE_DEFINE(C, T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ALLOCATE_DEFINE_WITH_OPTIONS(C, T, AUTO_FREE_EMPTY_PAGES, DO_NOT_ZERO_NEW_PAGES) + +/*introduces a function definition for thread_safe_paged_sparse_array_release*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_RELEASE_DEFINE_WITH_OPTIONS(C, T, empty_pages, new_pages) \ +/*hint: have THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_INTERNAL_DEFINE_WITH_OPTIONS(C, T, ...) before THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_RELEASE_DEFINE_WITH_OPTIONS(C, T, ...)*/ \ +void THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_RELEASE(C)(THREAD_SAFE_PAGED_SPARSE_ARRAY_LL(T) paged_sparse_array, uint64_t index) \ +{ \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_048: [ If paged_sparse_array is NULL, THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall return. ]*/ \ + if (paged_sparse_array == NULL) \ + { \ + LogError("Invalid arguments: " THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_STRING(T, empty_pages, new_pages) " paged_sparse_array=%p, uint64_t index=%" PRIu64 "", \ + paged_sparse_array, index); \ + } \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_049: [ If index is greater than or equal to max_size, THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall return. ]*/ \ + else if (index >= paged_sparse_array->max_size) \ + { \ + LogError("Invalid arguments: " THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_STRING(T, empty_pages, new_pages) " paged_sparse_array=%p, uint64_t index=%" PRIu64 " out of bound, max_size=%" PRIu64 ", page_size=%" PRIu64 "", \ + paged_sparse_array, index, paged_sparse_array->max_size, paged_sparse_array->page_size); \ + } \ + else \ + { \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* array = THANDLE_GET_T(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(C))(paged_sparse_array); \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_050: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall compute the page index as index / page_size. ]*/ \ + uint64_t page_index = index / array->page_size; \ + uint64_t index_in_page = index % array->page_size; \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_051: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T). ]*/ \ + T* page = THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_BEGIN_PAGE_USE_INTERNAL_NAME(C)(array, page_index); \ + if (page == NULL) \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_052: [ If the page is not allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 1 and return. ]*/ \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_END_PAGE_USE_INTERNAL_NAME(C)(array, page_index, 1); \ + } \ + else \ + { \ + uint8_t* bitmap = THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_BITMAP(page, array->page_size); \ + if (!THREAD_SAFE_PAGED_SPARSE_ARRAY_IS_ALLOCATED(bitmap, index_in_page)) \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_053: [ If the element at index is not allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 1 and return. ]*/ \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_END_PAGE_USE_INTERNAL_NAME(C)(array, page_index, 1); \ + } \ + else \ + { \ + if (array->item_dispose_func == NULL) \ + { \ + /* do nothing */ \ + } \ + else \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_054: [ If item_dispose_func is not NULL, THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall call item_dispose_func for the element being released before marking the element as not allocated. ]*/ \ + array->item_dispose_func(&page[index_in_page]); \ + } \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_055: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall mark the element at index as not allocated using interlocked operations. ]*/ \ + if (THREAD_SAFE_PAGED_SPARSE_ARRAY_TEST_AND_CLEAR_ALLOCATED(bitmap, index_in_page)) \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_056: [ If the element was still marked as allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 2. ]*/ \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_END_PAGE_USE_INTERNAL_NAME(C)(array, page_index, 2); \ + } \ + else \ + { \ + /* the element was released concurrently by another caller (which callers shall not do), that caller ended the use of the element */ \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_057: [ Otherwise, THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 1. ]*/ \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_END_PAGE_USE_INTERNAL_NAME(C)(array, page_index, 1); \ + } \ + } \ + } \ + } \ +} + +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_RELEASE_DEFINE(C, T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_RELEASE_DEFINE_WITH_OPTIONS(C, T, AUTO_FREE_EMPTY_PAGES, DO_NOT_ZERO_NEW_PAGES) + +/*introduces a function definition for thread_safe_paged_sparse_array_get*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_GET_DEFINE_WITH_OPTIONS(C, T, empty_pages, new_pages) \ +/*hint: have THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_INTERNAL_DEFINE_WITH_OPTIONS(C, T, ...) before THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_GET_DEFINE_WITH_OPTIONS(C, T, ...)*/ \ +PAGED_SPARSE_ARRAY_GET_RESULT THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_GET(C)(THREAD_SAFE_PAGED_SPARSE_ARRAY_LL(T) paged_sparse_array, uint64_t index, T** item) \ +{ \ + PAGED_SPARSE_ARRAY_GET_RESULT result; \ + if ( \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_058: [ If paged_sparse_array is NULL, THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T) shall fail and return PAGED_SPARSE_ARRAY_GET_INVALID_ARGS. ]*/ \ + (paged_sparse_array == NULL) || \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_059: [ If item is NULL, THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T) shall fail and return PAGED_SPARSE_ARRAY_GET_INVALID_ARGS. ]*/ \ + (item == NULL) \ + ) \ + { \ + result = PAGED_SPARSE_ARRAY_GET_INVALID_ARGS; \ + LogError("Invalid arguments: " THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_STRING(T, empty_pages, new_pages) " paged_sparse_array=%p, uint64_t index=%" PRIu64 ", " MU_TOSTRING(T) "** item=%p, result=%" PRI_MU_ENUM "", \ + paged_sparse_array, index, item, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_GET_RESULT, result)); \ + } \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_060: [ If index is greater than or equal to max_size, THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T) shall fail and return PAGED_SPARSE_ARRAY_GET_INVALID_ARGS. ]*/ \ + else if (index >= paged_sparse_array->max_size) \ + { \ + result = PAGED_SPARSE_ARRAY_GET_INVALID_ARGS; \ + LogError("Invalid arguments: " THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_STRING(T, empty_pages, new_pages) " paged_sparse_array=%p, uint64_t index=%" PRIu64 " out of bound, max_size=%" PRIu64 ", page_size=%" PRIu64 ", " MU_TOSTRING(T) "** item=%p, result=%" PRI_MU_ENUM "", \ + paged_sparse_array, index, paged_sparse_array->max_size, paged_sparse_array->page_size, item, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_GET_RESULT, result)); \ + } \ + else \ + { \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* array = THANDLE_GET_T(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(C))(paged_sparse_array); \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_061: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T) shall compute the page index as index / page_size. ]*/ \ + uint64_t page_index = index / array->page_size; \ + uint64_t index_in_page = index % array->page_size; \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_062: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T). ]*/ \ + T* page = THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_BEGIN_PAGE_USE_INTERNAL_NAME(C)(array, page_index); \ + if (page == NULL) \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_063: [ If the page is not allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 1 and return PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED. ]*/ \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_END_PAGE_USE_INTERNAL_NAME(C)(array, page_index, 1); \ + result = PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED; \ + } \ + else if (!THREAD_SAFE_PAGED_SPARSE_ARRAY_IS_ALLOCATED(THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_BITMAP(page, array->page_size), index_in_page)) \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_064: [ If the element at index is not allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 1 and return PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED. ]*/ \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_END_PAGE_USE_INTERNAL_NAME(C)(array, page_index, 1); \ + result = PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED; \ + } \ + else \ + { \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_065: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T) shall store in item a pointer to the element at index. ]*/ \ + *item = &page[index_in_page]; \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_066: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 1 and return PAGED_SPARSE_ARRAY_GET_OK. ]*/ \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_END_PAGE_USE_INTERNAL_NAME(C)(array, page_index, 1); \ + result = PAGED_SPARSE_ARRAY_GET_OK; \ + } \ + } \ + return result; \ +} + +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_GET_DEFINE(C, T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_GET_DEFINE_WITH_OPTIONS(C, T, AUTO_FREE_EMPTY_PAGES, DO_NOT_ZERO_NEW_PAGES) + +/*introduces a function definition for thread_safe_paged_sparse_array_free_empty_pages*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_FREE_EMPTY_PAGES_DEFINE_WITH_OPTIONS(C, T, empty_pages, new_pages) \ +/*hint: have THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_INTERNAL_DEFINE_WITH_OPTIONS(C, T, ...) before THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_FREE_EMPTY_PAGES_DEFINE_WITH_OPTIONS(C, T, ...)*/ \ +THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_FREE_EMPTY_PAGES(C)(THREAD_SAFE_PAGED_SPARSE_ARRAY_LL(T) paged_sparse_array, uint64_t start_index, uint64_t end_index) \ +{ \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT result; \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_067: [ If paged_sparse_array is NULL, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T) shall fail and return THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_INVALID_ARGS. ]*/ \ + if (paged_sparse_array == NULL) \ + { \ + result = THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_INVALID_ARGS; \ + LogError("Invalid arguments: " THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_STRING(T, empty_pages, new_pages) " paged_sparse_array=%p, uint64_t start_index=%" PRIu64 ", uint64_t end_index=%" PRIu64 ", result=%" PRI_MU_ENUM "", \ + paged_sparse_array, start_index, end_index, MU_ENUM_VALUE(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT, result)); \ + } \ + else if ( \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_068: [ If start_index is greater than end_index, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T) shall fail and return THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_INVALID_ARGS. ]*/ \ + (start_index > end_index) || \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_069: [ If end_index is greater than or equal to max_size, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T) shall fail and return THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_INVALID_ARGS. ]*/ \ + (end_index >= paged_sparse_array->max_size) \ + ) \ + { \ + result = THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_INVALID_ARGS; \ + LogError("Invalid arguments: " THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_STRING(T, empty_pages, new_pages) " paged_sparse_array=%p, uint64_t start_index=%" PRIu64 ", uint64_t end_index=%" PRIu64 ", max_size=%" PRIu64 ", page_size=%" PRIu64 ", result=%" PRI_MU_ENUM "", \ + paged_sparse_array, start_index, end_index, paged_sparse_array->max_size, paged_sparse_array->page_size, MU_ENUM_VALUE(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT, result)); \ + } \ + else \ + { \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(T)* array = THANDLE_GET_T(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(C))(paged_sparse_array); \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_070: [ For each page from start_index / page_size to end_index / page_size, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T). ]*/ \ + for (uint64_t page_index = start_index / array->page_size; page_index <= end_index / array->page_size; page_index++) \ + { \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TRY_FREE_PAGE_INTERNAL_NAME(C)(array, page_index); \ + } \ + /* Codes_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_071: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T) shall succeed and return THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_OK. ]*/ \ + result = THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_OK; \ + } \ + return result; \ +} + +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_FREE_EMPTY_PAGES_DEFINE(C, T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_FREE_EMPTY_PAGES_DEFINE_WITH_OPTIONS(C, T, AUTO_FREE_EMPTY_PAGES, DO_NOT_ZERO_NEW_PAGES) + +/*introduces all the function definitions of a THREAD_SAFE_PAGED_SPARSE_ARRAY, all with the same options*/ +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_DEFINE_WITH_OPTIONS(C, T, empty_pages, new_pages) \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_DISPOSE_DEFINE_WITH_OPTIONS(C, T, empty_pages, new_pages) \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_INTERNAL_DEFINE_WITH_OPTIONS(C, T, empty_pages, new_pages) \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_CREATE_DEFINE_WITH_OPTIONS(C, T, empty_pages, new_pages) \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ALLOCATE_DEFINE_WITH_OPTIONS(C, T, empty_pages, new_pages) \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_RELEASE_DEFINE_WITH_OPTIONS(C, T, empty_pages, new_pages) \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_GET_DEFINE_WITH_OPTIONS(C, T, empty_pages, new_pages) \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_FREE_EMPTY_PAGES_DEFINE_WITH_OPTIONS(C, T, empty_pages, new_pages) \ + +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_DEFINE(C, T) THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_TYPE_DEFINE_WITH_OPTIONS(C, T, AUTO_FREE_EMPTY_PAGES, DO_NOT_ZERO_NEW_PAGES) + +#endif /*THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_H*/ diff --git a/src/paged_sparse_array.c b/src/paged_sparse_array.c index 76882ba6..24f129e4 100644 --- a/src/paged_sparse_array.c +++ b/src/paged_sparse_array.c @@ -8,5 +8,3 @@ MU_DEFINE_ENUM_STRINGS(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_RESULT_VALUES) MU_DEFINE_ENUM_STRINGS(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_RESULT_VALUES) - -MU_DEFINE_ENUM_STRINGS(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT_VALUES) diff --git a/src/thread_safe_paged_sparse_array.c b/src/thread_safe_paged_sparse_array.c new file mode 100644 index 00000000..04bcd58e --- /dev/null +++ b/src/thread_safe_paged_sparse_array.c @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include "macro_utils/macro_utils.h" + +#include "c_util/thread_safe_paged_sparse_array_ll.h" + +MU_DEFINE_ENUM_STRINGS(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT_VALUES) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5e97149d..591e45f9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -30,6 +30,7 @@ if(${run_unittests}) build_test_folder(memory_data_ut) build_test_folder(object_lifetime_tracker_ut) build_test_folder(paged_sparse_array_ut) + build_test_folder(thread_safe_paged_sparse_array_ut) build_test_folder(rc_ptr_ut) build_test_folder(rc_string_array_ut) build_test_folder(rc_string_utils_ut) @@ -71,6 +72,7 @@ if(${run_int_tests}) build_test_folder(tarray_2_int) build_test_folder(two_d_array_int) build_test_folder(paged_sparse_array_int) + build_test_folder(thread_safe_paged_sparse_array_int) build_test_folder(channel_int) build_test_folder(worker_thread_int) build_test_folder(tp_worker_thread_int) diff --git a/tests/paged_sparse_array_int/paged_sparse_array_int.c b/tests/paged_sparse_array_int/paged_sparse_array_int.c index 575baf7b..f82c6ab4 100644 --- a/tests/paged_sparse_array_int/paged_sparse_array_int.c +++ b/tests/paged_sparse_array_int/paged_sparse_array_int.c @@ -55,21 +55,6 @@ THANDLE_TYPE_DEFINE(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(TEST_ELEMENT_WITH_MALLOC)); PAGED_SPARSE_ARRAY_TYPE_DECLARE(TEST_ELEMENT_WITH_MALLOC); PAGED_SPARSE_ARRAY_TYPE_DEFINE(TEST_ELEMENT_WITH_MALLOC); -/* - * RING_ELEMENT - element type used by the producer/consumer ring buffer test with a thread-safe array - */ -typedef struct RING_ELEMENT_TAG -{ - volatile_atomic int64_t published_sequence_plus_one; -} RING_ELEMENT; - -PAGED_SPARSE_ARRAY_DEFINE_STRUCT_TYPE(RING_ELEMENT); -THANDLE_TYPE_DECLARE(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(RING_ELEMENT)); -THANDLE_TYPE_DEFINE(PAGED_SPARSE_ARRAY_TYPEDEF_NAME(RING_ELEMENT)); - -PAGED_SPARSE_ARRAY_TYPE_DECLARE(RING_ELEMENT); -PAGED_SPARSE_ARRAY_TYPE_DEFINE(RING_ELEMENT); - #define CHAOS_TEST_ACTION_VALUES \ CHAOS_TEST_ACTION_ALLOCATE, \ CHAOS_TEST_ACTION_RELEASE, \ @@ -80,492 +65,6 @@ MU_DEFINE_ENUM_STRINGS(CHAOS_TEST_ACTION, CHAOS_TEST_ACTION_VALUES) TEST_DEFINE_ENUM_TYPE(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_RESULT_VALUES) TEST_DEFINE_ENUM_TYPE(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_RESULT_VALUES) -TEST_DEFINE_ENUM_TYPE(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT_VALUES) - -/* - * Multi-threaded tests for arrays created with thread_safe set to true. - * Asserts cannot be used from worker threads, so worker threads count failures and the test thread asserts on the count. - */ -#define CONCURRENT_THREAD_COUNT 8 -#define CONCURRENT_OPERATIONS_PER_THREAD 20000 -#define CONCURRENT_MAX_SIZE 256 -#define CONCURRENT_PAGE_SIZE 16 -#define CONCURRENT_MAX_HELD_ELEMENTS_PER_THREAD 4 - -static volatile_atomic int64_t g_dispose_count; - -static void count_dispose(TEST_ELEMENT* item) -{ - (void)item; - (void)interlocked_increment_64(&g_dispose_count); -} - -static uint64_t test_next_random(uint64_t* state) -{ - /* 64-bit LCG, each thread has its own state so no locking is needed */ - *state = *state * 6364136223846793005ULL + 1442695040888963407ULL; - return *state >> 33; -} - -typedef struct CONCURRENT_TEST_CONTEXT_TAG -{ - const PAGED_SPARSE_ARRAY_TYPEDEF_NAME(TEST_ELEMENT)* psa; - bool call_free_page_if_empty; - volatile_atomic int32_t owners[CONCURRENT_MAX_SIZE]; - volatile_atomic int64_t successful_allocations; - volatile_atomic int32_t failures; -} CONCURRENT_TEST_CONTEXT; - -typedef struct CONCURRENT_THREAD_CONTEXT_TAG -{ - CONCURRENT_TEST_CONTEXT* test_context; - int32_t thread_id; -} CONCURRENT_THREAD_CONTEXT; - -typedef struct HELD_ELEMENT_TAG -{ - uint64_t index; - TEST_ELEMENT* element; - int64_t expected_value; -} HELD_ELEMENT; - -static void concurrent_record_failure(CONCURRENT_TEST_CONTEXT* test_context) -{ - (void)interlocked_increment(&test_context->failures); -} - -static void concurrent_release_held_element(CONCURRENT_TEST_CONTEXT* test_context, int32_t thread_id, const HELD_ELEMENT* held) -{ - /* the element is owned by this thread, so its value must not have been changed and its page must not have been freed */ - if (held->element->value != held->expected_value) - { - LogError("thread_id=%" PRId32 ": held element value changed, index=%" PRIu64 ", page_index=%" PRIu64 ", element=%p, value=%" PRId64 ", expected_value=%" PRId64 "", - thread_id, held->index, held->index / CONCURRENT_PAGE_SIZE, (void*)held->element, held->element->value, held->expected_value); - concurrent_record_failure(test_context); - } - else - { - TEST_ELEMENT* element_from_get = NULL; - PAGED_SPARSE_ARRAY_GET_RESULT get_result = PAGED_SPARSE_ARRAY_GET(TEST_ELEMENT)(test_context->psa, held->index, &element_from_get); - if ( - (get_result != PAGED_SPARSE_ARRAY_GET_OK) || - (element_from_get != held->element) - ) - { - LogError("thread_id=%" PRId32 ": GET of a held element did not return the allocated pointer, index=%" PRIu64 ", page_index=%" PRIu64 ", get_result=%" PRI_MU_ENUM ", element_from_get=%p, allocated element=%p", - thread_id, held->index, held->index / CONCURRENT_PAGE_SIZE, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_GET_RESULT, get_result), (void*)element_from_get, (void*)held->element); - concurrent_record_failure(test_context); - } - else - { - /* do nothing, the element is as expected */ - } - } - - int32_t owner = interlocked_exchange(&test_context->owners[held->index], 0); - if (owner != thread_id) - { - LogError("thread_id=%" PRId32 ": element owner changed while held, index=%" PRIu64 ", page_index=%" PRIu64 ", owner=%" PRId32 "", - thread_id, held->index, held->index / CONCURRENT_PAGE_SIZE, owner); - concurrent_record_failure(test_context); - } - else - { - /* do nothing, ownership was exclusive */ - } - - PAGED_SPARSE_ARRAY_RELEASE(TEST_ELEMENT)(test_context->psa, held->index); -} - -static int concurrent_chaos_thread(void* arg) -{ - CONCURRENT_THREAD_CONTEXT* thread_context = arg; - CONCURRENT_TEST_CONTEXT* test_context = thread_context->test_context; - int32_t thread_id = thread_context->thread_id; - uint64_t random_state = (uint64_t)thread_id; - HELD_ELEMENT held[CONCURRENT_MAX_HELD_ELEMENTS_PER_THREAD]; - uint32_t held_count = 0; - - for (uint32_t operation = 0; operation < CONCURRENT_OPERATIONS_PER_THREAD; operation++) - { - uint64_t random_value = test_next_random(&random_state); - if ( - (held_count < CONCURRENT_MAX_HELD_ELEMENTS_PER_THREAD) && - ((held_count == 0) || ((random_value % 2) == 0)) - ) - { - uint64_t index = test_next_random(&random_state) % CONCURRENT_MAX_SIZE; - TEST_ELEMENT* element; - PAGED_SPARSE_ARRAY_ALLOCATE_RESULT allocate_result = PAGED_SPARSE_ARRAY_ALLOCATE(TEST_ELEMENT)(test_context->psa, index, &element); - if (allocate_result == PAGED_SPARSE_ARRAY_ALLOCATE_ALREADY_ALLOCATED) - { - /* do nothing, the element is owned by another thread (or by this one) */ - } - else if (allocate_result != PAGED_SPARSE_ARRAY_ALLOCATE_OK) - { - LogError("thread_id=%" PRId32 ": ALLOCATE failed, operation=%" PRIu32 ", index=%" PRIu64 ", page_index=%" PRIu64 ", held_count=%" PRIu32 ", allocate_result=%" PRI_MU_ENUM "", - thread_id, operation, index, index / CONCURRENT_PAGE_SIZE, held_count, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, allocate_result)); - concurrent_record_failure(test_context); - } - else - { - (void)interlocked_increment_64(&test_context->successful_allocations); - int32_t previous_owner = interlocked_compare_exchange(&test_context->owners[index], thread_id, 0); - if (previous_owner != 0) - { - LogError("thread_id=%" PRId32 ": ALLOCATE succeeded for an element owned by another thread, operation=%" PRIu32 ", index=%" PRIu64 ", page_index=%" PRIu64 ", previous_owner=%" PRId32 ", allocate_result=%" PRI_MU_ENUM "", - thread_id, operation, index, index / CONCURRENT_PAGE_SIZE, previous_owner, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, allocate_result)); - concurrent_record_failure(test_context); - } - else - { - /* do nothing, this thread is now the only owner */ - } - held[held_count].index = index; - held[held_count].element = element; - held[held_count].expected_value = ((int64_t)thread_id << 32) | operation; - element->value = held[held_count].expected_value; - held_count++; - } - } - else - { - uint32_t held_position = (uint32_t)(random_value % held_count); - concurrent_release_held_element(test_context, thread_id, &held[held_position]); - held_count--; - held[held_position] = held[held_count]; - } - - /* elements of other threads are not read, only the result of GET is checked */ - uint64_t other_index = test_next_random(&random_state) % CONCURRENT_MAX_SIZE; - TEST_ELEMENT* other_element; - PAGED_SPARSE_ARRAY_GET_RESULT get_result = PAGED_SPARSE_ARRAY_GET(TEST_ELEMENT)(test_context->psa, other_index, &other_element); - if ( - (get_result != PAGED_SPARSE_ARRAY_GET_OK) && - (get_result != PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED) - ) - { - LogError("thread_id=%" PRId32 ": GET returned an unexpected result, operation=%" PRIu32 ", index=%" PRIu64 ", page_index=%" PRIu64 ", get_result=%" PRI_MU_ENUM "", - thread_id, operation, other_index, other_index / CONCURRENT_PAGE_SIZE, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_GET_RESULT, get_result)); - concurrent_record_failure(test_context); - } - else - { - /* do nothing, both results are possible */ - } - - if (!test_context->call_free_page_if_empty) - { - /* do nothing */ - } - else - { - uint64_t index = test_next_random(&random_state) % CONCURRENT_MAX_SIZE; - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT free_result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(TEST_ELEMENT)(test_context->psa, index); - if (free_result == PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS) - { - LogError("thread_id=%" PRId32 ": FREE_PAGE_IF_EMPTY returned an unexpected result, operation=%" PRIu32 ", index=%" PRIu64 ", page_index=%" PRIu64 ", free_result=%" PRI_MU_ENUM "", - thread_id, operation, index, index / CONCURRENT_PAGE_SIZE, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, free_result)); - concurrent_record_failure(test_context); - } - else - { - /* do nothing, FREED, NOT_ALLOCATED and NOT_EMPTY are all possible */ - } - } - } - - while (held_count > 0) - { - held_count--; - concurrent_release_held_element(test_context, thread_id, &held[held_count]); - } - - return 0; -} - -static void run_concurrent_chaos_test(bool auto_free_empty_pages, bool call_free_page_if_empty) -{ - //arrange - PAGED_SPARSE_ARRAY_OPTIONS options; - options.thread_safe = true; - options.auto_free_empty_pages = auto_free_empty_pages; - options.zero_new_pages = false; - PAGED_SPARSE_ARRAY(TEST_ELEMENT) psa = PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(TEST_ELEMENT)(CONCURRENT_MAX_SIZE, CONCURRENT_PAGE_SIZE, count_dispose, &options); - ASSERT_IS_NOT_NULL(psa); - - CONCURRENT_TEST_CONTEXT* test_context = malloc(sizeof(CONCURRENT_TEST_CONTEXT)); - ASSERT_IS_NOT_NULL(test_context); - test_context->psa = psa; - test_context->call_free_page_if_empty = call_free_page_if_empty; - for (uint32_t i = 0; i < CONCURRENT_MAX_SIZE; i++) - { - (void)interlocked_exchange(&test_context->owners[i], 0); - } - (void)interlocked_exchange_64(&test_context->successful_allocations, 0); - (void)interlocked_exchange(&test_context->failures, 0); - (void)interlocked_exchange_64(&g_dispose_count, 0); - - CONCURRENT_THREAD_CONTEXT thread_contexts[CONCURRENT_THREAD_COUNT]; - THREAD_HANDLE threads[CONCURRENT_THREAD_COUNT]; - int32_t threads_created = 0; - bool thread_api_failed = false; - - //act - /* no ASSERT until all the started threads are joined, since they use stack variables of this function */ - for (int32_t i = 0; i < CONCURRENT_THREAD_COUNT; i++) - { - thread_contexts[i].test_context = test_context; - thread_contexts[i].thread_id = i + 1; /*0 means "no owner"*/ - if (ThreadAPI_Create(&threads[i], concurrent_chaos_thread, &thread_contexts[i]) != THREADAPI_OK) - { - thread_api_failed = true; - break; - } - else - { - threads_created++; - } - } - for (int32_t i = 0; i < threads_created; i++) - { - int thread_result; - if ( - (ThreadAPI_Join(threads[i], &thread_result) != THREADAPI_OK) || - (thread_result != 0) - ) - { - thread_api_failed = true; - } - else - { - /* do nothing */ - } - } - - //assert - ASSERT_IS_FALSE(thread_api_failed); - ASSERT_ARE_EQUAL(int32_t, 0, interlocked_add(&test_context->failures, 0)); - ASSERT_IS_TRUE(interlocked_add_64(&test_context->successful_allocations, 0) > 0); - ASSERT_ARE_EQUAL(int64_t, interlocked_add_64(&test_context->successful_allocations, 0), interlocked_add_64(&g_dispose_count, 0)); - - if (auto_free_empty_pages) - { - /* do nothing, every page was freed when its last element was released */ - } - else - { - for (uint64_t page_index = 0; page_index < psa->page_count; page_index++) - { - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT free_result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(TEST_ELEMENT)(psa, page_index * CONCURRENT_PAGE_SIZE); - ASSERT_ARE_NOT_EQUAL(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY, free_result); - ASSERT_ARE_NOT_EQUAL(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS, free_result); - } - } - for (uint64_t page_index = 0; page_index < psa->page_count; page_index++) - { - ASSERT_IS_NULL(psa->pages[page_index]); - } - - //cleanup - free(test_context); - PAGED_SPARSE_ARRAY_ASSIGN(TEST_ELEMENT)(&psa, NULL); -} - -/* - * Producer/consumer ring buffer over a thread-safe array, the same usage pattern as an ordered writer: - * producers allocate sequence % RING_MAX_SIZE (at most RING_MAX_SIZE sequences ahead of the tail), - * a single consumer releases the elements in sequence order and asks the array to free a page when the tail leaves it. - * A page can then still hold elements of the next lap (allocated by producers), in which case it is not freed. - */ -#define RING_PRODUCER_COUNT 4 -#define RING_TOTAL_SEQUENCES 200000 -#define RING_MAX_SIZE 2048 -#define RING_PAGE_SIZE 256 - -typedef struct RING_TEST_CONTEXT_TAG -{ - const PAGED_SPARSE_ARRAY_TYPEDEF_NAME(RING_ELEMENT)* psa; - volatile_atomic int64_t next_sequence; - volatile_atomic int64_t tail; - volatile_atomic int32_t failures; /*once not 0, the producers and the consumer stop instead of waiting for each other*/ -} RING_TEST_CONTEXT; - -static bool ring_has_failed(RING_TEST_CONTEXT* test_context) -{ - return interlocked_add(&test_context->failures, 0) != 0; -} - -static int ring_producer_thread(void* arg) -{ - RING_TEST_CONTEXT* test_context = arg; - int64_t sequence; - while ( - ((sequence = interlocked_increment_64(&test_context->next_sequence) - 1) < RING_TOTAL_SEQUENCES) && - (!ring_has_failed(test_context)) - ) - { - /* wait until the sequence is within the window */ - while ( - (sequence - interlocked_add_64(&test_context->tail, 0) >= RING_MAX_SIZE) && - (!ring_has_failed(test_context)) - ) - { - ThreadAPI_Sleep(0); - } - - if (ring_has_failed(test_context)) - { - /* do nothing, the loop ends */ - } - else - { - RING_ELEMENT* element; - PAGED_SPARSE_ARRAY_ALLOCATE_RESULT allocate_result = PAGED_SPARSE_ARRAY_ALLOCATE(RING_ELEMENT)(test_context->psa, (uint64_t)sequence % RING_MAX_SIZE, &element); - if (allocate_result != PAGED_SPARSE_ARRAY_ALLOCATE_OK) - { - uint64_t index = (uint64_t)sequence % RING_MAX_SIZE; - LogError("producer ALLOCATE failed, sequence=%" PRId64 ", index=%" PRIu64 ", page_index=%" PRIu64 ", tail=%" PRId64 ", allocate_result=%" PRI_MU_ENUM "", - sequence, index, index / RING_PAGE_SIZE, interlocked_add_64(&test_context->tail, 0), MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, allocate_result)); - (void)interlocked_increment(&test_context->failures); - } - else - { - (void)interlocked_exchange_64(&element->published_sequence_plus_one, sequence + 1); - } - } - } - return 0; -} - -static void run_ring_buffer_test(void) -{ - //arrange - PAGED_SPARSE_ARRAY_OPTIONS options; - options.thread_safe = true; - options.auto_free_empty_pages = false; - options.zero_new_pages = true; - PAGED_SPARSE_ARRAY(RING_ELEMENT) psa = PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(RING_ELEMENT)(RING_MAX_SIZE, RING_PAGE_SIZE, NULL, &options); - ASSERT_IS_NOT_NULL(psa); - - RING_TEST_CONTEXT test_context; - test_context.psa = psa; - (void)interlocked_exchange_64(&test_context.next_sequence, 0); - (void)interlocked_exchange_64(&test_context.tail, 0); - (void)interlocked_exchange(&test_context.failures, 0); - - THREAD_HANDLE producers[RING_PRODUCER_COUNT]; - uint32_t producers_created = 0; - bool thread_api_failed = false; - uint32_t pages_freed = 0; - uint32_t pages_kept_not_empty = 0; - - //act - /* no ASSERT until all the started producers are joined, since they use stack variables of this function */ - for (uint32_t i = 0; i < RING_PRODUCER_COUNT; i++) - { - if (ThreadAPI_Create(&producers[i], ring_producer_thread, &test_context) != THREADAPI_OK) - { - thread_api_failed = true; - (void)interlocked_increment(&test_context.failures); - break; - } - else - { - producers_created++; - } - } - - for (int64_t tail = 0; (tail < RING_TOTAL_SEQUENCES) && (!ring_has_failed(&test_context)); tail++) - { - uint64_t index = (uint64_t)tail % RING_MAX_SIZE; - RING_ELEMENT* element; - - /* wait until a producer allocated the element and published this sequence in it */ - while ( - (!ring_has_failed(&test_context)) && - ( - (PAGED_SPARSE_ARRAY_GET(RING_ELEMENT)(psa, index, &element) != PAGED_SPARSE_ARRAY_GET_OK) || - (interlocked_add_64(&element->published_sequence_plus_one, 0) != tail + 1) - ) - ) - { - ThreadAPI_Sleep(0); - } - - if (ring_has_failed(&test_context)) - { - /* do nothing, the loop ends */ - } - else - { - PAGED_SPARSE_ARRAY_RELEASE(RING_ELEMENT)(psa, index); - (void)interlocked_exchange_64(&test_context.tail, tail + 1); - - if ( - ((index + 1) % RING_PAGE_SIZE != 0) && - (index + 1 != RING_MAX_SIZE) - ) - { - /* do nothing, the tail did not leave the page */ - } - else - { - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT free_result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(RING_ELEMENT)(psa, index); - if (free_result == PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_FREED) - { - pages_freed++; - } - else if (free_result == PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY) - { - /* producers already allocated elements of the next lap in this page */ - pages_kept_not_empty++; - } - else - { - LogError("consumer got an unexpected FREE_PAGE_IF_EMPTY result, tail=%" PRId64 ", index=%" PRIu64 ", page_index=%" PRIu64 ", free_result=%" PRI_MU_ENUM "", - tail, index, index / RING_PAGE_SIZE, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, free_result)); - (void)interlocked_increment(&test_context.failures); - } - } - } - } - - for (uint32_t i = 0; i < producers_created; i++) - { - int thread_result; - if ( - (ThreadAPI_Join(producers[i], &thread_result) != THREADAPI_OK) || - (thread_result != 0) - ) - { - thread_api_failed = true; - } - else - { - /* do nothing */ - } - } - - //assert - LogInfo("ring buffer test: producers=%" PRIu32 "/%" PRIu32 ", sequences=%" PRIu32 ", consumed=%" PRId64 ", max_size=%" PRIu32 ", page_size=%" PRIu32 ", pages_freed=%" PRIu32 ", pages_kept_not_empty=%" PRIu32 ", failures=%" PRId32 ", thread_api_failed=%" PRI_BOOL "", - producers_created, (uint32_t)RING_PRODUCER_COUNT, (uint32_t)RING_TOTAL_SEQUENCES, interlocked_add_64(&test_context.tail, 0), (uint32_t)RING_MAX_SIZE, (uint32_t)RING_PAGE_SIZE, - pages_freed, pages_kept_not_empty, interlocked_add(&test_context.failures, 0), MU_BOOL_VALUE(thread_api_failed)); - ASSERT_IS_FALSE(thread_api_failed); - ASSERT_ARE_EQUAL(int32_t, 0, interlocked_add(&test_context.failures, 0)); - ASSERT_ARE_EQUAL(uint32_t, RING_TOTAL_SEQUENCES / RING_PAGE_SIZE, pages_freed + pages_kept_not_empty); - - /* every element was released, so every page that is still allocated is empty */ - for (uint64_t page_index = 0; page_index < psa->page_count; page_index++) - { - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT free_result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(RING_ELEMENT)(psa, page_index * RING_PAGE_SIZE); - ASSERT_ARE_NOT_EQUAL(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY, free_result); - ASSERT_IS_NULL(psa->pages[page_index]); - } - - //cleanup - PAGED_SPARSE_ARRAY_ASSIGN(RING_ELEMENT)(&psa, NULL); -} BEGIN_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) @@ -1013,23 +512,4 @@ TEST_FUNCTION(PAGED_SPARSE_ARRAY_with_malloc_element_chaos) PAGED_SPARSE_ARRAY_ASSIGN(TEST_ELEMENT_WITH_MALLOC)(&psa, NULL); } -/* - * Tests for arrays created with options - */ - -TEST_FUNCTION(PAGED_SPARSE_ARRAY_thread_safe_chaos_with_auto_free_empty_pages) -{ - run_concurrent_chaos_test(true, false); -} - -TEST_FUNCTION(PAGED_SPARSE_ARRAY_thread_safe_chaos_with_free_page_if_empty) -{ - run_concurrent_chaos_test(false, true); -} - -TEST_FUNCTION(PAGED_SPARSE_ARRAY_thread_safe_ring_buffer_with_free_page_if_empty_when_tail_leaves_a_page) -{ - run_ring_buffer_test(); -} - END_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) diff --git a/tests/paged_sparse_array_ut/paged_sparse_array_ut.c b/tests/paged_sparse_array_ut/paged_sparse_array_ut.c index b9f1f072..b112c568 100644 --- a/tests/paged_sparse_array_ut/paged_sparse_array_ut.c +++ b/tests/paged_sparse_array_ut/paged_sparse_array_ut.c @@ -44,95 +44,12 @@ MU_DEFINE_ENUM_STRINGS(UMOCK_C_ERROR_CODE, UMOCK_C_ERROR_CODE_VALUES) TEST_DEFINE_ENUM_TYPE(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_RESULT_VALUES) TEST_DEFINE_ENUM_TYPE(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_RESULT_VALUES) -TEST_DEFINE_ENUM_TYPE(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT_VALUES) - -#define TEST_MAX_SIZE 100 -#define TEST_PAGE_SIZE 16 -#define TEST_PAGE_COUNT 7 /*(TEST_MAX_SIZE + TEST_PAGE_SIZE - 1) / TEST_PAGE_SIZE*/ -#define TEST_INDEX_IN_FIRST_PAGE 5 -#define TEST_OTHER_INDEX_IN_FIRST_PAGE 10 -#define TEST_ITEM_VALUE 42 -#define TEST_GARBAGE_BYTE 0xFF -#define TEST_GARBAGE_ITEM_VALUE UINT32_MAX - -#define TEST_PSA_LOCK(psa) ((SRW_LOCK_LL*)&(psa)->lock) static void on_umock_c_error(UMOCK_C_ERROR_CODE error_code) { ASSERT_FAIL("umock_c reported error :%" PRI_MU_ENUM "", MU_ENUM_VALUE(UMOCK_C_ERROR_CODE, error_code)); } -static PAGED_SPARSE_ARRAY_OPTIONS test_make_options(bool thread_safe, bool auto_free_empty_pages, bool zero_new_pages) -{ - PAGED_SPARSE_ARRAY_OPTIONS options; - options.thread_safe = thread_safe; - options.auto_free_empty_pages = auto_free_empty_pages; - options.zero_new_pages = zero_new_pages; - return options; -} - -static PAGED_SPARSE_ARRAY(uint32_t) test_create_with_options(bool thread_safe, bool auto_free_empty_pages, bool zero_new_pages, PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(uint32_t) item_dispose_func) -{ - PAGED_SPARSE_ARRAY_OPTIONS options = test_make_options(thread_safe, auto_free_empty_pages, zero_new_pages); - PAGED_SPARSE_ARRAY(uint32_t) psa = PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(uint32_t)(TEST_MAX_SIZE, TEST_PAGE_SIZE, item_dispose_func, &options); - ASSERT_IS_NOT_NULL(psa); - umock_c_reset_all_calls(); - return psa; -} - -static uint32_t* test_allocate(PAGED_SPARSE_ARRAY(uint32_t) psa, uint64_t index) -{ - uint32_t* ptr = NULL; - ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, index, &ptr)); - ASSERT_IS_NOT_NULL(ptr); - umock_c_reset_all_calls(); - return ptr; -} - -static bool test_is_allocated_in_bitmap(PAGED_SPARSE_ARRAY(uint32_t) psa, uint64_t index) -{ - PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)* page = psa->pages[index / TEST_PAGE_SIZE]; - ASSERT_IS_NOT_NULL(page); - return PAGED_SPARSE_ARRAY_IS_ALLOCATED(PAGED_SPARSE_ARRAY_GET_BITMAP(page, TEST_PAGE_SIZE), index % TEST_PAGE_SIZE) != 0; -} - -/*used by tests that simulate another thread calling into the array while the lock is not held*/ -static struct -{ - const PAGED_SPARSE_ARRAY_TYPEDEF_NAME(uint32_t)* psa; - uint64_t index_to_allocate; - bool was_called; -} g_concurrent_allocate; - -static void hook_srw_lock_ll_acquire_exclusive_allocates_concurrently(SRW_LOCK_LL* srw_lock_ll) -{ - if (g_concurrent_allocate.was_called) - { - /* do nothing, only one concurrent allocation is simulated */ - } - else - { - uint32_t* other_ptr; - g_concurrent_allocate.was_called = true; - ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(g_concurrent_allocate.psa, g_concurrent_allocate.index_to_allocate, &other_ptr)); - } - real_srw_lock_ll_acquire_exclusive(srw_lock_ll); -} - -static void setup_concurrent_allocate_on_acquire_exclusive(PAGED_SPARSE_ARRAY(uint32_t) psa, uint64_t index_to_allocate) -{ - g_concurrent_allocate.psa = psa; - g_concurrent_allocate.index_to_allocate = index_to_allocate; - g_concurrent_allocate.was_called = false; - REGISTER_GLOBAL_MOCK_HOOK(srw_lock_ll_acquire_exclusive, hook_srw_lock_ll_acquire_exclusive_allocates_concurrently); -} - -static void cleanup_concurrent_allocate_on_acquire_exclusive(void) -{ - REGISTER_GLOBAL_MOCK_HOOK(srw_lock_ll_acquire_exclusive, real_srw_lock_ll_acquire_exclusive); - g_concurrent_allocate.psa = NULL; -} - BEGIN_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) @@ -143,10 +60,6 @@ TEST_SUITE_INITIALIZE(it_does_something) umock_c_init(on_umock_c_error); REGISTER_GBALLOC_HL_GLOBAL_MOCK_HOOK(); - REGISTER_SRW_LOCK_LL_GLOBAL_MOCK_HOOK(); - - REGISTER_GLOBAL_MOCK_FAIL_RETURN(malloc_flex, NULL); - REGISTER_GLOBAL_MOCK_FAIL_RETURN(srw_lock_ll_init, MU_FAILURE); } TEST_SUITE_CLEANUP(TestClassCleanup) @@ -298,152 +211,6 @@ TEST_FUNCTION(PAGED_SPARSE_ARRAY_CREATE_with_struct_type_succeeds) PAGED_SPARSE_ARRAY_ASSIGN(B_TEST)(&psa, NULL); } -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_046: [ PAGED_SPARSE_ARRAY_CREATE(T) shall create the paged sparse array with thread_safe set to false, auto_free_empty_pages set to true and zero_new_pages set to false. ]*/ -TEST_FUNCTION(PAGED_SPARSE_ARRAY_CREATE_uses_default_options_and_does_not_create_a_lock) -{ - //arrange - STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, TEST_PAGE_COUNT, sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)*))); - - //act - PAGED_SPARSE_ARRAY(uint32_t) psa = PAGED_SPARSE_ARRAY_CREATE(uint32_t)(TEST_MAX_SIZE, TEST_PAGE_SIZE, NULL); - - //assert - ASSERT_IS_NOT_NULL(psa); - ASSERT_IS_FALSE(psa->options.thread_safe); - ASSERT_IS_TRUE(psa->options.auto_free_empty_pages); - ASSERT_IS_FALSE(psa->options.zero_new_pages); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/*PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T)*/ - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_047: [ If options is NULL, PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall fail and return NULL. ]*/ -TEST_FUNCTION(when_options_is_NULL_then_PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS_fails) -{ - //arrange - - //act - PAGED_SPARSE_ARRAY(uint32_t) psa = PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(uint32_t)(TEST_MAX_SIZE, TEST_PAGE_SIZE, NULL, NULL); - - //assert - ASSERT_IS_NULL(psa); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_048: [ PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall validate max_size and page_size, and allocate and initialize the paged sparse array, in the same way as PAGED_SPARSE_ARRAY_CREATE(T). ]*/ -TEST_FUNCTION(when_max_size_is_zero_then_PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS_fails) -{ - //arrange - PAGED_SPARSE_ARRAY_OPTIONS options = test_make_options(true, true, false); - - //act - PAGED_SPARSE_ARRAY(uint32_t) psa = PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(uint32_t)(0, TEST_PAGE_SIZE, NULL, &options); - - //assert - ASSERT_IS_NULL(psa); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_048: [ PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall validate max_size and page_size, and allocate and initialize the paged sparse array, in the same way as PAGED_SPARSE_ARRAY_CREATE(T). ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_049: [ PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall store a copy of *options in the structure. ]*/ -TEST_FUNCTION(PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS_without_thread_safe_succeeds_and_does_not_create_a_lock) -{ - //arrange - PAGED_SPARSE_ARRAY_OPTIONS options = test_make_options(false, false, true); - STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, TEST_PAGE_COUNT, sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)*))); - - //act - PAGED_SPARSE_ARRAY(uint32_t) psa = PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(uint32_t)(TEST_MAX_SIZE, TEST_PAGE_SIZE, NULL, &options); - - //assert - ASSERT_IS_NOT_NULL(psa); - ASSERT_ARE_EQUAL(uint64_t, TEST_MAX_SIZE, psa->max_size); - ASSERT_ARE_EQUAL(uint64_t, TEST_PAGE_SIZE, psa->page_size); - ASSERT_ARE_EQUAL(uint64_t, TEST_PAGE_COUNT, psa->page_count); - for (uint64_t i = 0; i < psa->page_count; i++) - { - ASSERT_IS_NULL(psa->pages[i]); - } - ASSERT_IS_FALSE(psa->options.thread_safe); - ASSERT_IS_FALSE(psa->options.auto_free_empty_pages); - ASSERT_IS_TRUE(psa->options.zero_new_pages); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_049: [ PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall store a copy of *options in the structure. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_050: [ If options->thread_safe is true, PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall call srw_lock_ll_init to initialize the lock. ]*/ -TEST_FUNCTION(PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS_with_thread_safe_succeeds_and_initializes_the_lock) -{ - //arrange - PAGED_SPARSE_ARRAY_OPTIONS options = test_make_options(true, true, false); - STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, TEST_PAGE_COUNT, sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)*))); - STRICT_EXPECTED_CALL(srw_lock_ll_init(IGNORED_ARG)); - - //act - PAGED_SPARSE_ARRAY(uint32_t) psa = PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(uint32_t)(TEST_MAX_SIZE, TEST_PAGE_SIZE, NULL, &options); - - //assert - ASSERT_IS_NOT_NULL(psa); - ASSERT_IS_TRUE(psa->options.thread_safe); - ASSERT_IS_TRUE(psa->options.auto_free_empty_pages); - ASSERT_IS_FALSE(psa->options.zero_new_pages); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_051: [ If srw_lock_ll_init fails, PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall free the paged sparse array and return NULL. ]*/ -TEST_FUNCTION(when_srw_lock_ll_init_fails_then_PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS_frees_the_array_and_fails) -{ - //arrange - PAGED_SPARSE_ARRAY_OPTIONS options = test_make_options(true, true, false); - STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, TEST_PAGE_COUNT, sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)*))); - STRICT_EXPECTED_CALL(srw_lock_ll_init(IGNORED_ARG)) - .SetReturn(MU_FAILURE); - STRICT_EXPECTED_CALL(free(IGNORED_ARG)); - - //act - PAGED_SPARSE_ARRAY(uint32_t) psa = PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(uint32_t)(TEST_MAX_SIZE, TEST_PAGE_SIZE, NULL, &options); - - //assert - ASSERT_IS_NULL(psa); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_048: [ PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall validate max_size and page_size, and allocate and initialize the paged sparse array, in the same way as PAGED_SPARSE_ARRAY_CREATE(T). ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_051: [ If srw_lock_ll_init fails, PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(T) shall free the paged sparse array and return NULL. ]*/ -TEST_FUNCTION(when_underlying_calls_fail_then_PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS_fails) -{ - //arrange - PAGED_SPARSE_ARRAY_OPTIONS options = test_make_options(true, true, false); - STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, TEST_PAGE_COUNT, sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)*))); - STRICT_EXPECTED_CALL(srw_lock_ll_init(IGNORED_ARG)); - - umock_c_negative_tests_snapshot(); - - for (size_t i = 0; i < umock_c_negative_tests_call_count(); i++) - { - if (umock_c_negative_tests_can_call_fail(i)) - { - umock_c_negative_tests_reset(); - umock_c_negative_tests_fail_call(i); - - //act - PAGED_SPARSE_ARRAY(uint32_t) psa = PAGED_SPARSE_ARRAY_CREATE_WITH_OPTIONS(uint32_t)(TEST_MAX_SIZE, TEST_PAGE_SIZE, NULL, &options); - - //assert - ASSERT_IS_NULL(psa, "On failed call %zu", i); - } - } -} - /*PAGED_SPARSE_ARRAY_DISPOSE(T)*/ /* Tests_SRS_PAGED_SPARSE_ARRAY_88_009: [ If paged_sparse_array is NULL, PAGED_SPARSE_ARRAY_DISPOSE(T) shall return. ]*/ @@ -502,25 +269,6 @@ TEST_FUNCTION(PAGED_SPARSE_ARRAY_DISPOSE_frees_all_allocated_pages) //clean } -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_052: [ If thread_safe is true, PAGED_SPARSE_ARRAY_DISPOSE(T) shall call srw_lock_ll_deinit. ]*/ -TEST_FUNCTION(PAGED_SPARSE_ARRAY_DISPOSE_with_thread_safe_frees_pages_and_deinitializes_the_lock) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); - (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); - - STRICT_EXPECTED_CALL(free(IGNORED_ARG)); // page 0 - STRICT_EXPECTED_CALL(srw_lock_ll_deinit(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(free(IGNORED_ARG)); // THANDLE wrapper - - //act - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); - - //assert - ASSERT_IS_NULL(psa); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); -} - /*PAGED_SPARSE_ARRAY_ALLOCATE(T)*/ /* Tests_SRS_PAGED_SPARSE_ARRAY_88_013: [ PAGED_SPARSE_ARRAY_ALLOCATE(T) shall compute the page index as index / page_size. ]*/ @@ -729,214 +477,6 @@ TEST_FUNCTION(PAGED_SPARSE_ARRAY_ALLOCATE_fails_when_page_malloc_fails) PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); } -static PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)* test_allocate_page_memory_filled_with_garbage(void) -{ - size_t items_and_bitmap_size = TEST_PAGE_SIZE * sizeof(uint32_t) + PAGED_SPARSE_ARRAY_BITMAP_SIZE(TEST_PAGE_SIZE); - PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)* page_memory = real_gballoc_hl_malloc_flex(sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)), items_and_bitmap_size, 1); - ASSERT_IS_NOT_NULL(page_memory); - (void)memset(page_memory, TEST_GARBAGE_BYTE, sizeof(PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)) + items_and_bitmap_size); - return page_memory; -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_053: [ If zero_new_pages is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall set all the items of a newly allocated page to zero. ]*/ -TEST_FUNCTION(PAGED_SPARSE_ARRAY_ALLOCATE_with_zero_new_pages_sets_all_items_of_the_new_page_to_zero) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(false, true, true, NULL); - PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)* page_memory = test_allocate_page_memory_filled_with_garbage(); - STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, IGNORED_ARG, 1)) - .SetReturn(page_memory); - - //act - uint32_t* ptr; - PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &ptr); - - //assert - ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, result); - ASSERT_ARE_EQUAL(void_ptr, page_memory, psa->pages[0]); - for (uint64_t i = 0; i < TEST_PAGE_SIZE; i++) - { - ASSERT_ARE_EQUAL(uint32_t, 0, psa->pages[0]->items[i]); - } - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_053: [ If zero_new_pages is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall set all the items of a newly allocated page to zero. ]*/ -TEST_FUNCTION(PAGED_SPARSE_ARRAY_ALLOCATE_without_zero_new_pages_leaves_the_items_of_the_new_page_untouched) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = PAGED_SPARSE_ARRAY_CREATE(uint32_t)(TEST_MAX_SIZE, TEST_PAGE_SIZE, NULL); - ASSERT_IS_NOT_NULL(psa); - PAGED_SPARSE_ARRAY_PAGE_TYPEDEF_NAME(uint32_t)* page_memory = test_allocate_page_memory_filled_with_garbage(); - umock_c_reset_all_calls(); - STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, IGNORED_ARG, 1)) - .SetReturn(page_memory); - - //act - uint32_t* ptr; - PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &ptr); - - //assert - ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, result); - ASSERT_ARE_EQUAL(void_ptr, page_memory, psa->pages[0]); - for (uint64_t i = 0; i < TEST_PAGE_SIZE; i++) - { - ASSERT_ARE_EQUAL(uint32_t, TEST_GARBAGE_ITEM_VALUE, psa->pages[0]->items[i]); - } - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_054: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall acquire the lock in shared mode. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_055: [ If thread_safe is true and the page is not allocated, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall release the shared lock and acquire the lock in exclusive mode. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_056: [ After acquiring the lock in exclusive mode, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall check again whether the page is allocated, since another thread may have allocated it in the meantime. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_057: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall mark the element at index as allocated and increment the allocated count of the page using interlocked operations. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_058: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall release the lock (shared or exclusive, whichever is held) before returning. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_014: [ If the page is not allocated, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall call malloc_flex to allocate memory for the page structure containing page_size elements of type T plus a uint8_t allocation bitmap of (page_size + 7) / 8 bytes, and initialize all bitmap bits to 0 (not allocated). ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_017: [ PAGED_SPARSE_ARRAY_ALLOCATE(T) shall store a pointer to the element at index in *allocated_ptr and return PAGED_SPARSE_ARRAY_ALLOCATE_OK. ]*/ -TEST_FUNCTION(PAGED_SPARSE_ARRAY_ALLOCATE_with_thread_safe_allocates_the_page_under_the_exclusive_lock) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, IGNORED_ARG, 1)); - STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PSA_LOCK(psa))); - - //act - uint32_t* ptr; - PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &ptr); - - //assert - ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, result); - ASSERT_IS_NOT_NULL(psa->pages[0]); - ASSERT_ARE_EQUAL(void_ptr, &psa->pages[0]->items[TEST_INDEX_IN_FIRST_PAGE], ptr); - ASSERT_IS_TRUE(test_is_allocated_in_bitmap(psa, TEST_INDEX_IN_FIRST_PAGE)); - ASSERT_ARE_EQUAL(uint64_t, 1, psa->pages[0]->allocated_count); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_054: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall acquire the lock in shared mode. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_057: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall mark the element at index as allocated and increment the allocated count of the page using interlocked operations. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_058: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall release the lock (shared or exclusive, whichever is held) before returning. ]*/ -TEST_FUNCTION(PAGED_SPARSE_ARRAY_ALLOCATE_with_thread_safe_in_an_allocated_page_only_takes_the_shared_lock) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); - (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); - - //act - uint32_t* ptr; - PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE, &ptr); - - //assert - ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, result); - ASSERT_ARE_EQUAL(void_ptr, &psa->pages[0]->items[TEST_OTHER_INDEX_IN_FIRST_PAGE], ptr); - ASSERT_IS_TRUE(test_is_allocated_in_bitmap(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE)); - ASSERT_ARE_EQUAL(uint64_t, 2, psa->pages[0]->allocated_count); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_015: [ If the element at index is already allocated, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall fail and return PAGED_SPARSE_ARRAY_ALLOCATE_ALREADY_ALLOCATED. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_058: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall release the lock (shared or exclusive, whichever is held) before returning. ]*/ -TEST_FUNCTION(when_element_is_already_allocated_then_PAGED_SPARSE_ARRAY_ALLOCATE_with_thread_safe_fails) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); - (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); - - //act - uint32_t* ptr; - PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &ptr); - - //assert - ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_ALREADY_ALLOCATED, result); - ASSERT_ARE_EQUAL(uint64_t, 1, psa->pages[0]->allocated_count); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_018: [ If malloc_flex fails, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall fail and return PAGED_SPARSE_ARRAY_ALLOCATE_ERROR. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_058: [ If thread_safe is true, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall release the lock (shared or exclusive, whichever is held) before returning. ]*/ -TEST_FUNCTION(when_malloc_flex_fails_then_PAGED_SPARSE_ARRAY_ALLOCATE_with_thread_safe_fails) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, IGNORED_ARG, 1)) - .SetReturn(NULL); - STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PSA_LOCK(psa))); - - //act - uint32_t* ptr; - PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &ptr); - - //assert - ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_ERROR, result); - ASSERT_IS_NULL(psa->pages[0]); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_056: [ After acquiring the lock in exclusive mode, PAGED_SPARSE_ARRAY_ALLOCATE(T) shall check again whether the page is allocated, since another thread may have allocated it in the meantime. ]*/ -TEST_FUNCTION(when_another_thread_allocates_the_page_before_the_exclusive_lock_then_PAGED_SPARSE_ARRAY_ALLOCATE_with_thread_safe_uses_that_page) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); - setup_concurrent_allocate_on_acquire_exclusive(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE); - - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PSA_LOCK(psa))); - // the "other thread" allocates the page - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, IGNORED_ARG, 1)); - STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PSA_LOCK(psa))); - // no second page allocation - STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PSA_LOCK(psa))); - - //act - uint32_t* ptr; - PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &ptr); - - //assert - ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, result); - ASSERT_IS_TRUE(g_concurrent_allocate.was_called); - ASSERT_ARE_EQUAL(void_ptr, &psa->pages[0]->items[TEST_INDEX_IN_FIRST_PAGE], ptr); - ASSERT_IS_TRUE(test_is_allocated_in_bitmap(psa, TEST_INDEX_IN_FIRST_PAGE)); - ASSERT_IS_TRUE(test_is_allocated_in_bitmap(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE)); - ASSERT_ARE_EQUAL(uint64_t, 2, psa->pages[0]->allocated_count); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - cleanup_concurrent_allocate_on_acquire_exclusive(); - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - /*helper for item_dispose_func tests*/ MOCK_FUNCTION_WITH_CODE(, void, test_item_dispose_func, uint32_t*, item) @@ -997,7 +537,7 @@ TEST_FUNCTION(PAGED_SPARSE_ARRAY_RELEASE_with_NULL_item_dispose_func_does_not_ca /* Tests_SRS_PAGED_SPARSE_ARRAY_88_021: [ PAGED_SPARSE_ARRAY_RELEASE(T) shall compute the page index as index / page_size. ]*/ /* Tests_SRS_PAGED_SPARSE_ARRAY_88_024: [ PAGED_SPARSE_ARRAY_RELEASE(T) shall mark the element at index as not allocated. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_025: [ If auto_free_empty_pages is true and all elements in the page are now not allocated, PAGED_SPARSE_ARRAY_RELEASE(T) shall free the page and set the page pointer to NULL. ]*/ +/* Tests_SRS_PAGED_SPARSE_ARRAY_88_025: [ If all elements in the page are now not allocated, PAGED_SPARSE_ARRAY_RELEASE(T) shall free the page and set the page pointer to NULL. ]*/ TEST_FUNCTION(PAGED_SPARSE_ARRAY_RELEASE_succeeds_and_frees_page_when_last_element) { //arrange @@ -1118,195 +658,6 @@ TEST_FUNCTION(PAGED_SPARSE_ARRAY_RELEASE_with_element_not_allocated_returns) PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); } -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_025: [ If auto_free_empty_pages is true and all elements in the page are now not allocated, PAGED_SPARSE_ARRAY_RELEASE(T) shall free the page and set the page pointer to NULL. ]*/ -TEST_FUNCTION(PAGED_SPARSE_ARRAY_RELEASE_with_auto_free_empty_pages_false_keeps_the_page_when_last_element) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(false, false, false, NULL); - (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); - - //act - PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); - - //assert - ASSERT_IS_NOT_NULL(psa->pages[0]); - ASSERT_IS_FALSE(test_is_allocated_in_bitmap(psa, TEST_INDEX_IN_FIRST_PAGE)); - ASSERT_ARE_EQUAL(uint64_t, 0, psa->pages[0]->allocated_count); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_059: [ If thread_safe is true, PAGED_SPARSE_ARRAY_RELEASE(T) shall acquire the lock in shared mode. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_060: [ If thread_safe is true, PAGED_SPARSE_ARRAY_RELEASE(T) shall mark the element at index as not allocated using interlocked operations and, only if the element was still marked as allocated, decrement the allocated count of the page using interlocked operations. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_061: [ If thread_safe is true, PAGED_SPARSE_ARRAY_RELEASE(T) shall release the shared lock. ]*/ -TEST_FUNCTION(PAGED_SPARSE_ARRAY_RELEASE_with_thread_safe_keeps_the_page_when_other_elements_are_allocated) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); - (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); - (void)test_allocate(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE); - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); - - //act - PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); - - //assert - ASSERT_IS_NOT_NULL(psa->pages[0]); - ASSERT_IS_FALSE(test_is_allocated_in_bitmap(psa, TEST_INDEX_IN_FIRST_PAGE)); - ASSERT_IS_TRUE(test_is_allocated_in_bitmap(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE)); - ASSERT_ARE_EQUAL(uint64_t, 1, psa->pages[0]->allocated_count); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_062: [ If thread_safe is true, auto_free_empty_pages is true and the allocated count of the page became 0, PAGED_SPARSE_ARRAY_RELEASE(T) shall acquire the lock in exclusive mode, free the page only if it is still allocated and its allocated count is still 0, and release the exclusive lock. ]*/ -TEST_FUNCTION(PAGED_SPARSE_ARRAY_RELEASE_with_thread_safe_frees_the_page_under_the_exclusive_lock_when_last_element) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); - (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(free(IGNORED_ARG)); // page - STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PSA_LOCK(psa))); - - //act - PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); - - //assert - ASSERT_IS_NULL(psa->pages[0]); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_062: [ If thread_safe is true, auto_free_empty_pages is true and the allocated count of the page became 0, PAGED_SPARSE_ARRAY_RELEASE(T) shall acquire the lock in exclusive mode, free the page only if it is still allocated and its allocated count is still 0, and release the exclusive lock. ]*/ -TEST_FUNCTION(PAGED_SPARSE_ARRAY_RELEASE_with_thread_safe_and_auto_free_empty_pages_false_keeps_the_page_when_last_element) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, false, false, NULL); - (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); - - //act - PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); - - //assert - ASSERT_IS_NOT_NULL(psa->pages[0]); - ASSERT_ARE_EQUAL(uint64_t, 0, psa->pages[0]->allocated_count); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_062: [ If thread_safe is true, auto_free_empty_pages is true and the allocated count of the page became 0, PAGED_SPARSE_ARRAY_RELEASE(T) shall acquire the lock in exclusive mode, free the page only if it is still allocated and its allocated count is still 0, and release the exclusive lock. ]*/ -TEST_FUNCTION(when_another_thread_allocates_in_the_page_before_the_exclusive_lock_then_PAGED_SPARSE_ARRAY_RELEASE_with_thread_safe_keeps_the_page) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); - (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); - setup_concurrent_allocate_on_acquire_exclusive(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE); - - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PSA_LOCK(psa))); - // the "other thread" allocates an element in the (still allocated) page - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); - // no free of the page - STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PSA_LOCK(psa))); - - //act - PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); - - //assert - ASSERT_IS_TRUE(g_concurrent_allocate.was_called); - ASSERT_IS_NOT_NULL(psa->pages[0]); - ASSERT_IS_FALSE(test_is_allocated_in_bitmap(psa, TEST_INDEX_IN_FIRST_PAGE)); - ASSERT_IS_TRUE(test_is_allocated_in_bitmap(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE)); - ASSERT_ARE_EQUAL(uint64_t, 1, psa->pages[0]->allocated_count); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - cleanup_concurrent_allocate_on_acquire_exclusive(); - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_044: [ If item_dispose_func is not NULL, PAGED_SPARSE_ARRAY_RELEASE(T) shall call item_dispose_func for the element being released. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_059: [ If thread_safe is true, PAGED_SPARSE_ARRAY_RELEASE(T) shall acquire the lock in shared mode. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_061: [ If thread_safe is true, PAGED_SPARSE_ARRAY_RELEASE(T) shall release the shared lock. ]*/ -TEST_FUNCTION(PAGED_SPARSE_ARRAY_RELEASE_with_thread_safe_calls_item_dispose_func_while_holding_the_shared_lock) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, test_item_dispose_func); - uint32_t* ptr = test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); - (void)test_allocate(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE); - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(test_item_dispose_func(ptr)); - STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); - - //act - PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); - - //assert - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - umock_c_reset_all_calls(); - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_022: [ If the page is not allocated, PAGED_SPARSE_ARRAY_RELEASE(T) shall return. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_061: [ If thread_safe is true, PAGED_SPARSE_ARRAY_RELEASE(T) shall release the shared lock. ]*/ -TEST_FUNCTION(PAGED_SPARSE_ARRAY_RELEASE_with_thread_safe_and_page_not_allocated_returns) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, test_item_dispose_func); - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); - - //act - PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); - - //assert - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_023: [ If the element at index is not allocated, PAGED_SPARSE_ARRAY_RELEASE(T) shall return. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_061: [ If thread_safe is true, PAGED_SPARSE_ARRAY_RELEASE(T) shall release the shared lock. ]*/ -TEST_FUNCTION(PAGED_SPARSE_ARRAY_RELEASE_with_thread_safe_and_element_not_allocated_returns) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, test_item_dispose_func); - (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); - - //act - PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE); - - //assert - ASSERT_IS_TRUE(test_is_allocated_in_bitmap(psa, TEST_INDEX_IN_FIRST_PAGE)); - ASSERT_ARE_EQUAL(uint64_t, 1, psa->pages[0]->allocated_count); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - umock_c_reset_all_calls(); - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - /*PAGED_SPARSE_ARRAY_GET(T)*/ /* Tests_SRS_PAGED_SPARSE_ARRAY_88_036: [ PAGED_SPARSE_ARRAY_GET(T) shall compute the page index as index / page_size. ]*/ @@ -1435,219 +786,6 @@ TEST_FUNCTION(PAGED_SPARSE_ARRAY_GET_fails_when_element_not_allocated) PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); } -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_063: [ If thread_safe is true, PAGED_SPARSE_ARRAY_GET(T) shall acquire the lock in shared mode. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_064: [ If thread_safe is true, PAGED_SPARSE_ARRAY_GET(T) shall check whether the element at index is allocated using interlocked operations. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_065: [ If thread_safe is true, PAGED_SPARSE_ARRAY_GET(T) shall release the shared lock before returning. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_039: [ PAGED_SPARSE_ARRAY_GET(T) shall store in item a pointer to the element at index and return PAGED_SPARSE_ARRAY_GET_OK. ]*/ -TEST_FUNCTION(PAGED_SPARSE_ARRAY_GET_with_thread_safe_succeeds_under_the_shared_lock) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); - uint32_t* allocated_ptr = test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); - *allocated_ptr = TEST_ITEM_VALUE; - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); - - //act - uint32_t* item = NULL; - PAGED_SPARSE_ARRAY_GET_RESULT result = PAGED_SPARSE_ARRAY_GET(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &item); - - //assert - ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_OK, result); - ASSERT_ARE_EQUAL(void_ptr, allocated_ptr, item); - ASSERT_ARE_EQUAL(uint32_t, TEST_ITEM_VALUE, *item); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_037: [ If the page is not allocated, PAGED_SPARSE_ARRAY_GET(T) shall return PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_065: [ If thread_safe is true, PAGED_SPARSE_ARRAY_GET(T) shall release the shared lock before returning. ]*/ -TEST_FUNCTION(PAGED_SPARSE_ARRAY_GET_with_thread_safe_and_page_not_allocated_returns_NOT_ALLOCATED) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); - - //act - uint32_t* item = NULL; - PAGED_SPARSE_ARRAY_GET_RESULT result = PAGED_SPARSE_ARRAY_GET(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &item); - - //assert - ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED, result); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_038: [ If the element at index is not allocated, PAGED_SPARSE_ARRAY_GET(T) shall return PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_064: [ If thread_safe is true, PAGED_SPARSE_ARRAY_GET(T) shall check whether the element at index is allocated using interlocked operations. ]*/ -TEST_FUNCTION(PAGED_SPARSE_ARRAY_GET_with_thread_safe_and_element_not_allocated_returns_NOT_ALLOCATED) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, true, false, NULL); - (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_shared(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_release_shared(TEST_PSA_LOCK(psa))); - - //act - uint32_t* item = NULL; - PAGED_SPARSE_ARRAY_GET_RESULT result = PAGED_SPARSE_ARRAY_GET(uint32_t)(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE, &item); - - //assert - ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED, result); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/*PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T)*/ - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_066: [ If paged_sparse_array is NULL, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall fail and return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS. ]*/ -TEST_FUNCTION(when_paged_sparse_array_is_NULL_then_PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_fails) -{ - //arrange - - //act - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(uint32_t)(NULL, TEST_INDEX_IN_FIRST_PAGE); - - //assert - ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS, result); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_067: [ If index is greater than or equal to max_size, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall fail and return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS. ]*/ -TEST_FUNCTION(when_index_is_max_size_then_PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_fails) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, false, false, NULL); - - //act - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(uint32_t)(psa, TEST_MAX_SIZE); - - //assert - ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_INVALID_ARGS, result); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_069: [ PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall compute the page index as index / page_size. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_070: [ If the page is not allocated, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_ALLOCATED. ]*/ -TEST_FUNCTION(when_page_is_not_allocated_then_PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_returns_NOT_ALLOCATED) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(false, false, false, NULL); - - //act - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); - - //assert - ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_ALLOCATED, result); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_071: [ If the allocated count of the page is not 0, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY. ]*/ -TEST_FUNCTION(when_page_is_not_empty_then_PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_returns_NOT_EMPTY) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(false, false, false, NULL); - (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); - - //act - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(uint32_t)(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE); - - //assert - ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY, result); - ASSERT_IS_NOT_NULL(psa->pages[0]); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_069: [ PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall compute the page index as index / page_size. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_072: [ Otherwise, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall free the page, set the page pointer to NULL and return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_FREED. ]*/ -TEST_FUNCTION(when_page_is_empty_then_PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_frees_it) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(false, false, false, NULL); - (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); - PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); - ASSERT_IS_NOT_NULL(psa->pages[0]); - umock_c_reset_all_calls(); - STRICT_EXPECTED_CALL(free(IGNORED_ARG)); // page - - //act - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(uint32_t)(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE); - - //assert - ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_FREED, result); - ASSERT_IS_NULL(psa->pages[0]); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_068: [ If thread_safe is true, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall acquire the lock in exclusive mode. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_072: [ Otherwise, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall free the page, set the page pointer to NULL and return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_FREED. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_073: [ If thread_safe is true, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall release the exclusive lock before returning. ]*/ -TEST_FUNCTION(when_page_is_empty_then_PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_with_thread_safe_frees_it_under_the_exclusive_lock) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, false, false, NULL); - (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); - PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); - ASSERT_IS_NOT_NULL(psa->pages[0]); - umock_c_reset_all_calls(); - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(free(IGNORED_ARG)); // page - STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PSA_LOCK(psa))); - - //act - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); - - //assert - ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_FREED, result); - ASSERT_IS_NULL(psa->pages[0]); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_068: [ If thread_safe is true, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall acquire the lock in exclusive mode. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_071: [ If the allocated count of the page is not 0, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall return PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY. ]*/ -/* Tests_SRS_PAGED_SPARSE_ARRAY_88_073: [ If thread_safe is true, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(T) shall release the exclusive lock before returning. ]*/ -TEST_FUNCTION(when_page_is_not_empty_then_PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_with_thread_safe_returns_NOT_EMPTY) -{ - //arrange - PAGED_SPARSE_ARRAY(uint32_t) psa = test_create_with_options(true, false, false, NULL); - (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); - STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PSA_LOCK(psa))); - STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PSA_LOCK(psa))); - - //act - PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT result = PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); - - //assert - ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_RESULT, PAGED_SPARSE_ARRAY_FREE_PAGE_IF_EMPTY_NOT_EMPTY, result); - ASSERT_IS_NOT_NULL(psa->pages[0]); - ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); - - //clean - PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); -} - /* PAGED_SPARSE_ARRAY_CREATE */ /*Tests_SRS_PAGED_SPARSE_ARRAY_88_008: [ PAGED_SPARSE_ARRAY_CREATE(T) shall succeed and return a non-NULL value. ]*/ diff --git a/tests/paged_sparse_array_ut/paged_sparse_array_ut_pch.h b/tests/paged_sparse_array_ut/paged_sparse_array_ut_pch.h index 7d89adc1..81b9eb8a 100644 --- a/tests/paged_sparse_array_ut/paged_sparse_array_ut_pch.h +++ b/tests/paged_sparse_array_ut/paged_sparse_array_ut_pch.h @@ -22,11 +22,9 @@ #include "umock_c/umock_c_ENABLE_MOCKS.h" // ============================== ENABLE_MOCKS #include "c_pal/gballoc_hl.h" #include "c_pal/gballoc_hl_redirect.h" -#include "c_pal/srw_lock_ll.h" #include "umock_c/umock_c_DISABLE_MOCKS.h" // ============================== DISABLE_MOCKS #include "real_gballoc_hl.h" -#include "real_srw_lock_ll.h" #include "c_pal/thandle.h" #include "c_util/paged_sparse_array.h" diff --git a/tests/thread_safe_paged_sparse_array_int/CMakeLists.txt b/tests/thread_safe_paged_sparse_array_int/CMakeLists.txt new file mode 100644 index 00000000..2abefce6 --- /dev/null +++ b/tests/thread_safe_paged_sparse_array_int/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright (c) Microsoft. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for full license information. + +set(theseTestsName thread_safe_paged_sparse_array_int) + +set(${theseTestsName}_test_files + ${theseTestsName}.c +) + +set(${theseTestsName}_c_files +) + +set(${theseTestsName}_h_files +) + +build_test_artifacts(${theseTestsName} "tests/c_util" ADDITIONAL_LIBS c_pal c_util) diff --git a/tests/thread_safe_paged_sparse_array_int/thread_safe_paged_sparse_array_int.c b/tests/thread_safe_paged_sparse_array_int/thread_safe_paged_sparse_array_int.c new file mode 100644 index 00000000..93f446ad --- /dev/null +++ b/tests/thread_safe_paged_sparse_array_int/thread_safe_paged_sparse_array_int.c @@ -0,0 +1,633 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include +#include +#include + +#include "macro_utils/macro_utils.h" +#include "testrunnerswitcher.h" + +#include "c_pal/gballoc_hl.h" +#include "c_pal/gballoc_hl_redirect.h" + +#include "c_pal/interlocked.h" +#include "c_pal/thandle.h" +#include "c_pal/threadapi.h" + +#include "c_util/thread_safe_paged_sparse_array.h" + +typedef struct TEST_ELEMENT_TAG +{ + int64_t value; +} TEST_ELEMENT; + + +/* + * THREAD_SAFE_PAGED_SPARSE_ARRAY(TEST_ELEMENT) uses the default options (AUTO_FREE_EMPTY_PAGES, DO_NOT_ZERO_NEW_PAGES), + * THREAD_SAFE_PAGED_SPARSE_ARRAY(KEEP_EMPTY_PAGES_TEST_ELEMENT) keeps the empty pages until FREE_EMPTY_PAGES is called + */ +THREAD_SAFE_PAGED_SPARSE_ARRAY_DEFINE_STRUCT_TYPE(TEST_ELEMENT); +THANDLE_TYPE_DECLARE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(TEST_ELEMENT)); +THANDLE_TYPE_DEFINE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(TEST_ELEMENT)); + +THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DECLARE(TEST_ELEMENT); +THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE(TEST_ELEMENT); + +typedef TEST_ELEMENT KEEP_EMPTY_PAGES_TEST_ELEMENT; + +THREAD_SAFE_PAGED_SPARSE_ARRAY_DEFINE_STRUCT_TYPE(KEEP_EMPTY_PAGES_TEST_ELEMENT); +THANDLE_TYPE_DECLARE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(KEEP_EMPTY_PAGES_TEST_ELEMENT)); +THANDLE_TYPE_DEFINE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(KEEP_EMPTY_PAGES_TEST_ELEMENT)); + +THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DECLARE(KEEP_EMPTY_PAGES_TEST_ELEMENT); +THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE_WITH_OPTIONS(KEEP_EMPTY_PAGES_TEST_ELEMENT, KEEP_EMPTY_PAGES, DO_NOT_ZERO_NEW_PAGES); + +/* + * RING_ELEMENT - element type used by the producer/consumer ring buffer test, with the options a ring buffer uses: + * thread-safe, empty pages are kept until FREE_EMPTY_PAGES is called and new pages are zeroed + */ +typedef struct RING_ELEMENT_TAG +{ + volatile_atomic int64_t published_sequence_plus_one; +} RING_ELEMENT; + +THREAD_SAFE_PAGED_SPARSE_ARRAY_DEFINE_STRUCT_TYPE(RING_ELEMENT); +THANDLE_TYPE_DECLARE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(RING_ELEMENT)); +THANDLE_TYPE_DEFINE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(RING_ELEMENT)); + +THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DECLARE(RING_ELEMENT); +THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE_WITH_OPTIONS(RING_ELEMENT, KEEP_EMPTY_PAGES, ZERO_NEW_PAGES); + +TEST_DEFINE_ENUM_TYPE(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_RESULT_VALUES) +TEST_DEFINE_ENUM_TYPE(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_RESULT_VALUES) +TEST_DEFINE_ENUM_TYPE(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT_VALUES) + +/* + * Multi-threaded tests for THREAD_SAFE_PAGED_SPARSE_ARRAY. + * Asserts cannot be used from worker threads, so worker threads count failures and the test thread asserts on the count. + */ +#define CONCURRENT_THREAD_COUNT 8 +#define CONCURRENT_OPERATIONS_PER_THREAD 20000 +#define CONCURRENT_MAX_SIZE 256 +#define CONCURRENT_PAGE_SIZE 16 +#define CONCURRENT_MAX_HELD_ELEMENTS_PER_THREAD 4 + +static volatile_atomic int64_t g_dispose_count; + +static void count_dispose(TEST_ELEMENT* item) +{ + (void)item; + (void)interlocked_increment_64(&g_dispose_count); +} + +static uint64_t test_next_random(uint64_t* state) +{ + /* 64-bit LCG, each thread has its own state so no locking is needed */ + *state = *state * 6364136223846793005ULL + 1442695040888963407ULL; + return *state >> 33; +} + +/*the chaos test runs the same threads on arrays of different types (with different options) through these functions*/ +typedef struct CONCURRENT_TEST_ARRAY_FUNCTIONS_TAG +{ + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT (*allocate)(const void* psa, uint64_t index, TEST_ELEMENT** element); + void (*release)(const void* psa, uint64_t index); + PAGED_SPARSE_ARRAY_GET_RESULT (*get)(const void* psa, uint64_t index, TEST_ELEMENT** element); + THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT (*free_empty_pages)(const void* psa, uint64_t start_index, uint64_t end_index); +} CONCURRENT_TEST_ARRAY_FUNCTIONS; + +#define DEFINE_CONCURRENT_TEST_ARRAY_FUNCTIONS(T) \ +static PAGED_SPARSE_ARRAY_ALLOCATE_RESULT MU_C2(concurrent_allocate_, T)(const void* psa, uint64_t index, TEST_ELEMENT** element) \ +{ \ + return THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)(psa, index, element); \ +} \ +static void MU_C2(concurrent_release_, T)(const void* psa, uint64_t index) \ +{ \ + THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T)(psa, index); \ +} \ +static PAGED_SPARSE_ARRAY_GET_RESULT MU_C2(concurrent_get_, T)(const void* psa, uint64_t index, TEST_ELEMENT** element) \ +{ \ + return THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T)(psa, index, element); \ +} \ +static THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT MU_C2(concurrent_free_empty_pages_, T)(const void* psa, uint64_t start_index, uint64_t end_index) \ +{ \ + return THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T)(psa, start_index, end_index); \ +} \ +static const CONCURRENT_TEST_ARRAY_FUNCTIONS MU_C2(concurrent_functions_, T) = \ +{ \ + MU_C2(concurrent_allocate_, T), \ + MU_C2(concurrent_release_, T), \ + MU_C2(concurrent_get_, T), \ + MU_C2(concurrent_free_empty_pages_, T) \ +}; + +DEFINE_CONCURRENT_TEST_ARRAY_FUNCTIONS(TEST_ELEMENT) +DEFINE_CONCURRENT_TEST_ARRAY_FUNCTIONS(KEEP_EMPTY_PAGES_TEST_ELEMENT) + +typedef struct CONCURRENT_TEST_CONTEXT_TAG +{ + const void* psa; + const CONCURRENT_TEST_ARRAY_FUNCTIONS* functions; + bool call_free_empty_pages; + volatile_atomic int32_t owners[CONCURRENT_MAX_SIZE]; + volatile_atomic int64_t successful_allocations; + volatile_atomic int32_t failures; +} CONCURRENT_TEST_CONTEXT; + +typedef struct CONCURRENT_THREAD_CONTEXT_TAG +{ + CONCURRENT_TEST_CONTEXT* test_context; + int32_t thread_id; +} CONCURRENT_THREAD_CONTEXT; + +typedef struct HELD_ELEMENT_TAG +{ + uint64_t index; + TEST_ELEMENT* element; + int64_t expected_value; +} HELD_ELEMENT; + +static void concurrent_record_failure(CONCURRENT_TEST_CONTEXT* test_context) +{ + (void)interlocked_increment(&test_context->failures); +} + +static void concurrent_release_held_element(CONCURRENT_TEST_CONTEXT* test_context, int32_t thread_id, const HELD_ELEMENT* held) +{ + /* the element is owned by this thread, so its value must not have been changed and its page must not have been freed */ + if (held->element->value != held->expected_value) + { + LogError("thread_id=%" PRId32 ": held element value changed, index=%" PRIu64 ", page_index=%" PRIu64 ", element=%p, value=%" PRId64 ", expected_value=%" PRId64 "", + thread_id, held->index, held->index / CONCURRENT_PAGE_SIZE, (void*)held->element, held->element->value, held->expected_value); + concurrent_record_failure(test_context); + } + else + { + TEST_ELEMENT* element_from_get = NULL; + PAGED_SPARSE_ARRAY_GET_RESULT get_result = test_context->functions->get(test_context->psa, held->index, &element_from_get); + if ( + (get_result != PAGED_SPARSE_ARRAY_GET_OK) || + (element_from_get != held->element) + ) + { + LogError("thread_id=%" PRId32 ": GET of a held element did not return the allocated pointer, index=%" PRIu64 ", page_index=%" PRIu64 ", get_result=%" PRI_MU_ENUM ", element_from_get=%p, allocated element=%p", + thread_id, held->index, held->index / CONCURRENT_PAGE_SIZE, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_GET_RESULT, get_result), (void*)element_from_get, (void*)held->element); + concurrent_record_failure(test_context); + } + else + { + /* do nothing, the element is as expected */ + } + } + + int32_t owner = interlocked_exchange(&test_context->owners[held->index], 0); + if (owner != thread_id) + { + LogError("thread_id=%" PRId32 ": element owner changed while held, index=%" PRIu64 ", page_index=%" PRIu64 ", owner=%" PRId32 "", + thread_id, held->index, held->index / CONCURRENT_PAGE_SIZE, owner); + concurrent_record_failure(test_context); + } + else + { + /* do nothing, ownership was exclusive */ + } + + test_context->functions->release(test_context->psa, held->index); +} + +static int concurrent_chaos_thread(void* arg) +{ + CONCURRENT_THREAD_CONTEXT* thread_context = arg; + CONCURRENT_TEST_CONTEXT* test_context = thread_context->test_context; + int32_t thread_id = thread_context->thread_id; + uint64_t random_state = (uint64_t)thread_id; + HELD_ELEMENT held[CONCURRENT_MAX_HELD_ELEMENTS_PER_THREAD]; + uint32_t held_count = 0; + + for (uint32_t operation = 0; operation < CONCURRENT_OPERATIONS_PER_THREAD; operation++) + { + uint64_t random_value = test_next_random(&random_state); + if ( + (held_count < CONCURRENT_MAX_HELD_ELEMENTS_PER_THREAD) && + ((held_count == 0) || ((random_value % 2) == 0)) + ) + { + uint64_t index = test_next_random(&random_state) % CONCURRENT_MAX_SIZE; + TEST_ELEMENT* element; + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT allocate_result = test_context->functions->allocate(test_context->psa, index, &element); + if (allocate_result == PAGED_SPARSE_ARRAY_ALLOCATE_ALREADY_ALLOCATED) + { + /* do nothing, the element is owned by another thread (or by this one) */ + } + else if (allocate_result != PAGED_SPARSE_ARRAY_ALLOCATE_OK) + { + LogError("thread_id=%" PRId32 ": ALLOCATE failed, operation=%" PRIu32 ", index=%" PRIu64 ", page_index=%" PRIu64 ", held_count=%" PRIu32 ", allocate_result=%" PRI_MU_ENUM "", + thread_id, operation, index, index / CONCURRENT_PAGE_SIZE, held_count, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, allocate_result)); + concurrent_record_failure(test_context); + } + else + { + (void)interlocked_increment_64(&test_context->successful_allocations); + int32_t previous_owner = interlocked_compare_exchange(&test_context->owners[index], thread_id, 0); + if (previous_owner != 0) + { + LogError("thread_id=%" PRId32 ": ALLOCATE succeeded for an element owned by another thread, operation=%" PRIu32 ", index=%" PRIu64 ", page_index=%" PRIu64 ", previous_owner=%" PRId32 ", allocate_result=%" PRI_MU_ENUM "", + thread_id, operation, index, index / CONCURRENT_PAGE_SIZE, previous_owner, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, allocate_result)); + concurrent_record_failure(test_context); + } + else + { + /* do nothing, this thread is now the only owner */ + } + held[held_count].index = index; + held[held_count].element = element; + held[held_count].expected_value = ((int64_t)thread_id << 32) | operation; + element->value = held[held_count].expected_value; + held_count++; + } + } + else + { + uint32_t held_position = (uint32_t)(random_value % held_count); + concurrent_release_held_element(test_context, thread_id, &held[held_position]); + held_count--; + held[held_position] = held[held_count]; + } + + /* elements of other threads are not read, only the result of GET is checked */ + uint64_t other_index = test_next_random(&random_state) % CONCURRENT_MAX_SIZE; + TEST_ELEMENT* other_element; + PAGED_SPARSE_ARRAY_GET_RESULT get_result = test_context->functions->get(test_context->psa, other_index, &other_element); + if ( + (get_result != PAGED_SPARSE_ARRAY_GET_OK) && + (get_result != PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED) + ) + { + LogError("thread_id=%" PRId32 ": GET returned an unexpected result, operation=%" PRIu32 ", index=%" PRIu64 ", page_index=%" PRIu64 ", get_result=%" PRI_MU_ENUM "", + thread_id, operation, other_index, other_index / CONCURRENT_PAGE_SIZE, MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_GET_RESULT, get_result)); + concurrent_record_failure(test_context); + } + else + { + /* do nothing, both results are possible */ + } + + if (!test_context->call_free_empty_pages) + { + /* do nothing */ + } + else + { + /* a range of up to 3 pages, which can contain pages that are not allocated, empty, used by other threads or with allocated elements */ + uint64_t start_index = test_next_random(&random_state) % CONCURRENT_MAX_SIZE; + uint64_t end_index = start_index + test_next_random(&random_state) % (3 * CONCURRENT_PAGE_SIZE); + if (end_index >= CONCURRENT_MAX_SIZE) + { + end_index = CONCURRENT_MAX_SIZE - 1; + } + else + { + /* do nothing, the range is within the array */ + } + THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT free_result = test_context->functions->free_empty_pages(test_context->psa, start_index, end_index); + if (free_result != THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_OK) + { + LogError("thread_id=%" PRId32 ": FREE_EMPTY_PAGES failed, operation=%" PRIu32 ", start_index=%" PRIu64 ", end_index=%" PRIu64 ", first page_index=%" PRIu64 ", last page_index=%" PRIu64 ", free_result=%" PRI_MU_ENUM "", + thread_id, operation, start_index, end_index, start_index / CONCURRENT_PAGE_SIZE, end_index / CONCURRENT_PAGE_SIZE, MU_ENUM_VALUE(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT, free_result)); + concurrent_record_failure(test_context); + } + else + { + /* do nothing, the pages of the range that were empty and not used were freed */ + } + } + } + + while (held_count > 0) + { + held_count--; + concurrent_release_held_element(test_context, thread_id, &held[held_count]); + } + + return 0; +} + +/*once all the elements are released and all the calls returned, no page is allocated and no page is used*/ +static void assert_all_pages_are_freed_and_not_used(const THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_SLOT* page_slots, uint64_t page_count) +{ + for (uint64_t page_index = 0; page_index < page_count; page_index++) + { + ASSERT_IS_NULL(page_slots[page_index].page, "page_index=%" PRIu64 "", page_index); + ASSERT_ARE_EQUAL(int64_t, 0, interlocked_add_64((volatile_atomic int64_t*)&page_slots[page_index].use_count, 0), "page_index=%" PRIu64 "", page_index); + } +} + +static void run_concurrent_chaos_test(const void* psa, const CONCURRENT_TEST_ARRAY_FUNCTIONS* functions, bool call_free_empty_pages) +{ + //arrange + CONCURRENT_TEST_CONTEXT* test_context = malloc(sizeof(CONCURRENT_TEST_CONTEXT)); + ASSERT_IS_NOT_NULL(test_context); + test_context->psa = psa; + test_context->functions = functions; + test_context->call_free_empty_pages = call_free_empty_pages; + for (uint32_t i = 0; i < CONCURRENT_MAX_SIZE; i++) + { + (void)interlocked_exchange(&test_context->owners[i], 0); + } + (void)interlocked_exchange_64(&test_context->successful_allocations, 0); + (void)interlocked_exchange(&test_context->failures, 0); + (void)interlocked_exchange_64(&g_dispose_count, 0); + + CONCURRENT_THREAD_CONTEXT thread_contexts[CONCURRENT_THREAD_COUNT]; + THREAD_HANDLE threads[CONCURRENT_THREAD_COUNT]; + int32_t threads_created = 0; + bool thread_api_failed = false; + + //act + /* no ASSERT until all the started threads are joined, since they use stack variables of this function */ + for (int32_t i = 0; i < CONCURRENT_THREAD_COUNT; i++) + { + thread_contexts[i].test_context = test_context; + thread_contexts[i].thread_id = i + 1; /*0 means "no owner"*/ + if (ThreadAPI_Create(&threads[i], concurrent_chaos_thread, &thread_contexts[i]) != THREADAPI_OK) + { + thread_api_failed = true; + break; + } + else + { + threads_created++; + } + } + for (int32_t i = 0; i < threads_created; i++) + { + int thread_result; + if ( + (ThreadAPI_Join(threads[i], &thread_result) != THREADAPI_OK) || + (thread_result != 0) + ) + { + thread_api_failed = true; + } + else + { + /* do nothing */ + } + } + + //assert + ASSERT_IS_FALSE(thread_api_failed); + ASSERT_ARE_EQUAL(int32_t, 0, interlocked_add(&test_context->failures, 0)); + ASSERT_IS_TRUE(interlocked_add_64(&test_context->successful_allocations, 0) > 0); + ASSERT_ARE_EQUAL(int64_t, interlocked_add_64(&test_context->successful_allocations, 0), interlocked_add_64(&g_dispose_count, 0)); + + //cleanup + free(test_context); +} + +/* + * Producer/consumer ring buffer over a thread-safe array: + * producers allocate sequence % RING_MAX_SIZE (at most RING_MAX_SIZE sequences ahead of the tail), + * a single consumer releases the elements in sequence order and asks the array to free a page when the tail leaves it. + * A page can then still hold elements of the next lap (allocated by producers), in which case it is not freed. + */ +#define RING_PRODUCER_COUNT 4 +#define RING_TOTAL_SEQUENCES 200000 +#define RING_MAX_SIZE 2048 +#define RING_PAGE_SIZE 256 + +typedef struct RING_TEST_CONTEXT_TAG +{ + const THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(RING_ELEMENT)* psa; + volatile_atomic int64_t next_sequence; + volatile_atomic int64_t tail; + volatile_atomic int32_t failures; /*once not 0, the producers and the consumer stop instead of waiting for each other*/ +} RING_TEST_CONTEXT; + +static bool ring_has_failed(RING_TEST_CONTEXT* test_context) +{ + return interlocked_add(&test_context->failures, 0) != 0; +} + +static int ring_producer_thread(void* arg) +{ + RING_TEST_CONTEXT* test_context = arg; + int64_t sequence; + while ( + ((sequence = interlocked_increment_64(&test_context->next_sequence) - 1) < RING_TOTAL_SEQUENCES) && + (!ring_has_failed(test_context)) + ) + { + /* wait until the sequence is within the window */ + while ( + (sequence - interlocked_add_64(&test_context->tail, 0) >= RING_MAX_SIZE) && + (!ring_has_failed(test_context)) + ) + { + ThreadAPI_Sleep(0); + } + + if (ring_has_failed(test_context)) + { + /* do nothing, the loop ends */ + } + else + { + RING_ELEMENT* element; + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT allocate_result = THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(RING_ELEMENT)(test_context->psa, (uint64_t)sequence % RING_MAX_SIZE, &element); + if (allocate_result != PAGED_SPARSE_ARRAY_ALLOCATE_OK) + { + uint64_t index = (uint64_t)sequence % RING_MAX_SIZE; + LogError("producer ALLOCATE failed, sequence=%" PRId64 ", index=%" PRIu64 ", page_index=%" PRIu64 ", tail=%" PRId64 ", allocate_result=%" PRI_MU_ENUM "", + sequence, index, index / RING_PAGE_SIZE, interlocked_add_64(&test_context->tail, 0), MU_ENUM_VALUE(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, allocate_result)); + (void)interlocked_increment(&test_context->failures); + } + else + { + (void)interlocked_exchange_64(&element->published_sequence_plus_one, sequence + 1); + } + } + } + return 0; +} + +static void run_ring_buffer_test(void) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(RING_ELEMENT) psa = THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(RING_ELEMENT)(RING_MAX_SIZE, RING_PAGE_SIZE, NULL); + ASSERT_IS_NOT_NULL(psa); + + RING_TEST_CONTEXT test_context; + test_context.psa = psa; + (void)interlocked_exchange_64(&test_context.next_sequence, 0); + (void)interlocked_exchange_64(&test_context.tail, 0); + (void)interlocked_exchange(&test_context.failures, 0); + + THREAD_HANDLE producers[RING_PRODUCER_COUNT]; + uint32_t producers_created = 0; + bool thread_api_failed = false; + uint32_t free_requests = 0; + + //act + /* no ASSERT until all the started producers are joined, since they use stack variables of this function */ + for (uint32_t i = 0; i < RING_PRODUCER_COUNT; i++) + { + if (ThreadAPI_Create(&producers[i], ring_producer_thread, &test_context) != THREADAPI_OK) + { + thread_api_failed = true; + (void)interlocked_increment(&test_context.failures); + break; + } + else + { + producers_created++; + } + } + + for (int64_t tail = 0; (tail < RING_TOTAL_SEQUENCES) && (!ring_has_failed(&test_context)); tail++) + { + uint64_t index = (uint64_t)tail % RING_MAX_SIZE; + RING_ELEMENT* element; + + /* wait until a producer allocated the element and published this sequence in it */ + while ( + (!ring_has_failed(&test_context)) && + ( + (THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(RING_ELEMENT)(psa, index, &element) != PAGED_SPARSE_ARRAY_GET_OK) || + (interlocked_add_64(&element->published_sequence_plus_one, 0) != tail + 1) + ) + ) + { + ThreadAPI_Sleep(0); + } + + if (ring_has_failed(&test_context)) + { + /* do nothing, the loop ends */ + } + else + { + THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(RING_ELEMENT)(psa, index); + (void)interlocked_exchange_64(&test_context.tail, tail + 1); + + if ( + ((index + 1) % RING_PAGE_SIZE != 0) && + (index + 1 != RING_MAX_SIZE) + ) + { + /* do nothing, the tail did not leave the page */ + } + else + { + THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT free_result = THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(RING_ELEMENT)(psa, index, index); + if (free_result != THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_OK) + { + LogError("consumer FREE_EMPTY_PAGES failed, tail=%" PRId64 ", index=%" PRIu64 ", page_index=%" PRIu64 ", free_result=%" PRI_MU_ENUM "", + tail, index, index / RING_PAGE_SIZE, MU_ENUM_VALUE(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT, free_result)); + (void)interlocked_increment(&test_context.failures); + } + else + { + /* the page is freed, unless producers already allocated elements of the next lap in it or are using it */ + free_requests++; + } + } + } + } + + for (uint32_t i = 0; i < producers_created; i++) + { + int thread_result; + if ( + (ThreadAPI_Join(producers[i], &thread_result) != THREADAPI_OK) || + (thread_result != 0) + ) + { + thread_api_failed = true; + } + else + { + /* do nothing */ + } + } + + //assert + LogInfo("ring buffer test: producers=%" PRIu32 "/%" PRIu32 ", sequences=%" PRIu32 ", consumed=%" PRId64 ", max_size=%" PRIu32 ", page_size=%" PRIu32 ", free_requests=%" PRIu32 ", failures=%" PRId32 ", thread_api_failed=%" PRI_BOOL "", + producers_created, (uint32_t)RING_PRODUCER_COUNT, (uint32_t)RING_TOTAL_SEQUENCES, interlocked_add_64(&test_context.tail, 0), (uint32_t)RING_MAX_SIZE, (uint32_t)RING_PAGE_SIZE, + free_requests, interlocked_add(&test_context.failures, 0), MU_BOOL_VALUE(thread_api_failed)); + ASSERT_IS_FALSE(thread_api_failed); + ASSERT_ARE_EQUAL(int32_t, 0, interlocked_add(&test_context.failures, 0)); + ASSERT_ARE_EQUAL(uint32_t, RING_TOTAL_SEQUENCES / RING_PAGE_SIZE, free_requests); + + /* every element was released, so every page that is still allocated is empty */ + ASSERT_ARE_EQUAL(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_OK, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(RING_ELEMENT)(psa, 0, RING_MAX_SIZE - 1)); + assert_all_pages_are_freed_and_not_used(psa->page_slots, psa->page_count); + + //cleanup + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(RING_ELEMENT)(&psa, NULL); +} + +BEGIN_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) + +TEST_SUITE_INITIALIZE(suite_init) +{ +} + +TEST_SUITE_CLEANUP(suite_cleanup) +{ +} + +TEST_FUNCTION_INITIALIZE(method_init) +{ +} + +TEST_FUNCTION_CLEANUP(method_cleanup) +{ +} + +/* + * Tests for THREAD_SAFE_PAGED_SPARSE_ARRAY + */ + +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_chaos_with_auto_free_empty_pages) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(TEST_ELEMENT) psa = THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(TEST_ELEMENT)(CONCURRENT_MAX_SIZE, CONCURRENT_PAGE_SIZE, count_dispose); + ASSERT_IS_NOT_NULL(psa); + + //act + run_concurrent_chaos_test(psa, &concurrent_functions_TEST_ELEMENT, false); + + //assert + /* every page was freed when its last use ended */ + assert_all_pages_are_freed_and_not_used(psa->page_slots, psa->page_count); + + //cleanup + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(TEST_ELEMENT)(&psa, NULL); +} + +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_chaos_with_free_empty_pages) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(KEEP_EMPTY_PAGES_TEST_ELEMENT) psa = THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(KEEP_EMPTY_PAGES_TEST_ELEMENT)(CONCURRENT_MAX_SIZE, CONCURRENT_PAGE_SIZE, count_dispose); + ASSERT_IS_NOT_NULL(psa); + + //act + run_concurrent_chaos_test(psa, &concurrent_functions_KEEP_EMPTY_PAGES_TEST_ELEMENT, true); + + //assert + /* every element was released, so every page that is still allocated is empty */ + ASSERT_ARE_EQUAL(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_OK, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(KEEP_EMPTY_PAGES_TEST_ELEMENT)(psa, 0, CONCURRENT_MAX_SIZE - 1)); + assert_all_pages_are_freed_and_not_used(psa->page_slots, psa->page_count); + + //cleanup + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(KEEP_EMPTY_PAGES_TEST_ELEMENT)(&psa, NULL); +} + +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_ring_buffer_with_free_empty_pages_when_tail_leaves_a_page) +{ + run_ring_buffer_test(); +} + +END_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) diff --git a/tests/thread_safe_paged_sparse_array_ut/CMakeLists.txt b/tests/thread_safe_paged_sparse_array_ut/CMakeLists.txt new file mode 100644 index 00000000..30862754 --- /dev/null +++ b/tests/thread_safe_paged_sparse_array_ut/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright (c) Microsoft. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for full license information. + +set(theseTestsName thread_safe_paged_sparse_array_ut) + +set(${theseTestsName}_test_files + ${theseTestsName}.c +) + +set(${theseTestsName}_c_files +) + +set(${theseTestsName}_h_files + ../../inc/c_util/thread_safe_paged_sparse_array.h +) + +build_test_artifacts(${theseTestsName} "tests/c_util" ADDITIONAL_LIBS c_pal c_pal_reals c_util c_util_reals + ENABLE_TEST_FILES_PRECOMPILED_HEADERS "${CMAKE_CURRENT_LIST_DIR}/thread_safe_paged_sparse_array_ut_pch.h" +) diff --git a/tests/thread_safe_paged_sparse_array_ut/thread_safe_paged_sparse_array_ut.c b/tests/thread_safe_paged_sparse_array_ut/thread_safe_paged_sparse_array_ut.c new file mode 100644 index 00000000..d397ebb3 --- /dev/null +++ b/tests/thread_safe_paged_sparse_array_ut/thread_safe_paged_sparse_array_ut.c @@ -0,0 +1,1565 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include "thread_safe_paged_sparse_array_ut_pch.h" + +/*THREAD_SAFE_PAGED_SPARSE_ARRAY with the default options (AUTO_FREE_EMPTY_PAGES, DO_NOT_ZERO_NEW_PAGES)*/ +THREAD_SAFE_PAGED_SPARSE_ARRAY_DEFINE_STRUCT_TYPE(uint32_t) +THANDLE_TYPE_DECLARE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(uint32_t)); +THANDLE_TYPE_DEFINE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(uint32_t)); + +THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DECLARE(uint32_t); +THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE(uint32_t); + +/*the arrays of KEEP_AND_ZERO_PAGES_UINT32 are defined with KEEP_EMPTY_PAGES and ZERO_NEW_PAGES*/ +typedef uint32_t KEEP_AND_ZERO_PAGES_UINT32; + +THREAD_SAFE_PAGED_SPARSE_ARRAY_DEFINE_STRUCT_TYPE(KEEP_AND_ZERO_PAGES_UINT32) +THANDLE_TYPE_DECLARE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(KEEP_AND_ZERO_PAGES_UINT32)); +THANDLE_TYPE_DEFINE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(KEEP_AND_ZERO_PAGES_UINT32)); + +THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DECLARE(KEEP_AND_ZERO_PAGES_UINT32); +THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPE_DEFINE_WITH_OPTIONS(KEEP_AND_ZERO_PAGES_UINT32, KEEP_EMPTY_PAGES, ZERO_NEW_PAGES); + +MU_DEFINE_ENUM_STRINGS(UMOCK_C_ERROR_CODE, UMOCK_C_ERROR_CODE_VALUES) + +TEST_DEFINE_ENUM_TYPE(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_RESULT_VALUES) +TEST_DEFINE_ENUM_TYPE(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_RESULT_VALUES) +TEST_DEFINE_ENUM_TYPE(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT_VALUES) + +#define TEST_MAX_SIZE 100 +#define TEST_PAGE_SIZE 16 +#define TEST_PAGE_COUNT 7 /*(TEST_MAX_SIZE + TEST_PAGE_SIZE - 1) / TEST_PAGE_SIZE*/ +#define TEST_EXACT_MULTIPLE_PAGE_COUNT 4 +#define TEST_INDEX_IN_FIRST_PAGE 5 +#define TEST_OTHER_INDEX_IN_FIRST_PAGE 10 +#define TEST_LAST_INDEX_IN_FIRST_PAGE 15 +#define TEST_INDEX_IN_SECOND_PAGE 20 +#define TEST_INDEX_IN_THIRD_PAGE 40 +#define TEST_INDEX_IN_FOURTH_PAGE 50 +#define TEST_INDEX_IN_FIFTH_PAGE 70 +#define TEST_LAST_INDEX (TEST_MAX_SIZE - 1) +#define TEST_ITEM_VALUE 42 +#define TEST_GARBAGE_BYTE 0xFF +#define TEST_GARBAGE_ITEM_VALUE UINT32_MAX +#define TEST_PAGE_BITMAP_SIZE PAGED_SPARSE_ARRAY_BITMAP_SIZE(TEST_PAGE_SIZE) + +/*all the array types used in the tests have the same layout*/ +#define TEST_PAGE_SLOT(psa, index) ((THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_SLOT*)&(psa)->page_slots[(index) / TEST_PAGE_SIZE]) +#define TEST_PAGE_LOCK(psa, index) (&TEST_PAGE_SLOT(psa, index)->lock) +#define TEST_PAGE(psa, index) ((uint32_t*)TEST_PAGE_SLOT(psa, index)->page) +#define TEST_USE_COUNT(psa, index) (TEST_PAGE_SLOT(psa, index)->use_count) +#define TEST_IS_ALLOCATED_IN_BITMAP(psa, index) \ + (PAGED_SPARSE_ARRAY_IS_ALLOCATED(THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_BITMAP(TEST_PAGE(psa, index), TEST_PAGE_SIZE), (index) % TEST_PAGE_SIZE) != 0) + +static void on_umock_c_error(UMOCK_C_ERROR_CODE error_code) +{ + ASSERT_FAIL("umock_c reported error :%" PRI_MU_ENUM "", MU_ENUM_VALUE(UMOCK_C_ERROR_CODE, error_code)); +} + +static THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) test_create(THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_ITEM_DISPOSE_FUNC(uint32_t) item_dispose_func) +{ + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(uint32_t)(TEST_MAX_SIZE, TEST_PAGE_SIZE, item_dispose_func); + ASSERT_IS_NOT_NULL(psa); + umock_c_reset_all_calls(); + return psa; +} + +static uint32_t* test_allocate(THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa, uint64_t index) +{ + uint32_t* ptr = NULL; + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, index, &ptr)); + ASSERT_IS_NOT_NULL(ptr); + umock_c_reset_all_calls(); + return ptr; +} + +static THREAD_SAFE_PAGED_SPARSE_ARRAY(KEEP_AND_ZERO_PAGES_UINT32) test_create_keep_and_zero_pages(void) +{ + THREAD_SAFE_PAGED_SPARSE_ARRAY(KEEP_AND_ZERO_PAGES_UINT32) psa = THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(KEEP_AND_ZERO_PAGES_UINT32)(TEST_MAX_SIZE, TEST_PAGE_SIZE, NULL); + ASSERT_IS_NOT_NULL(psa); + umock_c_reset_all_calls(); + return psa; +} + +static uint32_t* test_allocate_keep_and_zero_pages(THREAD_SAFE_PAGED_SPARSE_ARRAY(KEEP_AND_ZERO_PAGES_UINT32) psa, uint64_t index) +{ + uint32_t* ptr = NULL; + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(KEEP_AND_ZERO_PAGES_UINT32)(psa, index, &ptr)); + ASSERT_IS_NOT_NULL(ptr); + umock_c_reset_all_calls(); + return ptr; +} + +/*returns an array whose page of index is allocated and empty (no element in it is allocated)*/ +static THREAD_SAFE_PAGED_SPARSE_ARRAY(KEEP_AND_ZERO_PAGES_UINT32) test_create_keep_and_zero_pages_with_empty_page(uint64_t index) +{ + THREAD_SAFE_PAGED_SPARSE_ARRAY(KEEP_AND_ZERO_PAGES_UINT32) psa = test_create_keep_and_zero_pages(); + (void)test_allocate_keep_and_zero_pages(psa, index); + THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(KEEP_AND_ZERO_PAGES_UINT32)(psa, index); + ASSERT_IS_NOT_NULL(TEST_PAGE(psa, index)); + ASSERT_ARE_EQUAL(int64_t, 0, TEST_USE_COUNT(psa, index)); + umock_c_reset_all_calls(); + return psa; +} + +static void* test_allocate_page_memory_filled_with_garbage(void) +{ + size_t page_memory_size = TEST_PAGE_SIZE * sizeof(uint32_t) + TEST_PAGE_BITMAP_SIZE; + void* page_memory = real_gballoc_hl_malloc_flex(TEST_PAGE_BITMAP_SIZE, TEST_PAGE_SIZE, sizeof(uint32_t)); + ASSERT_IS_NOT_NULL(page_memory); + (void)memset(page_memory, TEST_GARBAGE_BYTE, page_memory_size); + return page_memory; +} + +/*used by tests that simulate another thread calling into the array right before the lock of a page slot is acquired in exclusive mode*/ +typedef enum TEST_CONCURRENT_CALL_TAG +{ + TEST_CONCURRENT_CALL_NONE, + /*another thread allocates the element at index*/ + TEST_CONCURRENT_CALL_ALLOCATE, + /*another thread frees the page of index*/ + TEST_CONCURRENT_CALL_FREE_EMPTY_PAGES, + /*another thread begins to use the page of index (e.g. it is in a call to GET)*/ + TEST_CONCURRENT_CALL_BEGIN_PAGE_USE, + /*the thread that is freeing the page of index finishes: it sets the page pointer to NULL and removes THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING from the use count*/ + TEST_CONCURRENT_CALL_FINISH_FREEING_PAGE +} TEST_CONCURRENT_CALL; + +static struct +{ + TEST_CONCURRENT_CALL call; + const THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(uint32_t)* psa; + uint64_t index; + bool was_called; +} g_concurrent_call; + +static void hook_srw_lock_ll_acquire_exclusive_calls_concurrently(SRW_LOCK_LL* srw_lock_ll) +{ + if ((g_concurrent_call.call == TEST_CONCURRENT_CALL_NONE) || g_concurrent_call.was_called) + { + /* do nothing, the test does not simulate a concurrent call or the only one was already simulated */ + } + else + { + g_concurrent_call.was_called = true; + if (g_concurrent_call.call == TEST_CONCURRENT_CALL_ALLOCATE) + { + uint32_t* other_ptr; + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(g_concurrent_call.psa, g_concurrent_call.index, &other_ptr)); + } + else if (g_concurrent_call.call == TEST_CONCURRENT_CALL_FREE_EMPTY_PAGES) + { + ASSERT_ARE_EQUAL(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_OK, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(uint32_t)(g_concurrent_call.psa, g_concurrent_call.index, g_concurrent_call.index)); + } + else if (g_concurrent_call.call == TEST_CONCURRENT_CALL_BEGIN_PAGE_USE) + { + (void)interlocked_increment_64(&TEST_USE_COUNT(g_concurrent_call.psa, g_concurrent_call.index)); + } + else + { + (void)interlocked_exchange_pointer(&TEST_PAGE_SLOT(g_concurrent_call.psa, g_concurrent_call.index)->page, NULL); + (void)interlocked_add_64(&TEST_USE_COUNT(g_concurrent_call.psa, g_concurrent_call.index), -THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING); + } + } + real_srw_lock_ll_acquire_exclusive(srw_lock_ll); +} + +static void setup_concurrent_call_on_acquire_exclusive(TEST_CONCURRENT_CALL call, THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa, uint64_t index) +{ + g_concurrent_call.call = call; + g_concurrent_call.psa = psa; + g_concurrent_call.index = index; + g_concurrent_call.was_called = false; +} + +/*used by tests that check the state of the array when item_dispose_func is called, optionally simulating another caller that releases the same element at that moment*/ +static struct +{ + const THREAD_SAFE_PAGED_SPARSE_ARRAY_TYPEDEF_NAME(uint32_t)* psa; + uint64_t index; + bool release_concurrently; + bool was_called; + bool was_allocated_when_called; + int64_t use_count_when_called; +} g_dispose_observer; + +MOCK_FUNCTION_WITH_CODE(, void, test_item_dispose_func, uint32_t*, item) + if (g_dispose_observer.psa == NULL) + { + /* do nothing, the observer is not set up (e.g. the array is being disposed) */ + } + else + { + uint8_t* bitmap = THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_BITMAP(TEST_PAGE(g_dispose_observer.psa, g_dispose_observer.index), TEST_PAGE_SIZE); + g_dispose_observer.was_called = true; + g_dispose_observer.was_allocated_when_called = THREAD_SAFE_PAGED_SPARSE_ARRAY_IS_ALLOCATED(bitmap, g_dispose_observer.index % TEST_PAGE_SIZE); + g_dispose_observer.use_count_when_called = interlocked_add_64(&TEST_USE_COUNT(g_dispose_observer.psa, g_dispose_observer.index), 0); + if (g_dispose_observer.release_concurrently) + { + /* the other caller marks the element as not allocated and ends the use of the page by the element */ + ASSERT_IS_TRUE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TEST_AND_CLEAR_ALLOCATED(bitmap, g_dispose_observer.index % TEST_PAGE_SIZE)); + (void)interlocked_decrement_64(&TEST_USE_COUNT(g_dispose_observer.psa, g_dispose_observer.index)); + } + } +MOCK_FUNCTION_END() + +static void setup_dispose_observer(THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa, uint64_t index, bool release_concurrently) +{ + g_dispose_observer.psa = psa; + g_dispose_observer.index = index; + g_dispose_observer.release_concurrently = release_concurrently; + g_dispose_observer.was_called = false; + g_dispose_observer.was_allocated_when_called = false; + g_dispose_observer.use_count_when_called = -1; +} + +BEGIN_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) + +TEST_SUITE_INITIALIZE(suite_init) +{ + ASSERT_ARE_EQUAL(int, 0, real_gballoc_hl_init(NULL, NULL)); + + ASSERT_ARE_EQUAL(int, 0, umock_c_init(on_umock_c_error)); + + REGISTER_GBALLOC_HL_GLOBAL_MOCK_HOOK(); + REGISTER_SRW_LOCK_LL_GLOBAL_MOCK_HOOK(); + REGISTER_GLOBAL_MOCK_HOOK(srw_lock_ll_acquire_exclusive, hook_srw_lock_ll_acquire_exclusive_calls_concurrently); + + REGISTER_GLOBAL_MOCK_FAIL_RETURN(malloc_flex, NULL); + REGISTER_GLOBAL_MOCK_FAIL_RETURN(srw_lock_ll_init, MU_FAILURE); +} + +TEST_SUITE_CLEANUP(suite_cleanup) +{ + umock_c_deinit(); + + real_gballoc_hl_deinit(); +} + +TEST_FUNCTION_INITIALIZE(method_init) +{ + (void)memset(&g_concurrent_call, 0, sizeof(g_concurrent_call)); + (void)memset(&g_dispose_observer, 0, sizeof(g_dispose_observer)); + + umock_c_negative_tests_init(); + umock_c_reset_all_calls(); +} + +TEST_FUNCTION_CLEANUP(method_cleanup) +{ + umock_c_negative_tests_deinit(); +} + +/*THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T)*/ + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_001: [ If max_size is zero, THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall fail and return NULL. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE_with_max_size_0_fails) +{ + //arrange + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(uint32_t)(0, TEST_PAGE_SIZE, NULL); + + //assert + ASSERT_IS_NULL(psa); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_002: [ If page_size is zero, THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall fail and return NULL. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE_with_page_size_0_fails) +{ + //arrange + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(uint32_t)(TEST_MAX_SIZE, 0, NULL); + + //assert + ASSERT_IS_NULL(psa); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_003: [ If max_size + page_size - 1 would overflow, THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall fail and return NULL. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE_when_max_size_plus_page_size_would_overflow_fails) +{ + //arrange + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(uint32_t)(UINT64_MAX, TEST_PAGE_SIZE, NULL); + + //assert + ASSERT_IS_NULL(psa); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_004: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall compute the number of pages as (max_size + page_size - 1) / page_size. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_005: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall call THANDLE_MALLOC_FLEX to allocate memory for the paged sparse array with one page slot for each page. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_006: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall store max_size, page_size, and item_dispose_func in the structure. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_007: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall set the page pointer of each page slot to NULL and its use count to 0. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_008: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall call srw_lock_ll_init to initialize the lock of each page slot. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_010: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall succeed and return a non-NULL value. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE_succeeds) +{ + //arrange + SRW_LOCK_LL* initialized_locks[TEST_PAGE_COUNT]; + STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, TEST_PAGE_COUNT, sizeof(THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_SLOT))); + for (uint64_t i = 0; i < TEST_PAGE_COUNT; i++) + { + STRICT_EXPECTED_CALL(srw_lock_ll_init(IGNORED_ARG)) + .CaptureArgumentValue_srw_lock_ll(&initialized_locks[i]); + } + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(uint32_t)(TEST_MAX_SIZE, TEST_PAGE_SIZE, test_item_dispose_func); + + //assert + ASSERT_IS_NOT_NULL(psa); + ASSERT_ARE_EQUAL(uint64_t, TEST_MAX_SIZE, psa->max_size); + ASSERT_ARE_EQUAL(uint64_t, TEST_PAGE_SIZE, psa->page_size); + ASSERT_ARE_EQUAL(uint64_t, TEST_PAGE_COUNT, psa->page_count); + ASSERT_IS_TRUE(psa->item_dispose_func == test_item_dispose_func); + for (uint64_t i = 0; i < TEST_PAGE_COUNT; i++) + { + ASSERT_IS_NULL(TEST_PAGE(psa, i * TEST_PAGE_SIZE)); + ASSERT_ARE_EQUAL(int64_t, 0, TEST_USE_COUNT(psa, i * TEST_PAGE_SIZE)); + ASSERT_ARE_EQUAL(void_ptr, TEST_PAGE_LOCK(psa, i * TEST_PAGE_SIZE), initialized_locks[i]); + } + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_004: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall compute the number of pages as (max_size + page_size - 1) / page_size. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE_with_max_size_multiple_of_page_size_computes_the_page_count) +{ + //arrange + STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, TEST_EXACT_MULTIPLE_PAGE_COUNT, sizeof(THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_SLOT))); + for (uint64_t i = 0; i < TEST_EXACT_MULTIPLE_PAGE_COUNT; i++) + { + STRICT_EXPECTED_CALL(srw_lock_ll_init(IGNORED_ARG)); + } + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(uint32_t)(TEST_EXACT_MULTIPLE_PAGE_COUNT * TEST_PAGE_SIZE, TEST_PAGE_SIZE, NULL); + + //assert + ASSERT_IS_NOT_NULL(psa); + ASSERT_ARE_EQUAL(uint64_t, TEST_EXACT_MULTIPLE_PAGE_COUNT, psa->page_count); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_009: [ If srw_lock_ll_init fails, THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall call srw_lock_ll_deinit for the locks that were initialized, free the paged sparse array and return NULL. ]*/ +TEST_FUNCTION(when_srw_lock_ll_init_fails_for_the_third_page_slot_then_THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE_deinitializes_the_first_2_locks_and_fails) +{ + //arrange + SRW_LOCK_LL* first_lock; + SRW_LOCK_LL* second_lock; + STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, TEST_PAGE_COUNT, sizeof(THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_SLOT))); + STRICT_EXPECTED_CALL(srw_lock_ll_init(IGNORED_ARG)) + .CaptureArgumentValue_srw_lock_ll(&first_lock); + STRICT_EXPECTED_CALL(srw_lock_ll_init(IGNORED_ARG)) + .CaptureArgumentValue_srw_lock_ll(&second_lock); + STRICT_EXPECTED_CALL(srw_lock_ll_init(IGNORED_ARG)) + .SetReturn(MU_FAILURE); + STRICT_EXPECTED_CALL(srw_lock_ll_deinit(IGNORED_ARG)) + .ValidateArgumentValue_srw_lock_ll(&first_lock); + STRICT_EXPECTED_CALL(srw_lock_ll_deinit(IGNORED_ARG)) + .ValidateArgumentValue_srw_lock_ll(&second_lock); + STRICT_EXPECTED_CALL(free(IGNORED_ARG)); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(uint32_t)(TEST_MAX_SIZE, TEST_PAGE_SIZE, NULL); + + //assert + ASSERT_IS_NULL(psa); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_009: [ If srw_lock_ll_init fails, THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall call srw_lock_ll_deinit for the locks that were initialized, free the paged sparse array and return NULL. ]*/ +TEST_FUNCTION(when_srw_lock_ll_init_fails_for_the_first_page_slot_then_THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE_frees_the_array_and_fails) +{ + //arrange + STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, TEST_PAGE_COUNT, sizeof(THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_SLOT))); + STRICT_EXPECTED_CALL(srw_lock_ll_init(IGNORED_ARG)) + .SetReturn(MU_FAILURE); + STRICT_EXPECTED_CALL(free(IGNORED_ARG)); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(uint32_t)(TEST_MAX_SIZE, TEST_PAGE_SIZE, NULL); + + //assert + ASSERT_IS_NULL(psa); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_009: [ If srw_lock_ll_init fails, THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall call srw_lock_ll_deinit for the locks that were initialized, free the paged sparse array and return NULL. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_011: [ If there are any other errors, THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(T) shall fail and return NULL. ]*/ +TEST_FUNCTION(when_underlying_calls_fail_then_THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE_fails) +{ + //arrange + STRICT_EXPECTED_CALL(malloc_flex(IGNORED_ARG, TEST_PAGE_COUNT, sizeof(THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_SLOT))); + for (uint64_t i = 0; i < TEST_PAGE_COUNT; i++) + { + STRICT_EXPECTED_CALL(srw_lock_ll_init(IGNORED_ARG)); + } + + umock_c_negative_tests_snapshot(); + + for (size_t i = 0; i < umock_c_negative_tests_call_count(); i++) + { + if (umock_c_negative_tests_can_call_fail(i)) + { + umock_c_negative_tests_reset(); + umock_c_negative_tests_fail_call(i); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = THREAD_SAFE_PAGED_SPARSE_ARRAY_CREATE(uint32_t)(TEST_MAX_SIZE, TEST_PAGE_SIZE, NULL); + + //assert + ASSERT_IS_NULL(psa, "On failed call %zu", i); + } + } +} + +/*THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE(T)*/ + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_012: [ If paged_sparse_array is NULL, THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE(T) shall return. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE_with_NULL_returns) +{ + //arrange + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_LL_DISPOSE_NAME(uint32_t)(NULL); + + //assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_014: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE(T) shall free all pages that are non-NULL. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_015: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE(T) shall call srw_lock_ll_deinit for the lock of each page slot. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE_frees_the_allocated_pages_and_deinitializes_the_lock_of_each_page_slot) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + (void)test_allocate(psa, TEST_INDEX_IN_THIRD_PAGE); + for (uint64_t i = 0; i < TEST_PAGE_COUNT; i++) + { + uint32_t* page = TEST_PAGE(psa, i * TEST_PAGE_SIZE); + if (page != NULL) + { + STRICT_EXPECTED_CALL(free(page)); + } + STRICT_EXPECTED_CALL(srw_lock_ll_deinit(TEST_PAGE_LOCK(psa, i * TEST_PAGE_SIZE))); + } + STRICT_EXPECTED_CALL(free(IGNORED_ARG)); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); + + //assert + ASSERT_IS_NULL(psa); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_015: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE(T) shall call srw_lock_ll_deinit for the lock of each page slot. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE_with_no_allocated_pages_only_deinitializes_the_locks) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(test_item_dispose_func); + for (uint64_t i = 0; i < TEST_PAGE_COUNT; i++) + { + STRICT_EXPECTED_CALL(srw_lock_ll_deinit(TEST_PAGE_LOCK(psa, i * TEST_PAGE_SIZE))); + } + STRICT_EXPECTED_CALL(free(IGNORED_ARG)); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); + + //assert + ASSERT_IS_NULL(psa); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_013: [ If item_dispose_func is not NULL, THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE(T) shall call item_dispose_func for each element that is still allocated. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_014: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE(T) shall free all pages that are non-NULL. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_DISPOSE_calls_item_dispose_func_for_each_allocated_element) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(test_item_dispose_func); + uint32_t* ptr_first = test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + uint32_t* ptr_other = test_allocate(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE); + uint32_t* ptr_third_page = test_allocate(psa, TEST_INDEX_IN_THIRD_PAGE); + STRICT_EXPECTED_CALL(test_item_dispose_func(ptr_first)); + STRICT_EXPECTED_CALL(test_item_dispose_func(ptr_other)); + STRICT_EXPECTED_CALL(free(TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE))); + STRICT_EXPECTED_CALL(srw_lock_ll_deinit(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + STRICT_EXPECTED_CALL(srw_lock_ll_deinit(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_SECOND_PAGE))); + STRICT_EXPECTED_CALL(test_item_dispose_func(ptr_third_page)); + STRICT_EXPECTED_CALL(free(TEST_PAGE(psa, TEST_INDEX_IN_THIRD_PAGE))); + for (uint64_t i = TEST_INDEX_IN_THIRD_PAGE / TEST_PAGE_SIZE; i < TEST_PAGE_COUNT; i++) + { + STRICT_EXPECTED_CALL(srw_lock_ll_deinit(TEST_PAGE_LOCK(psa, i * TEST_PAGE_SIZE))); + } + STRICT_EXPECTED_CALL(free(IGNORED_ARG)); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); + + //assert + ASSERT_IS_NULL(psa); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); +} + +/*THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T)*/ + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_029: [ If paged_sparse_array is NULL, THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall fail and return PAGED_SPARSE_ARRAY_ALLOCATE_INVALID_ARGS. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE_with_NULL_paged_sparse_array_fails) +{ + //arrange + uint32_t* ptr; + + //act + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(NULL, TEST_INDEX_IN_FIRST_PAGE, &ptr); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_INVALID_ARGS, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_030: [ If allocated_ptr is NULL, THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall fail and return PAGED_SPARSE_ARRAY_ALLOCATE_INVALID_ARGS. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE_with_NULL_allocated_ptr_fails) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + + //act + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, NULL); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_INVALID_ARGS, result); + ASSERT_IS_NULL(TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(int64_t, 0, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_031: [ If index is greater than or equal to max_size, THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall fail and return PAGED_SPARSE_ARRAY_ALLOCATE_INVALID_ARGS. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE_with_index_equal_to_max_size_fails) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + uint32_t* ptr; + + //act + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_MAX_SIZE, &ptr); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_INVALID_ARGS, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_031: [ If index is greater than or equal to max_size, THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall fail and return PAGED_SPARSE_ARRAY_ALLOCATE_INVALID_ARGS. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE_with_index_greater_than_max_size_fails) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + uint32_t* ptr; + + //act + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, UINT64_MAX, &ptr); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_INVALID_ARGS, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_032: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall compute the page index as index / page_size. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_033: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T). ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_034: [ If the page is not allocated: ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_035: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 1. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_036: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall acquire the lock of the page slot in exclusive mode. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_037: [ If the page is still not allocated: ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_038: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall call malloc_flex to allocate memory for page_size elements followed by the allocation bitmap. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_042: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall store the page pointer in the page slot using interlocked operations. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_043: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall increment the use count of the page slot using interlocked operations. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_044: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall release the lock of the page slot. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_045: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall mark the element at index as allocated using interlocked operations. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_047: [ Otherwise, THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall store a pointer to the element at index in allocated_ptr and return PAGED_SPARSE_ARRAY_ALLOCATE_OK. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE_in_a_page_that_is_not_allocated_allocates_the_page_while_holding_the_lock_of_the_page_slot) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + uint32_t* ptr = NULL; + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + STRICT_EXPECTED_CALL(malloc_flex(TEST_PAGE_BITMAP_SIZE, TEST_PAGE_SIZE, sizeof(uint32_t))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + + //act + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &ptr); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, result); + ASSERT_IS_NOT_NULL(TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(void_ptr, &TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)[TEST_INDEX_IN_FIRST_PAGE], ptr); + ASSERT_IS_TRUE(TEST_IS_ALLOCATED_IN_BITMAP(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(int64_t, 1, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + for (uint64_t i = 1; i < TEST_PAGE_COUNT; i++) + { + ASSERT_IS_NULL(TEST_PAGE(psa, i * TEST_PAGE_SIZE)); + ASSERT_ARE_EQUAL(int64_t, 0, TEST_USE_COUNT(psa, i * TEST_PAGE_SIZE)); + } + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_032: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall compute the page index as index / page_size. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE_of_the_last_index_allocates_the_last_page) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + uint32_t* ptr = NULL; + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PAGE_LOCK(psa, TEST_LAST_INDEX))); + STRICT_EXPECTED_CALL(malloc_flex(TEST_PAGE_BITMAP_SIZE, TEST_PAGE_SIZE, sizeof(uint32_t))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PAGE_LOCK(psa, TEST_LAST_INDEX))); + + //act + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_LAST_INDEX, &ptr); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, result); + ASSERT_ARE_EQUAL(void_ptr, &TEST_PAGE(psa, TEST_LAST_INDEX)[TEST_LAST_INDEX % TEST_PAGE_SIZE], ptr); + ASSERT_IS_TRUE(TEST_IS_ALLOCATED_IN_BITMAP(psa, TEST_LAST_INDEX)); + ASSERT_ARE_EQUAL(int64_t, 1, TEST_USE_COUNT(psa, TEST_LAST_INDEX)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_033: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T). ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_045: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall mark the element at index as allocated using interlocked operations. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_047: [ Otherwise, THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall store a pointer to the element at index in allocated_ptr and return PAGED_SPARSE_ARRAY_ALLOCATE_OK. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE_in_an_allocated_page_does_not_acquire_any_lock) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + uint32_t* ptr = NULL; + + //act + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE, &ptr); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, result); + ASSERT_ARE_EQUAL(void_ptr, &TEST_PAGE(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE)[TEST_OTHER_INDEX_IN_FIRST_PAGE], ptr); + ASSERT_IS_TRUE(TEST_IS_ALLOCATED_IN_BITMAP(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_IS_TRUE(TEST_IS_ALLOCATED_IN_BITMAP(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(int64_t, 2, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_040: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall set all the bits of the allocation bitmap to 0. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_041: [ If new_pages is ZERO_NEW_PAGES, THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall set all the items of the page to zero. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE_with_DO_NOT_ZERO_NEW_PAGES_initializes_the_bitmap_and_does_not_zero_the_items) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + uint32_t* ptr = NULL; + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + STRICT_EXPECTED_CALL(malloc_flex(TEST_PAGE_BITMAP_SIZE, TEST_PAGE_SIZE, sizeof(uint32_t))) + .SetReturn(test_allocate_page_memory_filled_with_garbage()); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + + //act + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &ptr); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, result); + for (uint64_t i = 0; i < TEST_PAGE_SIZE; i++) + { + ASSERT_ARE_EQUAL(bool, i == TEST_INDEX_IN_FIRST_PAGE, TEST_IS_ALLOCATED_IN_BITMAP(psa, i), "index %" PRIu64 "", i); + ASSERT_ARE_EQUAL(uint32_t, TEST_GARBAGE_ITEM_VALUE, TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)[i], "index %" PRIu64 "", i); + } + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_040: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall set all the bits of the allocation bitmap to 0. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_041: [ If new_pages is ZERO_NEW_PAGES, THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall set all the items of the page to zero. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE_with_ZERO_NEW_PAGES_sets_all_the_items_of_a_new_page_to_zero) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(KEEP_AND_ZERO_PAGES_UINT32) psa = test_create_keep_and_zero_pages(); + uint32_t* ptr = NULL; + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + STRICT_EXPECTED_CALL(malloc_flex(TEST_PAGE_BITMAP_SIZE, TEST_PAGE_SIZE, sizeof(uint32_t))) + .SetReturn(test_allocate_page_memory_filled_with_garbage()); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + + //act + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(KEEP_AND_ZERO_PAGES_UINT32)(psa, TEST_INDEX_IN_FIRST_PAGE, &ptr); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, result); + for (uint64_t i = 0; i < TEST_PAGE_SIZE; i++) + { + ASSERT_ARE_EQUAL(bool, i == TEST_INDEX_IN_FIRST_PAGE, TEST_IS_ALLOCATED_IN_BITMAP(psa, i), "index %" PRIu64 "", i); + ASSERT_ARE_EQUAL(uint32_t, 0, TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)[i], "index %" PRIu64 "", i); + } + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(KEEP_AND_ZERO_PAGES_UINT32)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_041: [ If new_pages is ZERO_NEW_PAGES, THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall set all the items of the page to zero. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE_with_ZERO_NEW_PAGES_does_not_zero_the_items_of_a_page_that_was_kept) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(KEEP_AND_ZERO_PAGES_UINT32) psa = test_create_keep_and_zero_pages(); + uint32_t* ptr = test_allocate_keep_and_zero_pages(psa, TEST_INDEX_IN_FIRST_PAGE); + *ptr = TEST_ITEM_VALUE; + THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(KEEP_AND_ZERO_PAGES_UINT32)(psa, TEST_INDEX_IN_FIRST_PAGE); + umock_c_reset_all_calls(); + uint32_t* ptr_again = NULL; + + //act + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(KEEP_AND_ZERO_PAGES_UINT32)(psa, TEST_INDEX_IN_FIRST_PAGE, &ptr_again); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, result); + ASSERT_ARE_EQUAL(void_ptr, ptr, ptr_again); + ASSERT_ARE_EQUAL(uint32_t, TEST_ITEM_VALUE, *ptr_again); + ASSERT_ARE_EQUAL(int64_t, 1, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(KEEP_AND_ZERO_PAGES_UINT32)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_039: [ If malloc_flex fails, THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall release the lock of the page slot and return PAGED_SPARSE_ARRAY_ALLOCATE_ERROR. ]*/ +TEST_FUNCTION(when_malloc_flex_fails_then_THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE_releases_the_lock_of_the_page_slot_and_fails) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + uint32_t* ptr = NULL; + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + STRICT_EXPECTED_CALL(malloc_flex(TEST_PAGE_BITMAP_SIZE, TEST_PAGE_SIZE, sizeof(uint32_t))) + .SetReturn(NULL); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + + //act + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &ptr); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_ERROR, result); + ASSERT_IS_NULL(ptr); + ASSERT_IS_NULL(TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(int64_t, 0, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_037: [ If the page is still not allocated: ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_043: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall increment the use count of the page slot using interlocked operations. ]*/ +TEST_FUNCTION(when_another_thread_allocates_the_page_before_the_lock_is_acquired_then_THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE_uses_that_page) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + uint32_t* ptr = NULL; + setup_concurrent_call_on_acquire_exclusive(TEST_CONCURRENT_CALL_ALLOCATE, psa, TEST_OTHER_INDEX_IN_FIRST_PAGE); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + /* the other thread allocates the page */ + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + STRICT_EXPECTED_CALL(malloc_flex(TEST_PAGE_BITMAP_SIZE, TEST_PAGE_SIZE, sizeof(uint32_t))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + /* back to the call under test */ + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + + //act + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &ptr); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, result); + ASSERT_IS_TRUE(g_concurrent_call.was_called); + ASSERT_ARE_EQUAL(void_ptr, &TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)[TEST_INDEX_IN_FIRST_PAGE], ptr); + ASSERT_IS_TRUE(TEST_IS_ALLOCATED_IN_BITMAP(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_IS_TRUE(TEST_IS_ALLOCATED_IN_BITMAP(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(int64_t, 2, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_017: [ If the use count is greater than or equal to THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING, THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T) shall return NULL. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_034: [ If the page is not allocated: ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_035: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 1. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_036: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall acquire the lock of the page slot in exclusive mode. ]*/ +TEST_FUNCTION(when_the_page_is_being_freed_then_THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE_does_not_use_it_and_allocates_a_new_page) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + uint32_t* page_being_freed = TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE); + /* another thread released the element and is freeing the page: it changed the use count from 0 to THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING */ + ASSERT_IS_TRUE(THREAD_SAFE_PAGED_SPARSE_ARRAY_TEST_AND_CLEAR_ALLOCATED(THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_BITMAP(page_being_freed, TEST_PAGE_SIZE), TEST_INDEX_IN_FIRST_PAGE)); + (void)interlocked_exchange_64(&TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE), THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING); + /* the other thread finishes freeing the page while holding the lock of the page slot */ + setup_concurrent_call_on_acquire_exclusive(TEST_CONCURRENT_CALL_FINISH_FREEING_PAGE, psa, TEST_INDEX_IN_FIRST_PAGE); + uint32_t* ptr = NULL; + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + STRICT_EXPECTED_CALL(malloc_flex(TEST_PAGE_BITMAP_SIZE, TEST_PAGE_SIZE, sizeof(uint32_t))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + + //act + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE, &ptr); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, result); + ASSERT_IS_TRUE(g_concurrent_call.was_called); + ASSERT_IS_NOT_NULL(TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_NOT_EQUAL(void_ptr, page_being_freed, TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(void_ptr, &TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)[TEST_OTHER_INDEX_IN_FIRST_PAGE], ptr); + ASSERT_IS_TRUE(TEST_IS_ALLOCATED_IN_BITMAP(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(int64_t, 1, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + /* the page that was being freed was not changed */ + ASSERT_IS_FALSE(THREAD_SAFE_PAGED_SPARSE_ARRAY_IS_ALLOCATED(THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_BITMAP(page_being_freed, TEST_PAGE_SIZE), TEST_OTHER_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + real_gballoc_hl_free(page_being_freed); + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_045: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall mark the element at index as allocated using interlocked operations. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_046: [ If the element at index was already marked as allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 1 and return PAGED_SPARSE_ARRAY_ALLOCATE_ALREADY_ALLOCATED. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE_of_an_allocated_element_fails_and_ends_the_use_of_the_page) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + uint32_t* ptr = test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + uint32_t* ptr_again = NULL; + + //act + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &ptr_again); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_ALREADY_ALLOCATED, result); + ASSERT_IS_NULL(ptr_again); + ASSERT_IS_TRUE(TEST_IS_ALLOCATED_IN_BITMAP(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(void_ptr, &TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)[TEST_INDEX_IN_FIRST_PAGE], ptr); + ASSERT_ARE_EQUAL(int64_t, 1, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_034: [ If the page is not allocated: ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE_after_the_page_was_freed_allocates_the_page_again) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + ASSERT_IS_NULL(TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)); + umock_c_reset_all_calls(); + uint32_t* ptr = NULL; + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + STRICT_EXPECTED_CALL(malloc_flex(TEST_PAGE_BITMAP_SIZE, TEST_PAGE_SIZE, sizeof(uint32_t))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + + //act + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &ptr); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, result); + ASSERT_ARE_EQUAL(void_ptr, &TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)[TEST_INDEX_IN_FIRST_PAGE], ptr); + ASSERT_ARE_EQUAL(int64_t, 1, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_034: [ If the page is not allocated: ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE_after_FREE_EMPTY_PAGES_allocates_the_page_again) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(KEEP_AND_ZERO_PAGES_UINT32) psa = test_create_keep_and_zero_pages_with_empty_page(TEST_INDEX_IN_FIRST_PAGE); + ASSERT_ARE_EQUAL(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_OK, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(KEEP_AND_ZERO_PAGES_UINT32)(psa, 0, TEST_LAST_INDEX)); + ASSERT_IS_NULL(TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)); + umock_c_reset_all_calls(); + uint32_t* ptr = NULL; + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + STRICT_EXPECTED_CALL(malloc_flex(TEST_PAGE_BITMAP_SIZE, TEST_PAGE_SIZE, sizeof(uint32_t))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + + //act + PAGED_SPARSE_ARRAY_ALLOCATE_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_ALLOCATE(KEEP_AND_ZERO_PAGES_UINT32)(psa, TEST_INDEX_IN_FIRST_PAGE, &ptr); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_ALLOCATE_RESULT, PAGED_SPARSE_ARRAY_ALLOCATE_OK, result); + ASSERT_ARE_EQUAL(void_ptr, &TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)[TEST_INDEX_IN_FIRST_PAGE], ptr); + ASSERT_ARE_EQUAL(int64_t, 1, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(KEEP_AND_ZERO_PAGES_UINT32)(&psa, NULL); +} + +/*THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T)*/ + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_048: [ If paged_sparse_array is NULL, THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall return. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE_with_NULL_paged_sparse_array_returns) +{ + //arrange + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(NULL, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_049: [ If index is greater than or equal to max_size, THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall return. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE_with_index_equal_to_max_size_returns) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(test_item_dispose_func); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_MAX_SIZE); + + //assert + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_050: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall compute the page index as index / page_size. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_051: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T). ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_052: [ If the page is not allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 1 and return. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE_in_a_page_that_is_not_allocated_returns_without_acquiring_any_lock) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(test_item_dispose_func); + (void)test_allocate(psa, TEST_INDEX_IN_THIRD_PAGE); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_IS_NULL(TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(int64_t, 0, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(int64_t, 1, TEST_USE_COUNT(psa, TEST_INDEX_IN_THIRD_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_053: [ If the element at index is not allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 1 and return. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE_of_an_element_that_is_not_allocated_returns) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(test_item_dispose_func); + (void)test_allocate(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_IS_NOT_NULL(TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_IS_TRUE(TEST_IS_ALLOCATED_IN_BITMAP(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(int64_t, 1, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_016: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T) shall increment the use count of the page slot using interlocked operations. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_051: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T). ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_054: [ If item_dispose_func is not NULL, THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall call item_dispose_func for the element being released before marking the element as not allocated. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE_calls_item_dispose_func_while_using_the_page_before_marking_the_element_as_not_allocated) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(test_item_dispose_func); + uint32_t* ptr = test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + (void)test_allocate(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE); + setup_dispose_observer(psa, TEST_INDEX_IN_FIRST_PAGE, false); + STRICT_EXPECTED_CALL(test_item_dispose_func(ptr)); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_IS_TRUE(g_dispose_observer.was_called); + ASSERT_IS_TRUE(g_dispose_observer.was_allocated_when_called); + /* 2 allocated elements and the call to RELEASE */ + ASSERT_ARE_EQUAL(int64_t, 3, g_dispose_observer.use_count_when_called); + ASSERT_IS_FALSE(TEST_IS_ALLOCATED_IN_BITMAP(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(int64_t, 1, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + g_dispose_observer.psa = NULL; + umock_c_reset_all_calls(); + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_019: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) shall subtract ended_use_count from the use count of the page slot using interlocked operations. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_055: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall mark the element at index as not allocated using interlocked operations. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_056: [ If the element was still marked as allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 2. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE_of_an_element_when_the_page_has_other_allocated_elements_does_not_acquire_any_lock) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + (void)test_allocate(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_IS_NOT_NULL(TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_IS_FALSE(TEST_IS_ALLOCATED_IN_BITMAP(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_IS_TRUE(TEST_IS_ALLOCATED_IN_BITMAP(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(int64_t, 1, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_020: [ If empty_pages is AUTO_FREE_EMPTY_PAGES, the use count became 0 and the page is allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T). ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_021: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall acquire the lock of the page slot in exclusive mode. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_023: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall change the use count of the page slot from 0 to THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING using interlocked operations. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_025: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall set the page pointer of the page slot to NULL using interlocked operations. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_026: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall subtract THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING from the use count of the page slot using interlocked operations. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_027: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall release the lock of the page slot. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_028: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall free the page. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_056: [ If the element was still marked as allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 2. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE_of_the_last_element_frees_the_page_after_releasing_the_lock_of_the_page_slot) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + uint32_t* page = TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + STRICT_EXPECTED_CALL(free(page)); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_IS_NULL(TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(int64_t, 0, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_020: [ If empty_pages is AUTO_FREE_EMPTY_PAGES, the use count became 0 and the page is allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T). ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE_of_the_last_element_with_KEEP_EMPTY_PAGES_keeps_the_page) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(KEEP_AND_ZERO_PAGES_UINT32) psa = test_create_keep_and_zero_pages(); + (void)test_allocate_keep_and_zero_pages(psa, TEST_INDEX_IN_FIRST_PAGE); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(KEEP_AND_ZERO_PAGES_UINT32)(psa, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_IS_NOT_NULL(TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_IS_FALSE(TEST_IS_ALLOCATED_IN_BITMAP(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(int64_t, 0, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(KEEP_AND_ZERO_PAGES_UINT32)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_055: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall mark the element at index as not allocated using interlocked operations. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_057: [ Otherwise, THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 1. ]*/ +TEST_FUNCTION(when_another_caller_releases_the_same_element_concurrently_then_THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE_ends_only_the_use_of_the_call) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(test_item_dispose_func); + uint32_t* ptr = test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + (void)test_allocate(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE); + setup_dispose_observer(psa, TEST_INDEX_IN_FIRST_PAGE, true); + STRICT_EXPECTED_CALL(test_item_dispose_func(ptr)); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_IS_TRUE(g_dispose_observer.was_called); + ASSERT_IS_NOT_NULL(TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_IS_FALSE(TEST_IS_ALLOCATED_IN_BITMAP(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_IS_TRUE(TEST_IS_ALLOCATED_IN_BITMAP(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(int64_t, 1, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + g_dispose_observer.psa = NULL; + umock_c_reset_all_calls(); + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_017: [ If the use count is greater than or equal to THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING, THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T) shall return NULL. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_052: [ If the page is not allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 1 and return. ]*/ +TEST_FUNCTION(when_the_page_is_being_freed_then_THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE_does_not_access_the_page) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(test_item_dispose_func); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + /* the bit of the element is still set, so accessing the page would call item_dispose_func */ + (void)interlocked_exchange_64(&TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE), THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_IS_TRUE(TEST_IS_ALLOCATED_IN_BITMAP(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(int64_t, THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + /* restore the use count of the page: the use of its 1 allocated element */ + (void)interlocked_exchange_64(&TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE), 1); + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_023: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall change the use count of the page slot from 0 to THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING using interlocked operations. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_024: [ If the use count was not 0, THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall release the lock of the page slot and return. ]*/ +TEST_FUNCTION(when_another_call_begins_to_use_the_page_before_the_lock_is_acquired_then_THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE_does_not_free_the_page) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + setup_concurrent_call_on_acquire_exclusive(TEST_CONCURRENT_CALL_BEGIN_PAGE_USE, psa, TEST_INDEX_IN_FIRST_PAGE); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_IS_TRUE(g_concurrent_call.was_called); + ASSERT_IS_NOT_NULL(TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_IS_FALSE(TEST_IS_ALLOCATED_IN_BITMAP(psa, TEST_INDEX_IN_FIRST_PAGE)); + /* the use of the other call */ + ASSERT_ARE_EQUAL(int64_t, 1, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + (void)interlocked_decrement_64(&TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_022: [ If the page is not allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall release the lock of the page slot and return. ]*/ +TEST_FUNCTION(when_another_thread_frees_the_page_before_the_lock_is_acquired_then_THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE_does_not_free_it_again) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + uint32_t* page = TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE); + setup_concurrent_call_on_acquire_exclusive(TEST_CONCURRENT_CALL_FREE_EMPTY_PAGES, psa, TEST_INDEX_IN_FIRST_PAGE); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + /* the other thread frees the page */ + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + STRICT_EXPECTED_CALL(free(page)); + /* back to the call under test */ + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_IS_TRUE(g_concurrent_call.was_called); + ASSERT_IS_NULL(TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(int64_t, 0, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/*THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T)*/ + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_058: [ If paged_sparse_array is NULL, THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T) shall fail and return PAGED_SPARSE_ARRAY_GET_INVALID_ARGS. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_GET_with_NULL_paged_sparse_array_fails) +{ + //arrange + uint32_t* item; + + //act + PAGED_SPARSE_ARRAY_GET_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(uint32_t)(NULL, TEST_INDEX_IN_FIRST_PAGE, &item); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_INVALID_ARGS, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_059: [ If item is NULL, THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T) shall fail and return PAGED_SPARSE_ARRAY_GET_INVALID_ARGS. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_GET_with_NULL_item_fails) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + + //act + PAGED_SPARSE_ARRAY_GET_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, NULL); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_INVALID_ARGS, result); + ASSERT_ARE_EQUAL(int64_t, 1, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_060: [ If index is greater than or equal to max_size, THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T) shall fail and return PAGED_SPARSE_ARRAY_GET_INVALID_ARGS. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_GET_with_index_equal_to_max_size_fails) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + uint32_t* item; + + //act + PAGED_SPARSE_ARRAY_GET_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(uint32_t)(psa, TEST_MAX_SIZE, &item); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_INVALID_ARGS, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_020: [ If empty_pages is AUTO_FREE_EMPTY_PAGES, the use count became 0 and the page is allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T). ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_061: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T) shall compute the page index as index / page_size. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_062: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T). ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_063: [ If the page is not allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 1 and return PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_GET_in_a_page_that_is_not_allocated_returns_NOT_ALLOCATED_without_acquiring_any_lock) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + uint32_t* item = NULL; + + //act + PAGED_SPARSE_ARRAY_GET_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &item); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED, result); + ASSERT_IS_NULL(item); + ASSERT_ARE_EQUAL(int64_t, 0, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_064: [ If the element at index is not allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 1 and return PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_GET_of_an_element_that_is_not_allocated_returns_NOT_ALLOCATED) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + (void)test_allocate(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE); + uint32_t* item = NULL; + + //act + PAGED_SPARSE_ARRAY_GET_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &item); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED, result); + ASSERT_IS_NULL(item); + ASSERT_ARE_EQUAL(int64_t, 1, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_018: [ Otherwise, THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T) shall return the page pointer of the page slot, read using interlocked operations. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_019: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) shall subtract ended_use_count from the use count of the page slot using interlocked operations. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_065: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T) shall store in item a pointer to the element at index. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_066: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 1 and return PAGED_SPARSE_ARRAY_GET_OK. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_GET_returns_the_element_without_acquiring_any_lock) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + uint32_t* ptr = test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + *ptr = TEST_ITEM_VALUE; + uint32_t* item = NULL; + + //act + PAGED_SPARSE_ARRAY_GET_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &item); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_OK, result); + ASSERT_ARE_EQUAL(void_ptr, ptr, item); + ASSERT_ARE_EQUAL(uint32_t, TEST_ITEM_VALUE, *item); + ASSERT_ARE_EQUAL(int64_t, 1, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_061: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T) shall compute the page index as index / page_size. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_GET_of_the_last_index_returns_the_element) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + uint32_t* ptr = test_allocate(psa, TEST_LAST_INDEX); + uint32_t* item = NULL; + + //act + PAGED_SPARSE_ARRAY_GET_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(uint32_t)(psa, TEST_LAST_INDEX, &item); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_OK, result); + ASSERT_ARE_EQUAL(void_ptr, ptr, item); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_017: [ If the use count is greater than or equal to THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING, THREAD_SAFE_PAGED_SPARSE_ARRAY_BEGIN_PAGE_USE(T) shall return NULL. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_063: [ If the page is not allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_END_PAGE_USE(T) with ended_use_count 1 and return PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED. ]*/ +TEST_FUNCTION(when_the_page_is_being_freed_then_THREAD_SAFE_PAGED_SPARSE_ARRAY_GET_returns_NOT_ALLOCATED_without_accessing_the_page) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t) psa = test_create(NULL); + (void)test_allocate(psa, TEST_INDEX_IN_FIRST_PAGE); + /* the bit of the element is still set, so accessing the page would return the element */ + (void)interlocked_exchange_64(&TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE), THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING); + uint32_t* item = NULL; + + //act + PAGED_SPARSE_ARRAY_GET_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_GET(uint32_t)(psa, TEST_INDEX_IN_FIRST_PAGE, &item); + + //assert + ASSERT_ARE_EQUAL(PAGED_SPARSE_ARRAY_GET_RESULT, PAGED_SPARSE_ARRAY_GET_NOT_ALLOCATED, result); + ASSERT_IS_NULL(item); + ASSERT_ARE_EQUAL(int64_t, THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + /* restore the use count of the page: the use of its 1 allocated element */ + (void)interlocked_exchange_64(&TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE), 1); + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(uint32_t)(&psa, NULL); +} + +/*THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T)*/ + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_067: [ If paged_sparse_array is NULL, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T) shall fail and return THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_INVALID_ARGS. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_with_NULL_paged_sparse_array_fails) +{ + //arrange + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(uint32_t)(NULL, 0, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_ARE_EQUAL(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_INVALID_ARGS, result); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_068: [ If start_index is greater than end_index, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T) shall fail and return THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_INVALID_ARGS. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_with_start_index_greater_than_end_index_fails) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(KEEP_AND_ZERO_PAGES_UINT32) psa = test_create_keep_and_zero_pages_with_empty_page(TEST_INDEX_IN_FIRST_PAGE); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(KEEP_AND_ZERO_PAGES_UINT32)(psa, TEST_OTHER_INDEX_IN_FIRST_PAGE, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_ARE_EQUAL(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_INVALID_ARGS, result); + ASSERT_IS_NOT_NULL(TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(KEEP_AND_ZERO_PAGES_UINT32)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_069: [ If end_index is greater than or equal to max_size, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T) shall fail and return THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_INVALID_ARGS. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_with_end_index_equal_to_max_size_fails) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(KEEP_AND_ZERO_PAGES_UINT32) psa = test_create_keep_and_zero_pages_with_empty_page(TEST_INDEX_IN_FIRST_PAGE); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(KEEP_AND_ZERO_PAGES_UINT32)(psa, 0, TEST_MAX_SIZE); + + //assert + ASSERT_ARE_EQUAL(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_INVALID_ARGS, result); + ASSERT_IS_NOT_NULL(TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(KEEP_AND_ZERO_PAGES_UINT32)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_021: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall acquire the lock of the page slot in exclusive mode. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_022: [ If the page is not allocated, THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall release the lock of the page slot and return. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_023: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall change the use count of the page slot from 0 to THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING using interlocked operations. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_024: [ If the use count was not 0, THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall release the lock of the page slot and return. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_025: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall set the page pointer of the page slot to NULL using interlocked operations. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_026: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall subtract THREAD_SAFE_PAGED_SPARSE_ARRAY_PAGE_FREEING from the use count of the page slot using interlocked operations. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_027: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall release the lock of the page slot. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_028: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall free the page. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_070: [ For each page from start_index / page_size to end_index / page_size, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T). ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_071: [ THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T) shall succeed and return THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_OK. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_frees_the_empty_pages_of_the_range_and_skips_the_others) +{ + //arrange + /* page 0: empty, page 1: not allocated, page 2: empty, page 3: has an allocated element, page 4: empty but outside of the range */ + THREAD_SAFE_PAGED_SPARSE_ARRAY(KEEP_AND_ZERO_PAGES_UINT32) psa = test_create_keep_and_zero_pages_with_empty_page(TEST_INDEX_IN_FIRST_PAGE); + (void)test_allocate_keep_and_zero_pages(psa, TEST_INDEX_IN_THIRD_PAGE); + THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(KEEP_AND_ZERO_PAGES_UINT32)(psa, TEST_INDEX_IN_THIRD_PAGE); + (void)test_allocate_keep_and_zero_pages(psa, TEST_INDEX_IN_FOURTH_PAGE); + (void)test_allocate_keep_and_zero_pages(psa, TEST_INDEX_IN_FIFTH_PAGE); + THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(KEEP_AND_ZERO_PAGES_UINT32)(psa, TEST_INDEX_IN_FIFTH_PAGE); + umock_c_reset_all_calls(); + uint32_t* first_page = TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE); + uint32_t* third_page = TEST_PAGE(psa, TEST_INDEX_IN_THIRD_PAGE); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + STRICT_EXPECTED_CALL(free(first_page)); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_SECOND_PAGE))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_SECOND_PAGE))); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_THIRD_PAGE))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_THIRD_PAGE))); + STRICT_EXPECTED_CALL(free(third_page)); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FOURTH_PAGE))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FOURTH_PAGE))); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(KEEP_AND_ZERO_PAGES_UINT32)(psa, TEST_INDEX_IN_FIRST_PAGE, TEST_INDEX_IN_FOURTH_PAGE); + + //assert + ASSERT_ARE_EQUAL(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_OK, result); + ASSERT_IS_NULL(TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_IS_NULL(TEST_PAGE(psa, TEST_INDEX_IN_SECOND_PAGE)); + ASSERT_IS_NULL(TEST_PAGE(psa, TEST_INDEX_IN_THIRD_PAGE)); + ASSERT_IS_NOT_NULL(TEST_PAGE(psa, TEST_INDEX_IN_FOURTH_PAGE)); + ASSERT_IS_NOT_NULL(TEST_PAGE(psa, TEST_INDEX_IN_FIFTH_PAGE)); + ASSERT_ARE_EQUAL(int64_t, 0, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(int64_t, 0, TEST_USE_COUNT(psa, TEST_INDEX_IN_SECOND_PAGE)); + ASSERT_ARE_EQUAL(int64_t, 0, TEST_USE_COUNT(psa, TEST_INDEX_IN_THIRD_PAGE)); + ASSERT_ARE_EQUAL(int64_t, 1, TEST_USE_COUNT(psa, TEST_INDEX_IN_FOURTH_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(KEEP_AND_ZERO_PAGES_UINT32)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_070: [ For each page from start_index / page_size to end_index / page_size, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T). ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_with_start_index_equal_to_end_index_frees_only_the_page_of_that_index) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(KEEP_AND_ZERO_PAGES_UINT32) psa = test_create_keep_and_zero_pages_with_empty_page(TEST_INDEX_IN_FIRST_PAGE); + (void)test_allocate_keep_and_zero_pages(psa, TEST_INDEX_IN_SECOND_PAGE); + THREAD_SAFE_PAGED_SPARSE_ARRAY_RELEASE(KEEP_AND_ZERO_PAGES_UINT32)(psa, TEST_INDEX_IN_SECOND_PAGE); + umock_c_reset_all_calls(); + uint32_t* first_page = TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + STRICT_EXPECTED_CALL(free(first_page)); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(KEEP_AND_ZERO_PAGES_UINT32)(psa, TEST_LAST_INDEX_IN_FIRST_PAGE, TEST_LAST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_ARE_EQUAL(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_OK, result); + ASSERT_IS_NULL(TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_IS_NOT_NULL(TEST_PAGE(psa, TEST_INDEX_IN_SECOND_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(KEEP_AND_ZERO_PAGES_UINT32)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_069: [ If end_index is greater than or equal to max_size, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T) shall fail and return THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_INVALID_ARGS. ]*/ +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_070: [ For each page from start_index / page_size to end_index / page_size, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(T) shall call THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T). ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_with_end_index_equal_to_the_last_index_frees_the_last_page) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(KEEP_AND_ZERO_PAGES_UINT32) psa = test_create_keep_and_zero_pages_with_empty_page(TEST_LAST_INDEX); + uint32_t* last_page = TEST_PAGE(psa, TEST_LAST_INDEX); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PAGE_LOCK(psa, TEST_LAST_INDEX))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PAGE_LOCK(psa, TEST_LAST_INDEX))); + STRICT_EXPECTED_CALL(free(last_page)); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(KEEP_AND_ZERO_PAGES_UINT32)(psa, TEST_LAST_INDEX, TEST_LAST_INDEX); + + //assert + ASSERT_ARE_EQUAL(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_OK, result); + ASSERT_IS_NULL(TEST_PAGE(psa, TEST_LAST_INDEX)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(KEEP_AND_ZERO_PAGES_UINT32)(&psa, NULL); +} + +/* Tests_SRS_THREAD_SAFE_PAGED_SPARSE_ARRAY_09_024: [ If the use count was not 0, THREAD_SAFE_PAGED_SPARSE_ARRAY_TRY_FREE_PAGE(T) shall release the lock of the page slot and return. ]*/ +TEST_FUNCTION(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_does_not_free_a_page_that_another_call_uses) +{ + //arrange + THREAD_SAFE_PAGED_SPARSE_ARRAY(KEEP_AND_ZERO_PAGES_UINT32) psa = test_create_keep_and_zero_pages_with_empty_page(TEST_INDEX_IN_FIRST_PAGE); + setup_concurrent_call_on_acquire_exclusive(TEST_CONCURRENT_CALL_BEGIN_PAGE_USE, (THREAD_SAFE_PAGED_SPARSE_ARRAY(uint32_t))(const void*)psa, TEST_INDEX_IN_FIRST_PAGE); + STRICT_EXPECTED_CALL(srw_lock_ll_acquire_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + STRICT_EXPECTED_CALL(srw_lock_ll_release_exclusive(TEST_PAGE_LOCK(psa, TEST_INDEX_IN_FIRST_PAGE))); + + //act + THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT result = THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES(KEEP_AND_ZERO_PAGES_UINT32)(psa, TEST_INDEX_IN_FIRST_PAGE, TEST_INDEX_IN_FIRST_PAGE); + + //assert + ASSERT_ARE_EQUAL(THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_RESULT, THREAD_SAFE_PAGED_SPARSE_ARRAY_FREE_EMPTY_PAGES_OK, result); + ASSERT_IS_TRUE(g_concurrent_call.was_called); + ASSERT_IS_NOT_NULL(TEST_PAGE(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(int64_t, 1, TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls()); + + //clean + (void)interlocked_decrement_64(&TEST_USE_COUNT(psa, TEST_INDEX_IN_FIRST_PAGE)); + THREAD_SAFE_PAGED_SPARSE_ARRAY_ASSIGN(KEEP_AND_ZERO_PAGES_UINT32)(&psa, NULL); +} + +END_TEST_SUITE(TEST_SUITE_NAME_FROM_CMAKE) diff --git a/tests/thread_safe_paged_sparse_array_ut/thread_safe_paged_sparse_array_ut_pch.h b/tests/thread_safe_paged_sparse_array_ut/thread_safe_paged_sparse_array_ut_pch.h new file mode 100644 index 00000000..ef45a162 --- /dev/null +++ b/tests/thread_safe_paged_sparse_array_ut/thread_safe_paged_sparse_array_ut_pch.h @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// Precompiled header for thread_safe_paged_sparse_array_ut + +#ifndef THREAD_SAFE_PAGED_SPARSE_ARRAY_UT_PCH_H +#define THREAD_SAFE_PAGED_SPARSE_ARRAY_UT_PCH_H + +#include +#include +#include +#include +#include + +#include "macro_utils/macro_utils.h" + +#include "testrunnerswitcher.h" + +#include "umock_c/umock_c_negative_tests.h" + +#include "umock_c/umock_c.h" + +#include "c_pal/interlocked.h" + +#include "umock_c/umock_c_ENABLE_MOCKS.h" // ============================== ENABLE_MOCKS +#include "c_pal/gballoc_hl.h" +#include "c_pal/gballoc_hl_redirect.h" +#include "c_pal/srw_lock_ll.h" +#include "umock_c/umock_c_DISABLE_MOCKS.h" // ============================== DISABLE_MOCKS + +#include "real_gballoc_hl.h" +#include "real_srw_lock_ll.h" + +#include "c_pal/thandle.h" +#include "c_util/thread_safe_paged_sparse_array.h" + +#endif // THREAD_SAFE_PAGED_SPARSE_ARRAY_UT_PCH_H