Open vinser52 opened 4 weeks ago
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.
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: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:A quick fix for the issue was to init the config data structure with
0
and than assign the required fields: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 correspondingfoo_config_t
structure that contains an int field in the 1st version:Now in the 2nd version of UMF we extend the
foo_params_t
structure with additional field: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:
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:
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.
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 callumfFooParamsInit(¶ms)
function.Between Options 2 and 3, I prefer Option 3 because of two things:
umfMemoryProviderCreate
API remains unchanged.Implementation details
TBD