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.