diff --git a/src/ablog/__init__.py b/src/ablog/__init__.py index 4b2c4283..195d370e 100755 --- a/src/ablog/__init__.py +++ b/src/ablog/__init__.py @@ -24,6 +24,7 @@ process_postlist, process_posts, purge_posts, + merge_posts, ) from .version import version as __version__ @@ -150,6 +151,7 @@ def setup(app): app.connect("builder-inited", builder_inited) app.connect("doctree-read", process_posts) app.connect("env-purge-doc", purge_posts) + app.connect("env-merge-info", merge_posts) app.connect("doctree-resolved", process_postlist) app.connect("missing-reference", missing_reference) app.connect("html-collect-pages", generate_archive_pages) @@ -167,6 +169,6 @@ def setup(app): app.add_message_catalog(MESSAGE_CATALOG_NAME, locale_dir) return { "version": __version__, - "parallel_read_safe": False, + "parallel_read_safe": True, "parallel_write_safe": True, } diff --git a/src/ablog/post.py b/src/ablog/post.py index c68bcea4..5609148f 100644 --- a/src/ablog/post.py +++ b/src/ablog/post.py @@ -25,6 +25,7 @@ "PostListDirective", "CheckFrontMatter", "purge_posts", + "merge_posts", "process_posts", "process_postlist", "generate_archive_pages", @@ -220,6 +221,22 @@ def purge_posts(app, env, docname): env.domains["std"].data["anonlabels"].pop(filename, None) +def merge_posts(app, env, docnames, other): + """ + Merge post data from a worker environment into the main environment + during parallel reading. + """ + if hasattr(other, "ablog_posts"): + if not hasattr(env, "ablog_posts"): + env.ablog_posts = {} + # post doctrees reference the worker env via settings.env, which + # would end up in environment.pickle once per worker + for posts in other.ablog_posts.values(): + for post in posts: + post["doctree"].document.settings.env = env + env.ablog_posts.update(other.ablog_posts) + + def _update_post_node(node, options, arguments): """ Extract metadata from options and populate a post node. diff --git a/src/ablog/tests/test_parallel.py b/src/ablog/tests/test_parallel.py new file mode 100644 index 00000000..fea1dbd1 --- /dev/null +++ b/src/ablog/tests/test_parallel.py @@ -0,0 +1,16 @@ +import pytest + + +@pytest.mark.sphinx("html", testroot="parallel", parallel=2) # using roots/test-parallel +def test_parallel_read(app, status, warning): + app.build() + assert app.statuscode == 0 + + html = (app.outdir / "postlist.html").read_text(encoding="utf-8") + for i in range(1, 5): + assert f"post {i}" in html + + # merged posts must not keep the worker's env alive + for posts in app.env.ablog_posts.values(): + for post in posts: + assert post["doctree"].document.settings.env is app.env