Repository: oneapi-src/unified-memory-framework (language: python)

Issue:
Backward compatibility for configs of pools/providers

## Rationale

UMF should provide backward compatible interfaces.

## Description

### Background

MPI team experienced the issue after the PR #692 extends `level_zero_memory_provider_params_t` config structure. The root cause of the issue relates to how MPI instantiate/initialize the L0 provider config. They did the following:
```c
level_zero_memory_provider_params_t l0_config = {
    .level_zero_context_handle = hContext;
    .level_zero_device_handle = hDevice;
    .memory_type = UMF_MEMORY_TYPE_DEVICE;
};

umf_memory_provider_handle_t hProvider = NULL;
umf_memory_provider_ops_t *l0_ops = umfLevelZeroMemoryProviderOps();
umfMemoryProviderCreate(l0_ops, &l0_config, &hProvider);
```

After PR #692 the code above start crashing because two new fields were added to the `level_zero_memory_provider_params_t` data structure and the example above does not initialize these new fields:
```c
typedef struct level_zero_memory_provider_params_t {
    ze_context_handle_t level_zero_context_handle;
    ze_device_handle_t level_zero_device_handle;
    umf_usm_memory_type_t memory_type;

    // New fields
    ze_device_handle_t *resident_device_handles;
    uint32_t resident_device_count;
} level_zero_memory_provider_params_t;
```

A quick fix for the issue was to init the config data structure with `0` and than assign the required fields:
```c
level_zero_memory_provider_params_t l0_config = { 0 }; // zero-initialize
l0_config.level_zero_context_handle = hContext;
l0_config.level_zero_device_handle = hDevice;
l0_config.memory_type = UMF_MEMORY_TYPE_DEVICE;
```

### Open Questions

The quick fix above works only because it is OK to init fields of the `level_zero_memory_provider_params_t` structure with zeroes. But there are 2 related major question we should address:

#### 1. How to initialize the configs with default values?

In general case, not every field of the config data structure could/should be initialized with zeroes.

#### 2. How to support backward compatibility?

Config data structures are defined in the headers. If application was built with old version of UMF but on the system there is a newer version than even the application initialize all config's fields properly it initializes only fields that exists in the old version of UMF.
Consider an example. There is a `provider_foo` and corresponding `foo_config_t` structure that contains an int field in the 1st version:

```c
// provider_foo.h:
typedef struct foo_params_t {
    int field 1;
} foo_config_t;

// provider_foo.c:
umf_result_t foo_provider_initialize(void *params, void **provider) {
    foo_params_t *foo_params = (foo_params_t *)params;
    // expected sizeof(foo_params_t) is equal sizeof(int) that is 4 bytes

    assert(foo_params->field1 == 0);
}

// application code:
int main() {
    foo_params_t foo_params = {0};

    umf_memory_provider_handle_t hProvider = NULL;
    umf_memory_provider_ops_t *foo_ops = umfFooMemoryProviderOps();
    umfMemoryProviderCreate(foo_ops, &foo_params, &hProvider);
}
```

Now in the 2nd version of UMF we extend the `foo_params_t` structure with additional field:

```c
// provider_foo.h:
typedef struct foo_params_t {
    int field 1;
    int field2;
} foo_config_t;

// provider_foo.c:
umf_result_t foo_provider_initialize(void *params, void **provider) {
    foo_params_t *foo_params = (foo_params_t *)params;
    // expected sizeof(foo_params_t) is equal 2*sizeof(int) that is 8 bytes

    assert(foo_params->field1 == 0);
    assert(foo_params->field2 == 0); // ERROR: if application was compiled with 1st version of the UMF lib then the size of memory pointed by `params` is 4 bytes
}
```

## Possible API Changes

### Option 1: handle-based approach
Do not expose the config structure in interfaces. The config object is allocated inside libumf.so and handle to the config is returned to the client code. Setter/getter APIs are used to setup config parameters. For example:

```c
foo_params_handle_t foo_params = umfCreateFooParams();
umfFooParamsSetField1(foo_params, 7);

umf_memory_provider_handle_t hProvider = NULL;
umf_memory_provider_ops_t *foo_ops = umfFooMemoryProviderOps();
umfMemoryProviderCreate(foo_ops, foo_params, &hProvider);
```

Client code does not depend on the layout of the `foo_params_t` structure only UMF knows it and can change in different versions.

### Option 2: explicitly pass the size of params 

Data structures that define configs for the pools/providers remains in headers (the same as today). The new fields can be added only to the end. Application explicitly passes the size of the config data structure. Provider/pool implementation determine the version of the config based on the size. To init the config data structure we need to introduce a special macros or header-based inline functions. For example:

```c
// Version 1 provider_foo.h:
typedef struct foo_params_t {
    int field 1;
} foo_config_t;

inline void umfFooParamsInit(foo_params_t *params) {
     params->field1 = 0;
}

// Version 2 provider_foo.h:
typedef struct foo_params_t {
    int field 1;
    int field2;
} foo_config_t;

inline void umfFooParamsInit(foo_params_t *params) {
     params->field1 = 0;
     params->field2 = 0;
}

// Version 2 provider_foo.c:
umf_result_t foo_provider_initialize(void *params, size_t params_size, void **provider) {
    foo_params_t foo_params;
    umfFooParamsInit(& foo_params);

    if(sizeof(foo_params_t) == params_size) {
         // current version, just copy input params as is.
        foo_params = *(foo_params_t*)params;
    } else {
        // old version, copy only first `params_size` bytes and keep the rest default initialized
        memcpy(& foo_params, params, params_size);
    }

    assert(foo_params->field1 == 0);
    assert(foo_params->field2 == 0);
}

// application code:
int main() {
    foo_params_t foo_params;
    umfFooParamsInit(&params);

    umf_memory_provider_handle_t hProvider = NULL;
    umf_memory_provider_ops_t *foo_ops = umfFooMemoryProviderOps();
    umfMemoryProviderCreate(foo_ops, &foo_params, sizeof(foo_params_t), &hProvider);
}
```

