Skip to content

Commit 906e29b

Browse files
committed
Prepare 3.0.0 release
Date the 3.0.0 changelog (2026-05-03) and add Fixed/Documentation/Removed entries for the issue #63 thread-callback fix and the doc-audit cleanup (Py_GIL_OWN typo, broken SharedDict snippet, removed _ssl.py, dead py_util exports, new lint-docs guard). Correct a stale "Python 3.12+ for OWN_GIL" claim in docs/event_loop_architecture.md to 3.14+. Add CHANGELOG.md to the hex package files list and drop the stale top-level CMakeLists.txt entry that doesn't exist.
1 parent e6b05f2 commit 906e29b

3 files changed

Lines changed: 37 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Changelog
22

3-
## 3.0.0 (Unreleased)
3+
## 3.0.0 (2026-05-03)
44

55
### Breaking Changes
66

@@ -46,6 +46,33 @@
4646
`{ok, [Result1, ...]}` on success or `{error, {gather_failed, [{Idx, Reason}, ...]}}`
4747
if any call fails. The previous implementation returned `gather_not_implemented`.
4848

49+
- **Thread-callback flakes (issue #63)** - Six layered defects in the
50+
`erlang.call`/`erlang.async_call` plumbing could deliver wrong values to
51+
the wrong caller under load. Reads now loop on partial/EINTR with a
52+
monotonic deadline; sync writes use a single length-prefixed frame on a
53+
dirty I/O scheduler with deadlined non-blocking writes; the sync wire
54+
carries the originating callback id and the receiver discards mismatched
55+
frames; the async pipe has one writer process per fd with an
56+
atomics-bounded mailbox (`?ASYNC_WRITER_MAX_QUEUE = 10000`) and a
57+
resumable nonblocking parser on the read end; workers that fail to
58+
resync are unlinked from the pool, freed, and bounded by
59+
`MAX_POISONED_WORKERS = 64`.
60+
61+
### Documentation
62+
63+
- Audited every fenced code block in `README.md` and `docs/*.md` for
64+
current-API references. Fixed `Py_GIL_OWN` to `PyInterpreterConfig_OWN_GIL`
65+
in `docs/scalability.md`, corrected the `multi_executor` fallback claim
66+
in `docs/migration.md`, and repaired a broken `SharedDict` example in
67+
`docs/shared-dict.md`.
68+
- New `test/coverage_audit.md` maps every public `py:*` and `erlang.*` API
69+
to its test suite. Added cases for `py:cast/4`, `py:async_gather/2`, and
70+
`py:dup_fd/1` so each documented API has a regression test.
71+
- New `scripts/lint_doc_snippets.escript` (driven by `make lint-docs` and
72+
CI) statically validates every Erlang `py:Fn(/N)` call and parses every
73+
Python block in the docs. Snippets that intentionally show removed APIs
74+
or REPL output opt out via `<!-- skip-lint -->`.
75+
4976
### Changed
5077

5178
- **Per-context worker threads** - Each context now gets its own dedicated pthread
@@ -73,6 +100,11 @@
73100
- `context_dispatch_call/eval/exec` functions (dead code)
74101
- References to `PY_MODE_MULTI_EXECUTOR` in context operations
75102
- `py_async_pool` legacy gen_server (unused after async API rewire)
103+
- `priv/_erlang_impl/_ssl.py` (`SSLTransport`, `create_ssl_transport`) had no
104+
importer and was never wired into the asyncio event loop. Removed.
105+
- Internal `py_util` exports `send_response/3`, `normalize_timeout/1`, and
106+
`normalize_timeout/2` had no callers anywhere. Removed. The module is
107+
marked `@private`; no external API changes.
76108
- **Explicit `py:subinterp_*` handle API removed.** `py:subinterp_create/0`,
77109
`subinterp_destroy/1`, `subinterp_call/4,5`, `subinterp_eval/2,3`,
78110
`subinterp_exec/2`, `subinterp_cast/4`, `subinterp_async_call/4`,

docs/event_loop_architecture.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,9 +437,11 @@ Run benchmark: `escript examples/bench_owngil_pool.erl`
437437
**Use OWN_GIL when:**
438438
- Running CPU-bound Python code from multiple Erlang processes
439439
- Need true parallel execution (not just concurrency)
440-
- Python 3.12+ is available
440+
- Python 3.14+ is available (subinterpreters require 3.14+ due to C
441+
extension global-state bugs in 3.12/3.13)
441442

442443
**Use regular mode when:**
443444
- Primarily I/O-bound operations
444445
- Single-process workload
445446
- Need shared state across all tasks
447+
- Running on Python 3.12 or 3.13

rebar.config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
{hex, [
3232
{doc, ex_doc},
33-
{files, ["src", "include", "priv", "c_src", "CMakeLists.txt", "do_cmake.sh", "do_build.sh", "rebar.config", "rebar.lock", "README.md", "LICENSE"]}
33+
{files, ["src", "include", "priv", "c_src", "do_cmake.sh", "do_build.sh", "rebar.config", "rebar.lock", "README.md", "LICENSE", "CHANGELOG.md"]}
3434
]}.
3535

3636
{ex_doc, [

0 commit comments

Comments
 (0)