I am working on a Mathematica package for knot theory that combines functionality of several other packages. Two notable examples are SnapPy and Regina. Fortunately, both have a rich Python interface and I can call them from Mathematica by using an ExternalSession.
Here is a minimal example that creates a Python environment that loads the packages snappy and numpy.
$snappy = StartExternalSession[Association[ "System" -> "Python", "Evaluator" -> <|"Dependencies" -> {"snappy"}, "EnvironmentName" -> "snappyenv"|> ]]; ExternalEvaluate[$snappy, " import numpy as np; import snappy; def snappy_setLink(pdcode) : global snappy_L a = np.array(pdcode); if a.size > 0 : snappy_L = snappy.Link(np.array(pdcode).tolist()) else: snappy_L = snappy.Link('0_1') "]; I could move on now and can define functions that send or retrieve link diagrams or compute some knot invariants. But these functions are not really important here. The point is that the Python session is alive now. On macOS the Activity Monitor shows me two(!) processes called python3.11, which together consume about 100 MB of RAM.
Now to my problem: My package creates such an ExternalSession whenever it is loaded. When I regularly Exit[] the kernel, they are deleted by the destructor of the ExternalSessionObject. But as I am still in the (never-ending) developing phase for this package and since I link a lot of experimental C++ code via LibraryLink, the following events are quite common in my workflow:
- the operating system shuts down the Mathematica kernel as mother process of the compiled library due to some segfault; or
- I have to shut down the Mathematica kernel manually because the library is trapped in an infinite loop; or
- --which is far more common--I have to shut down Mathematica altogether because the Mathematica frontend cannot swallow bazillions of log messages that are accidentally created; or
- something like that.
The problem with this not-so-uncommon kernel kills is that the Python processes are not killed this way. So after a couple of hours of work I have accumulated several dozens of Python zombie processes that happily gnaw away my brain RAM. Well I have a lot of it (of both, haha), but it is very annoying, nonetheless. Also, at some point I want to ship this package to customers and they might complain about the undead feasting on their hardware.
So, is there any canonical solution to this? The Python session is run as a child process of the Mathematica kernel WolframKernel. Is it possible to make the operating system automatically kill the child process if the parent is killed? Is there some magical option for StartExternalSession maybe? Also, are there cross-platform solutions to this?
$Epilogcould work. -- Nope, it seems not, usingkill.... $\endgroup$