### Option 3: store version in the params data structure

Similar to Option 2, but instead of using the size of params data structure to determine the version store it explicitly as a first field in the params data structure.

```c
// Version 1 provider_foo.h:
typedef struct foo_params_t {
    int version;
    int field 1;
} foo_config_t;

inline void umfFooParamsInit(foo_params_t *params) {
    version = 1;
    params->field1 = 0;
}

// Version 2 provider_foo.h:
typedef struct foo_params_t {
    int field 1;
    int field2;
} foo_config_t;

inline void umfFooParamsInit(foo_params_t *params) {
    version = 2;
    params->field1 = 0;
    params->field2 = 0;
}

// Version 2 provider_foo.c:
umf_result_t foo_provider_initialize(void *params, size_t params_size, void **provider) {
    foo_params_t foo_params;
    umfFooParamsInit(& foo_params);

    int params_version = *(int *)params;

    switch(params_version) {
        case 1:
            ...
            break;
        case 2:
            ...
            break;
        default:
            LOG_ERR("Wrong version");
    }

    assert(foo_params->field1 == 0);
    assert(foo_params->field2 == 0);
}

// application code:
int main() {
    foo_params_t foo_params;
    umfFooParamsInit(&params);

    umf_memory_provider_handle_t hProvider = NULL;
    umf_memory_provider_ops_t *foo_ops = umfFooMemoryProviderOps();
    umfMemoryProviderCreate(foo_ops, &foo_params, &hProvider);
}
```

### Design Considerations

I prefer Option 1 over Option 2 and Option 3. I think it provides best ABI compatibility. Another advantage is that the config data structure always initialized when user calls `umfCreateFooParams()`. There is no way to get non-initialized fields in the config, while in case of Option 2 or 3 user might forget to call `umfFooParamsInit(&params)` function.

Between Options 2 and 3, I prefer Option 3 because of two things:

- it stores version explicitly.
- the `umfMemoryProviderCreate` API remains unchanged.

## Implementation details

TBD




Discussion / hints:
We must address this issue before the first stable release because all three options require non-backward compatible changes.
:+1: to option 1.

Another option is to do what UR does and create a "chain" (linked list) of parameters.
```
struct foo_param {
  param_type type;
  int foo;
  void *next;
}

struct bar_param {
  param_type type;
  int bar;
  void *next;
}

struct foo_param foo = { PARAM_FOO, 5, nullptr };
struct bar_param bar = { PARAM_BAR, 10, &foo };

someApi(&bar); // this would apply all options in the chain.

```

This is super verbose, but really flexible. One downside is that it's on the api function to validate the parameters, and users need to remember to correctly set param type.

I also vote for option 1. What UR does is also OK but from my experience it's quite easy to forget setting the type (and we even had a bug in UMF where we set the type incorrectly).

One question: how's responsible for destroying the params - user or UMF?
> One question: how's responsible for destroying the params - user or UMF?

In the case of option 1, User calls `umfCreateFooParams()` function which internally allocates required data structure. After pool/provider is created user should call the appropriate `umfDestroyFooParams` function to tell UMF library that params data structure should be destroyed. 
Option 1 > Option 3 >>>> Option 2

~The same issue we have with other structures in public header. Also we have the same issue with other structures, like memprovider ops - in case of those structures probably option 3 (version field) is the way.~

Edit. I checked code and i found that we already have version in ops structure so it is fine. 

Fix patch:
diff --git a/examples/dram_and_fsdax/dram_and_fsdax.c b/examples/dram_and_fsdax/dram_and_fsdax.c
index 0d21ce620..ef11c186e 100644
--- a/examples/dram_and_fsdax/dram_and_fsdax.c
+++ b/examples/dram_and_fsdax/dram_and_fsdax.c
@@ -48,13 +48,25 @@ static umf_memory_pool_handle_t create_fsdax_pool(const char *path) {
     umf_memory_pool_handle_t pool_fsdax;
     umf_result_t umf_result;
 
-    umf_file_memory_provider_params_t params_fsdax =
-        umfFileMemoryProviderParamsDefault(path);
+    umf_file_memory_provider_params_handle_t params_fsdax = NULL;
+    umf_result = umfFileMemoryProviderParamsCreate(&params_fsdax, path);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(stderr, "Failed to create the File Memory Provider params");
+        return NULL;
+    }
     // FSDAX requires mapping the UMF_MEM_MAP_SHARED flag
