Skip to content

[BUG] OnlineOptim.MULTIPROCESS fails with spawn/forkserver start methods #1082

Description

@mickaelbegon

Description

OnlineOptim.MULTIPROCESS fails when Python uses a multiprocessing start method that requires the process target to be pickled (spawn or forkserver).

This is now directly visible on Linux with Python 3.14, where forkserver replaced fork as the default start method.

Reproduction

Run an OCP containing CasADi symbolic expressions with:

solver = Solver.IPOPT(online_optim=OnlineOptim.MULTIPROCESS)
sol = ocp.solve(solver)

Using Python 3.14 on Linux raises an exception during:

self.plot_process.start()

The traceback ends with:

Exception: Cannot pickle SX objects without a casadi context
when serializing dict item 'dt'
when serializing NonLinearProgram
when serializing OptimalControlProgram
when serializing ProcessPlotter

Root-cause hypothesis

OnlineCallbackMultiprocess creates a process whose target is a ProcessPlotter containing the complete OCP:

self.plotter = self.ProcessPlotter(self.ocp)
self.plot_process = mp.Process(target=self.plotter, ...)

With fork, the child inherits the parent memory and the OCP is not serialized. With spawn or forkserver, the process target must be pickled, including the OCP and its CasADi SX/MX expressions.

A local comparison found:

  • CasADi 3.6.7: regular pickling of an SX fails with TypeError: cannot pickle 'SwigPyObject' object.
  • CasADi 3.7.2: regular pickling fails with the explicit CasADi-context error, while global_pickle_context() plus global_unpickle_context() can serialize and restore a simple SX graph.

CasADi added this contextual mechanism in casadi/casadi@e1bb6d7, associated with casadi/casadi#3624. Python documents the POSIX default change to forkserver in Python 3.14.

This suggests a latent dependency on fork: Python 3.14 makes serialization mandatory and CasADi determines the resulting error. This hypothesis should still be confirmed against Bioptim's complete OCP graph and supported environments.

The current OnlineOptim.DEFAULT uses MULTIPROCESS_SERVER, which avoids this path. The problem concerns explicit use of OnlineOptim.MULTIPROCESS.

Possible directions

  • Serialize only the plotting payload before starting the child, then deserialize it inside a top-level process entry point.
  • Investigate CasADi's global pickle/unpickle contexts for the complete required graph.
  • Reuse the OcpSerializable representation used by the server backend.
  • If safe support is not practical, detect unsupported start methods early and provide an actionable error recommending OnlineOptim.MULTIPROCESS_SERVER.

Explicitly forcing fork could confirm the diagnosis or provide a temporary Linux workaround, but should probably not be the long-term solution because Python moved away from it for multithreading safety.

Acceptance criteria

  • OnlineOptim.MULTIPROCESS either works with forkserver/spawn, or fails early with a clear documented fallback.
  • A functional test actually starts the online callback using a non-fork context.
  • Python 3.14 is covered explicitly.
  • Supported combinations of operating system, Python start method and OnlineOptim backend are documented.

@EveCharbie, could you please confirm or refine the reproduction details from your new environment, especially the Python and CasADi versions?

@pariterre, could you please complete or nuance the CasADi analysis above? In particular, does the contextual pickling mechanism change the implementation options you had in mind?

Could you both also indicate how you would prioritize this relative to improving feature parity for plot_ipopt_outputs and plot_check_conditioning on the server backend?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions