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?
Description
OnlineOptim.MULTIPROCESSfails when Python uses a multiprocessing start method that requires the process target to be pickled (spawnorforkserver).This is now directly visible on Linux with Python 3.14, where
forkserverreplacedforkas the default start method.Reproduction
Run an OCP containing CasADi symbolic expressions with:
Using Python 3.14 on Linux raises an exception during:
The traceback ends with:
Root-cause hypothesis
OnlineCallbackMultiprocesscreates a process whose target is aProcessPlottercontaining the complete OCP:With
fork, the child inherits the parent memory and the OCP is not serialized. Withspawnorforkserver, the process target must be pickled, including the OCP and its CasADi SX/MX expressions.A local comparison found:
TypeError: cannot pickle 'SwigPyObject' object.global_pickle_context()plusglobal_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
forkserverin 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.DEFAULTusesMULTIPROCESS_SERVER, which avoids this path. The problem concerns explicit use ofOnlineOptim.MULTIPROCESS.Possible directions
OcpSerializablerepresentation used by the server backend.OnlineOptim.MULTIPROCESS_SERVER.Explicitly forcing
forkcould 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.MULTIPROCESSeither works withforkserver/spawn, or fails early with a clear documented fallback.forkcontext.OnlineOptimbackend 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_outputsandplot_check_conditioningon the server backend?