-    params_fsdax.visibility = UMF_MEM_MAP_SHARED;
+    umf_result = umfFileMemoryProviderParamsSetVisibility(params_fsdax,
+                                                          UMF_MEM_MAP_SHARED);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(stderr,
+                "Failed to set the visibility of the FSDAX file provider");
+        umfFileMemoryProviderParamsDestroy(params_fsdax);
+        return NULL;
+    }
 
     umf_result = umfMemoryProviderCreate(umfFileMemoryProviderOps(),
-                                         &params_fsdax, &provider_fsdax);
+                                         params_fsdax, &provider_fsdax);
+    umfFileMemoryProviderParamsDestroy(params_fsdax);
     if (umf_result != UMF_RESULT_SUCCESS) {
         fprintf(stderr, "Failed to create the FSDAX file provider");
         return NULL;
diff --git a/include/umf/providers/provider_file_memory.h b/include/umf/providers/provider_file_memory.h
index 4b5b59b81..f652e2cb8 100644
--- a/include/umf/providers/provider_file_memory.h
+++ b/include/umf/providers/provider_file_memory.h
@@ -18,15 +18,45 @@ extern "C" {
 #define UMF_FILE_RESULTS_START_FROM 3000
 /// @endcond
 
-/// @brief Memory provider settings struct
-typedef struct umf_file_memory_provider_params_t {
-    /// a path to the file (of maximum length PATH_MAX characters)
-    const char *path;
-    /// combination of 'umf_mem_protection_flags_t' flags
-    unsigned protection;
-    /// memory visibility mode
-    umf_memory_visibility_t visibility;
-} umf_file_memory_provider_params_t;
+struct umf_file_memory_provider_params_t;
+
+typedef struct umf_file_memory_provider_params_t
+    *umf_file_memory_provider_params_handle_t;
+
+/// @brief  Create a struct to store parameters of the File Memory Provider.
+/// @param  hParams [out] handle to the newly created parameters struct.
+/// @param  path path to the file.
+/// @return UMF_RESULT_SUCCESS on success or appropriate error code on failure.
+umf_result_t umfFileMemoryProviderParamsCreate(
+    umf_file_memory_provider_params_handle_t *hParams, const char *path);
+
+/// @brief  Destroy parameters struct.
+/// @param  hParams handle to the parameters of the File Memory Provider.
+/// @return UMF_RESULT_SUCCESS on success or appropriate error code on failure.
+umf_result_t umfFileMemoryProviderParamsDestroy(
+    umf_file_memory_provider_params_handle_t hParams);
+
+/// @brief  Set the path in the parameters struct.
+/// @param  hParams handle to the parameters of the File Memory Provider.
+/// @param  path path to the file.
+/// @return UMF_RESULT_SUCCESS on success or appropriate error code on failure.
+umf_result_t umfFileMemoryProviderParamsSetPath(
+    umf_file_memory_provider_params_handle_t hParams, const char *path);
+
+/// @brief  Set the protection in the parameters struct.
+/// @param  hParams handle to the parameters of the File Memory Provider.
+/// @param  protection protection. Combination of \p umf_mem_protection_flags_t flags
+/// @return UMF_RESULT_SUCCESS on success or appropriate error code on failure.
+umf_result_t umfFileMemoryProviderParamsSetProtection(
+    umf_file_memory_provider_params_handle_t hParams, unsigned protection);
+
+/// @brief  Set the visibility in the parameters struct.
+/// @param  hParams handle to the parameters of the File Memory Provider.
+/// @param  visibility memory visibility mode.
+/// @return UMF_RESULT_SUCCESS on success or appropriate error code on failure.
+umf_result_t umfFileMemoryProviderParamsSetVisibility(
+    umf_file_memory_provider_params_handle_t hParams,
+    umf_memory_visibility_t visibility);
 
 /// @brief File Memory Provider operation results
 typedef enum umf_file_memory_provider_native_error {
@@ -38,18 +68,6 @@ typedef enum umf_file_memory_provider_native_error {
 
 umf_memory_provider_ops_t *umfFileMemoryProviderOps(void);
 
-/// @brief Create default params for the file memory provider
-static inline umf_file_memory_provider_params_t
-umfFileMemoryProviderParamsDefault(const char *path) {
-    umf_file_memory_provider_params_t params = {
-        path,                                       /* a path to the file */
-        UMF_PROTECTION_READ | UMF_PROTECTION_WRITE, /* protection */
-        UMF_MEM_MAP_PRIVATE,                        /* visibility mode */
-    };
-
-    return params;
-}
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/libumf.def b/src/libumf.def
index 42a924bd7..abb25204c 100644
--- a/src/libumf.def
+++ b/src/libumf.def
@@ -25,6 +25,11 @@ EXPORTS
     umfDevDaxMemoryProviderOps
     umfFree
     umfFileMemoryProviderOps
+    umfFileMemoryProviderParamsCreate
+    umfFileMemoryProviderParamsDestroy
+    umfFileMemoryProviderParamsSetPath
+    umfFileMemoryProviderParamsSetProtection
+    umfFileMemoryProviderParamsSetVisibility
     umfGetIPCHandle
     umfGetLastFailedMemoryProvider
     umfLevelZeroMemoryProviderOps
diff --git a/src/libumf.map b/src/libumf.map
index 443194e55..3a5e73f44 100644
--- a/src/libumf.map
+++ b/src/libumf.map
@@ -19,6 +19,11 @@ UMF_1.0 {
         umfDevDaxMemoryProviderOps;
         umfFree;
         umfFileMemoryProviderOps;
+        umfFileMemoryProviderParamsCreate;
+        umfFileMemoryProviderParamsDestroy;
+        umfFileMemoryProviderParamsSetPath;
+        umfFileMemoryProviderParamsSetProtection;
+        umfFileMemoryProviderParamsSetVisibility;
         umfGetIPCHandle;
         umfGetLastFailedMemoryProvider;
         umfLevelZeroMemoryProviderOps;
diff --git a/src/provider/provider_file_memory.c b/src/provider/provider_file_memory.c
index 09d50a625..32383a5ec 100644
--- a/src/provider/provider_file_memory.c
+++ b/src/provider/provider_file_memory.c
@@ -25,10 +25,46 @@ umf_memory_provider_ops_t *umfFileMemoryProviderOps(void) {
     return NULL;
 }
 
+umf_result_t umfFileMemoryProviderParamsCreate(
+    umf_file_memory_provider_params_handle_t *hParams, const char *path) {
+    (void)hParams;
+    (void)path;
+    return UMF_RESULT_ERROR_NOT_SUPPORTED;
+}
+
+umf_result_t umfFileMemoryProviderParamsDestroy(
+    umf_file_memory_provider_params_handle_t hParams) {
+    (void)hParams;
+    return UMF_RESULT_ERROR_NOT_SUPPORTED;
+}
+
+umf_result_t umfFileMemoryProviderParamsSetPath(
+    umf_file_memory_provider_params_handle_t hParams, const char *path) {
+    (void)hParams;
+    (void)path;
+    return UMF_RESULT_ERROR_NOT_SUPPORTED;
+}
+
+umf_result_t umfFileMemoryProviderParamsSetProtection(
+    umf_file_memory_provider_params_handle_t hParams, unsigned protection) {
+    (void)hParams;
+    (void)protection;
+    return UMF_RESULT_ERROR_NOT_SUPPORTED;
+}
+
+umf_result_t umfFileMemoryProviderParamsSetVisibility(
+    umf_file_memory_provider_params_handle_t hParams,
+    umf_memory_visibility_t visibility) {
+    (void)hParams;
+    (void)visibility;
+    return UMF_RESULT_ERROR_NOT_SUPPORTED;
+}
+
 #else // !defined(_WIN32) && !defined(UMF_NO_HWLOC)
 
 #include "base_alloc_global.h"
 #include "critnib.h"
+#include "libumf.h"
 #include "utils_common.h"
 #include "utils_concurrency.h"
 #include "utils_log.h"
@@ -67,6 +103,13 @@ typedef struct file_memory_provider_t {
     critnib *fd_offset_map;
 } file_memory_provider_t;
 
+// File Memory Provider settings struct
+typedef struct umf_file_memory_provider_params_t {
+    char *path;
+    unsigned protection;
+    umf_memory_visibility_t visibility;
+} umf_file_memory_provider_params_t;
+
 typedef struct file_last_native_error_t {
     int32_t native_error;
     int errno_value;
@@ -748,4 +791,108 @@ umf_memory_provider_ops_t *umfFileMemoryProviderOps(void) {
     return &UMF_FILE_MEMORY_PROVIDER_OPS;
 }
 
+umf_result_t umfFileMemoryProviderParamsCreate(
+    umf_file_memory_provider_params_handle_t *hParams, const char *path) {
+    libumfInit();
+    if (hParams == NULL) {
+        LOG_ERR("File Memory Provider params handle is NULL");
+        return UMF_RESULT_ERROR_INVALID_ARGUMENT;
+    }
+
+    if (path == NULL) {
+        LOG_ERR("File path is NULL");
+        return UMF_RESULT_ERROR_INVALID_ARGUMENT;
+    }
+
+    umf_file_memory_provider_params_handle_t params =
+        umf_ba_global_alloc(sizeof(*params));
+    if (params == NULL) {
+        LOG_ERR("allocating memory for File Memory Provider params failed");
+        return UMF_RESULT_ERROR_OUT_OF_HOST_MEMORY;
+    }
+
+    params->path = NULL;
+    params->protection = UMF_PROTECTION_READ | UMF_PROTECTION_WRITE;
+    params->visibility = UMF_MEM_MAP_PRIVATE;
+
+    umf_result_t res = umfFileMemoryProviderParamsSetPath(params, path);
+    if (res != UMF_RESULT_SUCCESS) {
+        umf_ba_global_free(params);
+        return res;
+    }
+
+    *hParams = params;
+
+    return UMF_RESULT_SUCCESS;
+}
+
+umf_result_t umfFileMemoryProviderParamsDestroy(
+    umf_file_memory_provider_params_handle_t hParams) {
+    if (hParams != NULL) {
+        umf_ba_global_free(hParams->path);
+        umf_ba_global_free(hParams);
+    }
+
+    return UMF_RESULT_SUCCESS;
+}
+
+umf_result_t umfFileMemoryProviderParamsSetPath(
+    umf_file_memory_provider_params_handle_t hParams, const char *path) {
+    if (hParams == NULL) {
+        LOG_ERR("File Memory Provider params handle is NULL");
+        return UMF_RESULT_ERROR_INVALID_ARGUMENT;
+    }
+
+    if (path == NULL) {
+        LOG_ERR("File path is NULL");
+        return UMF_RESULT_ERROR_INVALID_ARGUMENT;
+    }
+
+    size_t len = strlen(path);
+    if (len == 0) {
+        LOG_ERR("File path is empty");
+        return UMF_RESULT_ERROR_INVALID_ARGUMENT;
+    }
+
+    len += 1; // for the null terminator
+    char *new_path = NULL;
+    new_path = umf_ba_global_alloc(len);
+    if (new_path == NULL) {
+        LOG_ERR("allocating memory for the file path failed");
+        return UMF_RESULT_ERROR_OUT_OF_HOST_MEMORY;
+    }
+
+    strncpy(new_path, path, len);
+
+    umf_ba_global_free(hParams->path);
+    hParams->path = new_path;
+
+    return UMF_RESULT_SUCCESS;
+}
+
+umf_result_t umfFileMemoryProviderParamsSetProtection(
+    umf_file_memory_provider_params_handle_t hParams, unsigned protection) {
+    if (hParams == NULL) {
+        LOG_ERR("File Memory Provider params handle is NULL");
+        return UMF_RESULT_ERROR_INVALID_ARGUMENT;
+    }
+
+    hParams->protection = protection;
+
+    return UMF_RESULT_SUCCESS;
+}
+
+umf_result_t umfFileMemoryProviderParamsSetVisibility(
+    umf_file_memory_provider_params_handle_t hParams,
+    umf_memory_visibility_t visibility) {
+    if (hParams == NULL) {
+        LOG_ERR("File Memory Provider params handle is NULL");
+        return UMF_RESULT_ERROR_INVALID_ARGUMENT;
+    }
+
+    hParams->visibility = visibility;
+
+    return UMF_RESULT_SUCCESS;
+}
+
 #endif // !defined(_WIN32) && !defined(UMF_NO_HWLOC)


Test patch:
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 1f2ef5959..051e75949 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -338,6 +338,11 @@ if(LINUX AND (NOT UMF_DISABLE_HWLOC)) # OS-specific functions are implemented
     if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND UMF_BUILD_FUZZTESTS)
         add_subdirectory(fuzz)
     endif()
+else()
+    add_umf_test(
+        NAME provider_file_memory_not_impl
+        SRCS provider_file_memory_not_impl.cpp
+        LIBS ${UMF_UTILS_FOR_TEST})
 endif()
 
 if(UMF_BUILD_GPU_TESTS AND UMF_BUILD_LEVEL_ZERO_PROVIDER)
diff --git a/test/ipc_file_prov_consumer.c b/test/ipc_file_prov_consumer.c
index 6333552a2..0c50991a9 100644
--- a/test/ipc_file_prov_consumer.c
+++ b/test/ipc_file_prov_consumer.c
@@ -22,16 +22,36 @@ int main(int argc, char *argv[]) {
         return -1;
     }
 
+    int ret = 0;
     int port = atoi(argv[1]);
     char *file_name = argv[2];
 
-    umf_file_memory_provider_params_t file_params;
-    file_params = umfFileMemoryProviderParamsDefault(file_name);
-    file_params.visibility = UMF_MEM_MAP_SHARED;
+    umf_file_memory_provider_params_handle_t file_params = NULL;
+    umf_result_t umf_result =
+        umfFileMemoryProviderParamsCreate(&file_params, file_name);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(
+            stderr,
+            "[consumer] ERROR: creating File Memory Provider params failed\n");
+        return -1;
+    }
+
+    umf_result = umfFileMemoryProviderParamsSetVisibility(file_params,
+                                                          UMF_MEM_MAP_SHARED);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(stderr, "[consumer] ERROR: setting File Memory Provider "
+                        "visibility failed\n");
+        ret = -1;
+        goto destroy_provider_params;
+    }
 
     void *pool_params = NULL;
 
-    return run_consumer(port, umfScalablePoolOps(), pool_params,
-                        umfFileMemoryProviderOps(), &file_params, memcopy,
-                        NULL);
+    ret = run_consumer(port, umfScalablePoolOps(), pool_params,
+                       umfFileMemoryProviderOps(), file_params, memcopy, NULL);
+
+destroy_provider_params:
+    umfFileMemoryProviderParamsDestroy(file_params);
+
+    return ret;
 }
diff --git a/test/ipc_file_prov_producer.c b/test/ipc_file_prov_producer.c
index efcbdd3bf..c620437e0 100644
--- a/test/ipc_file_prov_producer.c
+++ b/test/ipc_file_prov_producer.c
@@ -22,16 +22,36 @@ int main(int argc, char *argv[]) {
         return -1;
     }
 
+    int ret = 0;
     int port = atoi(argv[1]);
     char *file_name = argv[2];
 
-    umf_file_memory_provider_params_t file_params;
-    file_params = umfFileMemoryProviderParamsDefault(file_name);
-    file_params.visibility = UMF_MEM_MAP_SHARED;
+    umf_file_memory_provider_params_handle_t file_params = NULL;
+    umf_result_t umf_result =
+        umfFileMemoryProviderParamsCreate(&file_params, file_name);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(
+            stderr,
+            "[producer] ERROR: creating File Memory Provider params failed\n");
+        return -1;
+    }
+
+    umf_result = umfFileMemoryProviderParamsSetVisibility(file_params,
+                                                          UMF_MEM_MAP_SHARED);
+    if (umf_result != UMF_RESULT_SUCCESS) {
+        fprintf(stderr, "[producer] ERROR: setting File Memory Provider "
+                        "visibility failed\n");
+        ret = -1;
+        goto destroy_provider_params;
+    }
 
     void *pool_params = NULL;
 
-    return run_producer(port, umfScalablePoolOps(), pool_params,
-                        umfFileMemoryProviderOps(), &file_params, memcopy,
-                        NULL);
+    ret = run_producer(port, umfScalablePoolOps(), pool_params,
+                       umfFileMemoryProviderOps(), file_params, memcopy, NULL);
+
+destroy_provider_params:
+    umfFileMemoryProviderParamsDestroy(file_params);
+
+    return ret;
 }
