From df157552f3040773eaac84a2d6dee807e75a1b7e Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Fri, 1 Jan 2021 01:37:09 -0800 Subject: [PATCH] Make ocrmypdf.ocr take a threading lock --- docs/api.rst | 5 +++++ src/ocrmypdf/api.py | 23 ++++++++++++++++------- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index a93c4a9c..3459e8b8 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -56,6 +56,11 @@ Programs that call ``ocrmypdf.ocr()`` should also install a SIGBUS signal handler (except on Windows), to raise an exception if access to a memory mapped file fails. OCRmyPDF may use memory mapping. +``ocrmypdf.ocr()`` will take a threading lock to prevent multiple runs of itself +in the same Python interpreter process. This is not thread-safe, because of how +OCRmyPDF's plugins and Python's library import system work. If you need to parallelize +OCRmyPDF, use processes. + .. warning:: On Windows and macOS, the script that calls ``ocrmypdf.ocr()`` must be diff --git a/src/ocrmypdf/api.py b/src/ocrmypdf/api.py index 6eaf5a4b..9a37cad6 100644 --- a/src/ocrmypdf/api.py +++ b/src/ocrmypdf/api.py @@ -8,6 +8,7 @@ import logging import os import sys +import threading from enum import IntEnum from io import IOBase from pathlib import Path @@ -30,6 +31,8 @@ except ModuleNotFoundError: StrPath = Union[os.PathLike, AnyStr] PathOrIO = Union[BinaryIO, StrPath] +_api_lock = threading.Lock() + class Verbosity(IntEnum): """Verbosity level for configure_logging.""" @@ -306,12 +309,18 @@ def ocr( # pylint: disable=unused-argument parser = get_parser() create_options_kwargs['parser'] = parser - plugin_manager = get_plugin_manager(plugins) - plugin_manager.hook.add_options(parser=parser) # pylint: disable=no-member - if 'verbose' in kwargs: - warn("ocrmypdf.ocr(verbose=) is ignored. Use ocrmypdf.configure_logging().") + with _api_lock: + # We can't allow multiple ocrmypdf.ocr() threads to run in parallel, because + # they might install different plugins, and generally speaking we have areas + # of code that use global state. - options = create_options(**create_options_kwargs) - check_options(options, plugin_manager) - return run_pipeline(options=options, plugin_manager=plugin_manager, api=True) + plugin_manager = get_plugin_manager(plugins) + plugin_manager.hook.add_options(parser=parser) # pylint: disable=no-member + + if 'verbose' in kwargs: + warn("ocrmypdf.ocr(verbose=) is ignored. Use ocrmypdf.configure_logging().") + + options = create_options(**create_options_kwargs) + check_options(options, plugin_manager) + return run_pipeline(options=options, plugin_manager=plugin_manager, api=True)