Deprecate semfree and don't auto activate it

Instead the standard executor will fall back to threads.

semfree caused test failures  with Py3.14:
https://github.com/ocrmypdf/OCRmyPDF/issues/1558

In retrospect and with emerging Python tech like freethreading, semfree is becoming less necessary. We can use threads for the time being.

A consequence is that performance may be lower on Lambda and Termux when we are using threads and not shelling out work.
This commit is contained in:
James R. Barlow
2025-09-11 17:13:04 -07:00
parent 7ca4ae4e16
commit 414d80fc16
5 changed files with 51 additions and 36 deletions
+2 -11
View File
@@ -73,19 +73,10 @@ class OcrmypdfPluginManager(pluggy.PluginManager):
module = importlib.import_module(name)
self.register(module)
# 2. Install semfree if needed
try:
# pylint: disable=import-outside-toplevel
from multiprocessing.synchronize import SemLock
del SemLock
except ImportError:
self.register(importlib.import_module('ocrmypdf.extra_plugins.semfree'))
# 3. Register setuptools plugins
# 2. Register setuptools plugins
self.load_setuptools_entrypoints('ocrmypdf')
# 4. Register plugins specified on command line
# 3. Register plugins specified on command line
for name in self.__plugins:
if isinstance(name, Path) or name.endswith('.py'):
# Import by filename
+26 -8
View File
@@ -96,6 +96,31 @@ def thread_init(q: Queue, user_init: UserInit, loglevel) -> None:
return
def setup_executor(use_threads: bool) -> tuple[Queue, Executor, WorkerInit]:
if not use_threads:
# Some execution environments like AWS Lambda and Termux do not support
# semaphores. Check if semaphore support is available, and if not, fall back
# to using threads.
try:
# pylint: disable=import-outside-toplevel
from multiprocessing.synchronize import SemLock
del SemLock
except ImportError:
use_threads = True
if use_threads:
loq_queue = queue.Queue(-1)
executor_class = ThreadPoolExecutor
initializer = thread_init
else:
loq_queue = multiprocessing.Queue(-1)
executor_class = ProcessPoolExecutor
initializer = process_init
return loq_queue, executor_class, initializer
class StandardExecutor(Executor):
"""Standard OCRmyPDF concurrent task executor."""
@@ -110,14 +135,7 @@ class StandardExecutor(Executor):
task_arguments: Iterable,
task_finished: Callable,
):
if use_threads:
log_queue: Queue = queue.Queue(-1)
executor_class: FuturesExecutorClass = ThreadPoolExecutor
initializer: WorkerInit = thread_init
else:
log_queue = multiprocessing.Queue(-1)
executor_class = ProcessPoolExecutor
initializer = process_init
log_queue, executor_class, initializer = setup_executor(use_threads)
# Regardless of whether we use_threads for worker processes, the log_listener
# must be a thread. Make sure we create the listener after the worker pool,
+1 -5
View File
@@ -2,8 +2,4 @@
#
# SPDX-License-Identifier: MPL-2.0
"""Extra plugins. These are not automatically inserted when ocrmypdf is run.
You can use these plugins by specifying them on the command line, e.g.:
ocrmypdf --plugin ocrmypdf.extra_plugins.semfree ...
"""
"""Extra plugins. These are not automatically inserted when ocrmypdf is run."""
+9
View File
@@ -13,6 +13,9 @@ worker communicates only with the main process.
This is not without drawbacks. If the tasks are not "even" in size, which cannot
be guaranteed, some workers may end up with too much work while others are idle.
It is less efficient than the standard implementation, so not the default.
This module is deprecated and will be removed in a future release. The standard
executor will fall back to threads in these environments.
"""
from __future__ import annotations
@@ -20,6 +23,7 @@ from __future__ import annotations
import logging
import logging.handlers
import signal
import warnings
from collections.abc import Callable, Iterable, Iterator
from contextlib import suppress
from enum import Enum, auto
@@ -32,6 +36,11 @@ from ocrmypdf._concurrent import NullProgressBar
from ocrmypdf.exceptions import InputFileError
from ocrmypdf.helpers import remove_all_log_handlers
warnings.warn(
"semfree.py is deprecated and will be removed in a future release.",
DeprecationWarning,
)
class MessageType(Enum):
"""Implement basic IPC messaging."""
+13 -12
View File
@@ -12,15 +12,16 @@ from .conftest import is_linux, run_ocrmypdf_api
@pytest.mark.skipif(not is_linux(), reason='semfree plugin only works on Linux')
def test_semfree(resources, outpdf):
exitcode = run_ocrmypdf_api(
resources / 'multipage.pdf',
outpdf,
'--skip-text',
'--skip-big',
'2',
'--plugin',
'ocrmypdf.extra_plugins.semfree',
'--plugin',
'tests/plugins/tesseract_noop.py',
)
assert exitcode in (ExitCode.ok, ExitCode.pdfa_conversion_failed)
with pytest.warns(DeprecationWarning, match="semfree.py is deprecated"):
exitcode = run_ocrmypdf_api(
resources / 'multipage.pdf',
outpdf,
'--skip-text',
'--skip-big',
'2',
'--plugin',
'ocrmypdf.extra_plugins.semfree',
'--plugin',
'tests/plugins/tesseract_noop.py',
)
assert exitcode in (ExitCode.ok, ExitCode.pdfa_conversion_failed)