diff --git a/test/pools/jemalloc_coarse_file.cpp b/test/pools/jemalloc_coarse_file.cpp
index 7eb904903..74ad36d56 100644
--- a/test/pools/jemalloc_coarse_file.cpp
+++ b/test/pools/jemalloc_coarse_file.cpp
@@ -7,11 +7,27 @@
 
 #include "pool_coarse.hpp"
 
+using file_params_unique_handle_t =
+    std::unique_ptr<umf_file_memory_provider_params_t,
+                    decltype(&umfFileMemoryProviderParamsDestroy)>;
+
+file_params_unique_handle_t get_file_params_default(char *path) {
+    umf_file_memory_provider_params_handle_t file_params = NULL;
+    umf_result_t res = umfFileMemoryProviderParamsCreate(&file_params, path);
+    if (res != UMF_RESULT_SUCCESS) {
+        throw std::runtime_error(
+            "Failed to create File Memory Provider params");
+    }
+
+    return file_params_unique_handle_t(file_params,
+                                       &umfFileMemoryProviderParamsDestroy);
+}
+
 auto coarseParams = umfCoarseMemoryProviderParamsDefault();
-auto fileParams = umfFileMemoryProviderParamsDefault(FILE_PATH);
+file_params_unique_handle_t fileParams = get_file_params_default(FILE_PATH);
 
 INSTANTIATE_TEST_SUITE_P(jemallocCoarseFileTest, umfPoolTest,
                          ::testing::Values(poolCreateExtParams{
                              umfJemallocPoolOps(), nullptr,
-                             umfFileMemoryProviderOps(), &fileParams,
+                             umfFileMemoryProviderOps(), fileParams.get(),
                              &coarseParams}));
