2025, Dec 02 09:00
Why Python multiprocessing breaks in LibreOffice macros (pickling errors) and how to fix it with threading
Learn why Python multiprocessing fails in LibreOffice macros with pickling errors and Windows __main__ quirks, and how to use threading for background tasks.
Running Python multiprocessing inside a LibreOffice macro looks tempting until the interpreter reminds you where that code actually lives. In this environment, the macro code is loaded under a module that LibreOffice uses internally, and that changes how objects are pickled and spawned. The result is a seemingly odd pickling failure when you try to start a new process from a macro.
Reproducing the issue
The following minimal macro writes a file and then attempts to run that work in a separate process. The structure is straightforward, but it triggers a pickling error tied to the macro execution model.
import multiprocessing as mproc
import time
def write_stub():
with open("./test.txt", "w") as out_f:
out_f.write("toto")
def entry_macro():
proc = mproc.Process(write_stub)
proc.start()
time.sleep(1)
proc.stop()
The failure manifests as a message like:
"Can't pickle <function task at 0x...>: import of module 'ooo_script_framework' failed"
The function that you intended to run in a child process ends up being seen as part of a LibreOffice-hosted module rather than a plain top-level function, and then the process bootstrap cannot import it back.
What is happening under the hood
LibreOffice executes Python macros by loading them into a module used by its scripting framework. In practice, your function — however it is named — becomes a member of that host module. When Python’s multiprocessing tries to spawn a new process, it pickles the target callable and expects the child to import it. That import path now points into the LibreOffice module, and the import fails in the new interpreter context, which leads to the pickling error you see.
There is another piece to this puzzle on Windows. The multiprocessing module should be run behind the standard guard if __name__ == "__main__":, but macros are not executed that way, so the usual entry-point behavior multiprocessing relies on is not present.
Practical fix in a macro: use threading
In macros, threading.Thread works reliably and avoids the pickling and module import path complications, because threads share the same interpreter and module state. Swapping out multiprocessing for threading is a pragmatic way to perform background work within LibreOffice’s macro environment.
import threading as th
import time
def write_stub():
with open("./test.txt", "w") as out_f:
out_f.write("toto")
def entry_macro():
worker = th.Thread(target=write_stub)
worker.start()
time.sleep(1)
This approach aligns with how macros are executed and avoids the child-process import problem entirely. For a reference pattern, see an example that demonstrates threading in this context: https://stackoverflow.com/a/35853062/5100564.
Why this matters
It’s important to recognize that the macro runtime changes how Python sees your module structure. Trying to “hide” the target function from that host module won’t work; the interpreter still treats it as part of the LibreOffice scripting module, and multiprocessing will try to pickle and import it from there. Knowing that macros don’t execute under if __name__ == "__main__": on Windows also clarifies why multiprocessing is a poor fit in this particular setup.
There is a broader performance consideration as well. In this specific case, moving to threads is a reasonable replacement because the task is I/O bound. In general, though, multithreading is not a universal substitute for multiprocessing.
Takeaways
If you need background execution inside a LibreOffice macro, prefer threading over multiprocessing. It integrates cleanly with the macro environment, avoids pickling errors tied to the scripting module, and doesn’t depend on the Windows __main__ guard. Keep in mind the nature of your workload: when the work is I/O bound, threads will usually do fine; beyond that, threading won’t always be a suitable stand-in for processes.