Thread Safe version

Hi all,

I have a question regarding the thread safety of ACADOS. I see that the generated solvers are using mainly global variables, which are not thread-safe. I would like to run a bunch of solvers in parallel, so they can not share any memory. Intuitively, a solver should be able to run as a single instance and should not be dependent on external/global variables. Is there a way to compile a solver that will be thread-safe? Or what needs to be changed? Are the underlying solvers thread-safe?

Thanks
Juraj

Hi,

I do not know much about thread safety, but let me try to answer:

I get that the template based interface is not thread safe as it is because of the global data.
I think it should be relatively easy to modify it, such that it works without global data.
The idea would be to make the create function return a pointer to a struct that contains the pointers to all the substructures (the ones that are currently global data).
This one would have to be passed to all the functions interacting with the solver later on.

Do you think there is an issue wrt thread safety besides global data?

If it is just about running multiple solvers in parallel, would it be enough to prevent name clashing in the global data?
This could be done even easier, by adding model_name prefix to all the global data of the solver.

Best,
Jonathan

Thanks for the info!

Changing name of the variables would solve part of the issue, we could run different solvers in parallel but not the same one.

I think a good solution would be that acados_create will not create any variables, since creating variables in a function and returning pointer to them is not the safest thing. How about acados_create and any other function would accept a structure, containing all the global data needed. Then user can define how to construct the objects and they would be just filled in acados_create, so the signature of the function would change to int acados_create(all_global_data* data) . In this way, the user have the option to decide whether to use global data structure or local.

I think the struct could look like this:

struct all_global_data{
// ** global data **
ocp_nlp_in * nlp_in;
ocp_nlp_out * nlp_out;
ocp_nlp_solver * nlp_solver;
void * nlp_opts;
ocp_nlp_plan * nlp_solver_plan;
ocp_nlp_config * nlp_config;
ocp_nlp_dims * nlp_dims;
external_function_param_casadi * forw_vde_casadi;
external_function_param_casadi * expl_ode_fun;

external_function_param_casadi * nl_constr_h_fun_jac;
external_function_param_casadi * nl_constr_h_fun;

external_function_param_casadi nl_constr_h_e_fun_jac;
external_function_param_casadi nl_constr_h_e_fun;

external_function_param_casadi * cost_y_fun;
external_function_param_casadi * cost_y_fun_jac_ut_xt;
external_function_param_casadi * cost_y_hess;
external_function_param_casadi cost_y_e_fun;
external_function_param_casadi cost_y_e_fun_jac_ut_xt;
external_function_param_casadi cost_y_e_hess;
};

What do you think?

Hi @kabzo,

I now have some time to think about this again.
What you suggest makes sense to me.
For now I don’t have something better in mind.

I think, we will basically get rid of the if template logic, that determines, which function pointers are actually needed depending on the integrator, cost and constraint types, see https://github.com/acados/acados/blob/d0ef292abba784be327ada4ccd6392d1a337bc43/interfaces/acados_template/acados_template/c_templates_tera/acados_solver.in.h#L62-L139
and just have null pointers in the non relevant fields.

Any other thoughts on that?

Just a small comment on this topic.

From bottom up, BLASFEO, HPIPM and acados core are 100% thread safe, as they use no global variables or static memory at all.
So using just these components, multiple solvers can co-exist.

Then yes it’s just a matter of what the interfaces on top are designed to do.
And there it is mainly a matter of tradeoffs with user friendliness.
The most common case by far is to use just a single solver, and priority goes into making the solver creation process “as automated as possible”.
On the other hand, leaving to the end user the complete responsibility of allocating all memory requires more work but gives the expert user complete control over such issues.

Hi,

I made some changes in the C-templates to facilitate using multiple DIFFERENT template-solvers (single- or multi-threading) in one application. For this reason I changed the interface to contain the solver name and introduced a struct for encapsulating all global data as suggested in the posts above.

In order to support launching multiple threads of the SAME template-solver I would also suggest to provide an additional version of all template functions (create, free, solve, and update_params) with a pointer to the data structure being an argument. However, since I do not need this right now, I’ve not implemented it yet.

What is your opinion on that?

BR
Martin

Hi Martin,

thanks a lot for this contribution! :pray:
I am now checking out this commit:

On first sight, it looks really good and I was able to successfully run all Matlab tests.
From your posts, it seems that you are a Matlab user.

I will have a closer look now (the commit is quite big), and try to do the corresponding changes for the Python interface.

Best & Thanks again,
Jonathan

Hi,

This PR makes the use of the template based solvers without global variables possible.


I would like to merge this early next week, feedback is very welcome.

Best,
Jonathan