diff --git a/test/pools/scalable_coarse_file.cpp b/test/pools/scalable_coarse_file.cpp
index b83a12338..b45c112be 100644
--- a/test/pools/scalable_coarse_file.cpp
+++ b/test/pools/scalable_coarse_file.cpp
@@ -7,11 +7,27 @@
 
 #include "pool_coarse.hpp"
 
+using file_params_unique_handle_t =
+    std::unique_ptr<umf_file_memory_provider_params_t,
+                    decltype(&umfFileMemoryProviderParamsDestroy)>;
+
+file_params_unique_handle_t get_file_params_default(char *path) {
+    umf_file_memory_provider_params_handle_t file_params = NULL;
+    umf_result_t res = umfFileMemoryProviderParamsCreate(&file_params, path);
+    if (res != UMF_RESULT_SUCCESS) {
+        throw std::runtime_error(
+            "Failed to create File Memory Provider params");
+    }
+
+    return file_params_unique_handle_t(file_params,
+                                       &umfFileMemoryProviderParamsDestroy);
+}
+
 auto coarseParams = umfCoarseMemoryProviderParamsDefault();
-auto fileParams = umfFileMemoryProviderParamsDefault(FILE_PATH);
+file_params_unique_handle_t fileParams = get_file_params_default(FILE_PATH);
 
 INSTANTIATE_TEST_SUITE_P(scalableCoarseFileTest, umfPoolTest,
                          ::testing::Values(poolCreateExtParams{
                              umfScalablePoolOps(), nullptr,
-                             umfFileMemoryProviderOps(), &fileParams,
+                             umfFileMemoryProviderOps(), fileParams.get(),
                              &coarseParams}));
