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/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/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/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/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