diff --git a/test/provider_file_memory.cpp b/test/provider_file_memory.cpp
index eba7e8205..d3124aa11 100644
--- a/test/provider_file_memory.cpp
+++ b/test/provider_file_memory.cpp
@@ -136,11 +136,17 @@ TEST_F(test, test_if_mapped_with_MAP_SYNC) {
         GTEST_SKIP() << "Test skipped, UMF_TESTS_FSDAX_PATH is not set";
     }
 
-    auto params = umfFileMemoryProviderParamsDefault(path);
-    params.visibility = UMF_MEM_MAP_SHARED;
+    umf_file_memory_provider_params_handle_t params = nullptr;
+    umf_result = umfFileMemoryProviderParamsCreate(&params, path);
+    ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS);
+    ASSERT_NE(params, nullptr);
 
-    umf_result = umfMemoryProviderCreate(umfFileMemoryProviderOps(), &params,
-                                         &hProvider);
+    umf_result =
+        umfFileMemoryProviderParamsSetVisibility(params, UMF_MEM_MAP_SHARED);
+    ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS);
+
+    umf_result =
+        umfMemoryProviderCreate(umfFileMemoryProviderOps(), params, &hProvider);
     ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS);
     ASSERT_NE(hProvider, nullptr);
 
@@ -163,23 +169,52 @@ TEST_F(test, test_if_mapped_with_MAP_SYNC) {
 
 // positive tests using test_alloc_free_success
 
-umf_file_memory_provider_params_t file_params_default =
-    umfFileMemoryProviderParamsDefault(FILE_PATH);
+using file_params_unique_handle_t =
+    std::unique_ptr<umf_file_memory_provider_params_t,
+                    decltype(&umfFileMemoryProviderParamsDestroy)>;
+
+file_params_unique_handle_t get_file_params_default(char *path) {
+    umf_file_memory_provider_params_handle_t file_params = NULL;
+    umf_result_t res = umfFileMemoryProviderParamsCreate(&file_params, path);
+    if (res != UMF_RESULT_SUCCESS) {
+        throw std::runtime_error(
+            "Failed to create File Memory Provider params");
+    }
+
+    return file_params_unique_handle_t(file_params,
+                                       &umfFileMemoryProviderParamsDestroy);
+}
+
+file_params_unique_handle_t file_params_default =
+    get_file_params_default(FILE_PATH);
+
+file_params_unique_handle_t get_file_params_shared(char *path) {
+    umf_file_memory_provider_params_handle_t file_params = NULL;
+    umf_result_t res = umfFileMemoryProviderParamsCreate(&file_params, path);
+    if (res != UMF_RESULT_SUCCESS) {
+        throw std::runtime_error(
+            "Failed to create File Memory Provider params");
+    }
+
+    res = umfFileMemoryProviderParamsSetVisibility(file_params,
+                                                   UMF_MEM_MAP_SHARED);
+    if (res != UMF_RESULT_SUCCESS) {
+        umfFileMemoryProviderParamsDestroy(file_params);
+        throw std::runtime_error("Failed to set visibility to shared for File "
+                                 "Memory Provider params");
+    }
 
-umf_file_memory_provider_params_t get_file_params_shared(char *path) {
-    umf_file_memory_provider_params_t file_params =
-        umfFileMemoryProviderParamsDefault(path);
-    file_params.visibility = UMF_MEM_MAP_SHARED;
-    return file_params;
+    return file_params_unique_handle_t(file_params,
+                                       &umfFileMemoryProviderParamsDestroy);
 }
 
-umf_file_memory_provider_params_t file_params_shared =
+file_params_unique_handle_t file_params_shared =
     get_file_params_shared(FILE_PATH);
 
 INSTANTIATE_TEST_SUITE_P(fileProviderTest, FileProviderParamsDefault,
                          ::testing::Values(providerCreateExtParams{
                              umfFileMemoryProviderOps(),
-                             &file_params_default}));
+                             file_params_default.get()}));
 
 TEST_P(FileProviderParamsDefault, create_destroy) {}
 
@@ -341,14 +376,74 @@ TEST_P(FileProviderParamsDefault, free_NULL) {
 
 // other negative tests
 
+TEST_F(test, params_null_handle) {
+    umf_result_t umf_result =
+        umfFileMemoryProviderParamsCreate(nullptr, FILE_PATH);
+    ASSERT_EQ(umf_result, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+
+    umf_result = umfFileMemoryProviderParamsDestroy(nullptr);
+    ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS);
+
+    umf_result = umfFileMemoryProviderParamsSetPath(nullptr, FILE_PATH);
+    ASSERT_EQ(umf_result, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+
+    umf_result =
+        umfFileMemoryProviderParamsSetProtection(nullptr, UMF_PROTECTION_READ);
+    ASSERT_EQ(umf_result, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+
+    umf_result =
+        umfFileMemoryProviderParamsSetVisibility(nullptr, UMF_MEM_MAP_PRIVATE);
+    ASSERT_EQ(umf_result, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+}
+
 TEST_F(test, create_empty_path) {
-    umf_memory_provider_handle_t hProvider = nullptr;
     const char *path = "";
-    auto wrong_params = umfFileMemoryProviderParamsDefault((char *)path);
-    auto ret = umfMemoryProviderCreate(umfFileMemoryProviderOps(),
-                                       &wrong_params, &hProvider);
-    EXPECT_EQ(ret, UMF_RESULT_ERROR_INVALID_ARGUMENT);
-    EXPECT_EQ(hProvider, nullptr);
+
+    umf_file_memory_provider_params_handle_t wrong_params = nullptr;
+    umf_result_t umf_result =
+        umfFileMemoryProviderParamsCreate(&wrong_params, path);
+    ASSERT_EQ(umf_result, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+    ASSERT_EQ(wrong_params, nullptr);
+}
+
+TEST_F(test, create_null_path) {
+    const char *path = nullptr;
+
+    umf_file_memory_provider_params_handle_t wrong_params = nullptr;
+    umf_result_t umf_result =
+        umfFileMemoryProviderParamsCreate(&wrong_params, path);
+    ASSERT_EQ(umf_result, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+    ASSERT_EQ(wrong_params, nullptr);
+}
+
+TEST_F(test, set_empty_path) {
+    const char *empty_path = "";
+
+    umf_file_memory_provider_params_handle_t params = nullptr;
+    umf_result_t umf_result =
+        umfFileMemoryProviderParamsCreate(&params, FILE_PATH);
+    ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS);
+
+    umf_result = umfFileMemoryProviderParamsSetPath(params, empty_path);
+    ASSERT_EQ(umf_result, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+
+    umf_result = umfFileMemoryProviderParamsDestroy(params);
+    ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS);
+}
+
+TEST_F(test, set_null_path) {
+    const char *null_path = nullptr;
+
+    umf_file_memory_provider_params_handle_t params = nullptr;
+    umf_result_t umf_result =
+        umfFileMemoryProviderParamsCreate(&params, FILE_PATH);
+    ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS);
+
+    umf_result = umfFileMemoryProviderParamsSetPath(params, null_path);
+    ASSERT_EQ(umf_result, UMF_RESULT_ERROR_INVALID_ARGUMENT);
+
+    umf_result = umfFileMemoryProviderParamsDestroy(params);
+    ASSERT_EQ(umf_result, UMF_RESULT_SUCCESS);
 }
 
 TEST_P(FileProviderParamsDefault, free_INVALID_POINTER_SIZE_GT_0) {
@@ -376,7 +471,8 @@ TEST_P(FileProviderParamsDefault, purge_force_INVALID_POINTER) {
 
 INSTANTIATE_TEST_SUITE_P(fileProviderTest, FileProviderParamsShared,
                          ::testing::Values(providerCreateExtParams{
-                             umfFileMemoryProviderOps(), &file_params_shared}));
+                             umfFileMemoryProviderOps(),
+                             file_params_shared.get()}));
 
 TEST_P(FileProviderParamsShared, IPC_base_success_test) {
     umf_result_t umf_result;
diff --git a/test/provider_file_memory_ipc.cpp b/test/provider_file_memory_ipc.cpp
index 5bba5de50..ee7ab6c8f 100644
--- a/test/provider_file_memory_ipc.cpp
+++ b/test/provider_file_memory_ipc.cpp
@@ -17,24 +17,55 @@ using umf_test::test;
 
 #define FILE_PATH ((char *)"tmp_file")
 
-umf_file_memory_provider_params_t get_file_params_shared(char *path) {
-    umf_file_memory_provider_params_t file_params =
-        umfFileMemoryProviderParamsDefault(path);
-    file_params.visibility = UMF_MEM_MAP_SHARED;
-    return file_params;
+using file_params_unique_handle_t =
+    std::unique_ptr<umf_file_memory_provider_params_t,
+                    decltype(&umfFileMemoryProviderParamsDestroy)>;
+
+file_params_unique_handle_t get_file_params_shared(char *path) {
+    umf_file_memory_provider_params_handle_t file_params = NULL;
+    umf_result_t res = umfFileMemoryProviderParamsCreate(&file_params, path);
+    if (res != UMF_RESULT_SUCCESS) {
+        throw std::runtime_error(
+            "Failed to create File Memory Provider params");
+    }
+
+    res = umfFileMemoryProviderParamsSetVisibility(file_params,
+                                                   UMF_MEM_MAP_SHARED);
+    if (res != UMF_RESULT_SUCCESS) {
+        umfFileMemoryProviderParamsDestroy(file_params);
+        throw std::runtime_error("Failed to set visibility to shared for File "
+                                 "Memory Provider params");
+    }
+
+    return file_params_unique_handle_t(file_params,
+                                       &umfFileMemoryProviderParamsDestroy);
 }
 
-umf_file_memory_provider_params_t file_params_shared =
+file_params_unique_handle_t file_params_shared =
     get_file_params_shared(FILE_PATH);
 
-umf_file_memory_provider_params_t get_file_params_fsdax(char *path) {
-    umf_file_memory_provider_params_t file_params =
-        umfFileMemoryProviderParamsDefault(path);
-    file_params.visibility = UMF_MEM_MAP_SHARED;
-    return file_params;
+file_params_unique_handle_t get_file_params_fsdax(char *path) {
+    umf_file_memory_provider_params_handle_t file_params = NULL;
+    umf_result_t res = umfFileMemoryProviderParamsCreate(&file_params, path);
+    if (res != UMF_RESULT_SUCCESS) {
+        //test will be skipped.
+        return file_params_unique_handle_t(nullptr,
+                                           &umfFileMemoryProviderParamsDestroy);
+    }
+
+    res = umfFileMemoryProviderParamsSetVisibility(file_params,
+                                                   UMF_MEM_MAP_SHARED);
+    if (res != UMF_RESULT_SUCCESS) {
+        umfFileMemoryProviderParamsDestroy(file_params);
+        throw std::runtime_error("Failed to set visibility to shared for File "
+                                 "Memory Provider params");
+    }
+
+    return file_params_unique_handle_t(file_params,
+                                       &umfFileMemoryProviderParamsDestroy);
 }
 
-umf_file_memory_provider_params_t file_params_fsdax =
+file_params_unique_handle_t file_params_fsdax =
     get_file_params_fsdax(getenv("UMF_TESTS_FSDAX_PATH"));
 
 HostMemoryAccessor hostAccessor;
@@ -42,14 +73,14 @@ HostMemoryAccessor hostAccessor;
 static std::vector<ipcTestParams> ipcManyPoolsTestParamsList = {
 // TODO: enable it when sizes of allocations in ipcFixtures.hpp are fixed
 //    {umfProxyPoolOps(), nullptr, umfFileMemoryProviderOps(),
-//     &file_params_shared, &hostAccessor, true},
+//     file_params_shared.get(), &hostAccessor, true},
 #ifdef UMF_POOL_JEMALLOC_ENABLED
     {umfJemallocPoolOps(), nullptr, umfFileMemoryProviderOps(),
-     &file_params_shared, &hostAccessor, false},
+     file_params_shared.get(), &hostAccessor, false},
 #endif
 #ifdef UMF_POOL_SCALABLE_ENABLED
     {umfScalablePoolOps(), nullptr, umfFileMemoryProviderOps(),
-     &file_params_shared, &hostAccessor, false},
+     file_params_shared.get(), &hostAccessor, false},
 #endif
 };
 
@@ -65,14 +96,14 @@ static std::vector<ipcTestParams> getIpcFsDaxTestParamsList(void) {
     ipcFsDaxTestParamsList = {
 // TODO: enable it when sizes of allocations in ipcFixtures.hpp are fixed
 //        {umfProxyPoolOps(), nullptr, umfFileMemoryProviderOps(),
-//         &file_params_fsdax, &hostAccessor, true},
+//         file_params_fsdax.get(), &hostAccessor, true},
 #ifdef UMF_POOL_JEMALLOC_ENABLED
         {umfJemallocPoolOps(), nullptr, umfFileMemoryProviderOps(),
-         &file_params_fsdax, &hostAccessor, false},
+         file_params_fsdax.get(), &hostAccessor, false},
 #endif
 #ifdef UMF_POOL_SCALABLE_ENABLED
         {umfScalablePoolOps(), nullptr, umfFileMemoryProviderOps(),
-         &file_params_fsdax, &hostAccessor, false},
+         file_params_fsdax.get(), &hostAccessor, false},
 #endif
     };
 
diff --git a/test/provider_file_memory_not_impl.cpp b/test/provider_file_memory_not_impl.cpp
new file mode 100644
index 000000000..c82b8163c
--- /dev/null
+++ b/test/provider_file_memory_not_impl.cpp
@@ -0,0 +1,33 @@
+// Copyright (C) 2024 Intel Corporation
+// Under the Apache License v2.0 with LLVM Exceptions. See LICENSE.TXT.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "base.hpp"
+
+#include <umf/providers/provider_file_memory.h>
+
+using umf_test::test;
+
+TEST_F(test, file_provider_not_implemented) {
+    umf_file_memory_provider_params_handle_t params = nullptr;
+    umf_result_t umf_result =
+        umfFileMemoryProviderParamsCreate(&params, "path");
+    EXPECT_EQ(umf_result, UMF_RESULT_ERROR_NOT_SUPPORTED);
+    EXPECT_EQ(params, nullptr);
+
+    umf_result = umfFileMemoryProviderParamsDestroy(nullptr);
+    EXPECT_EQ(umf_result, UMF_RESULT_ERROR_NOT_SUPPORTED);
+
+    umf_result = umfFileMemoryProviderParamsSetPath(nullptr, "path");
+    EXPECT_EQ(umf_result, UMF_RESULT_ERROR_NOT_SUPPORTED);
+
+    umf_result = umfFileMemoryProviderParamsSetProtection(nullptr, 0);
+    EXPECT_EQ(umf_result, UMF_RESULT_ERROR_NOT_SUPPORTED);
+
+    umf_result =
+        umfFileMemoryProviderParamsSetVisibility(nullptr, UMF_MEM_MAP_PRIVATE);
+    EXPECT_EQ(umf_result, UMF_RESULT_ERROR_NOT_SUPPORTED);
+
+    umf_memory_provider_ops_t *ops = umfFileMemoryProviderOps();
+    EXPECT_EQ(ops, nullptr);
+}
\ No newline at end of file