Repository navigation
Add explicit Engine.close() + context-manager; bump engine to v3.1.55 - #27
Merged
Merged
Conversation
Companion to nlp-engine v3.1.55 (NLP-ENGINE-523), which made
NLP_ENGINE::close() idempotent so it can be called explicitly
from Python without breaking the existing ~NLP_ENGINE -> close()
path.
Changes:
1. bindings.cpp: expose .def("close", &NLP_ENGINE::close, ...) so
Python code can deterministically tear down the engine before
the auto-created TemporaryDirectory is removed.
2. NLPPlus/__init__.py Engine:
- New explicit .close() method that calls self.engine.close()
and then self.tmpdir.cleanup() in that order. Idempotent.
After close(), further analyze()/compile() calls are
undefined behavior.
- New __enter__ / __exit__ so `with Engine() as e:` gets
deterministic cleanup at the end of the block.
- New __del__ that best-effort calls close(). Swallows
exceptions because during interpreter shutdown the bindings
module may already be torn down.
- Initialized self._closed = False guard.
- AttributeError fallback in close() in case someone runs the
new NLPPlus against an older engine binding that doesn't have
.close() exposed -- behaves the same as before in that case.
3. nlp-engine submodule bumped from v3.1.54 to v3.1.55.
4. TemporaryDirectory(ignore_cleanup_errors=True) is kept as
defense-in-depth, not the load-bearing fix. Users who use
`with Engine() as e:` or call e.close() get a clean rmtree;
users who let __del__ tear things down at interpreter shutdown
are also fine (best-effort close() runs first, and the
ignore-flag catches the residual case where shutdown ordering
prevents __del__ from running at all -- crash, os._exit, etc).
Test plan:
# explicit close
e = NLPPlus.Engine()
e.analyze("hello world", "parse-en-us")
e.close()
# tempdir gone, no PermissionError on Windows
# context manager
with NLPPlus.Engine() as e:
e.analyze("hello world", "parse-en-us")
# tempdir gone
# legacy "just let it GC" (still works, ignore_cleanup_errors=True
# catches the residual case if __del__ doesn't run)
e = NLPPlus.Engine()
e.analyze("hello world", "parse-en-us")
del e
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
NLP_ENGINE has two close() overloads in include/Api/lite/nlp_engine.h:
int close();
int close(_TCHAR *analyzer);
So &NLP_ENGINE::close is an unresolved overloaded function type and
g++ rejects nb::class_<>::def("close", &NLP_ENGINE::close, ...) with:
error: no matching function for call to
'nanobind::class_<NLP_ENGINE>::def(const char [6],
<unresolved overloaded function type>, const char [521])'
Cast the member function pointer to the nullary overload — that's
the global-teardown one we want (the close(analyzer) overload removes
a single named analyzer from VTRun, unrelated to NLP-ENGINE-523).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Background
Today on Windows,
NLPPlus.Engine()(auto-tempdir form) silences aPermissionErrorduring cleanup viaTemporaryDirectory(prefix=..., ignore_cleanup_errors=True)— PR #24. The error came from the C++ engine holding<workfolder>/logs/cgerr.logopen at process exit; Python's tempdir cleanup ran during atexit, before nanobind tore down theNLP_ENGINEinstance, so Windows refused to delete a directory containing an open file.The band-aid leaks the tempdir on disk and silences a real ordering bug.
The companion engine PR (nlp-engine#641, shipped in v3.1.55 as NLP-ENGINE-523) made
NLP_ENGINE::close()idempotent so it can be called explicitly from Python without breaking the existing~NLP_ENGINE → close()path.What this PR does
Bump
nlp-enginesubmodule to v3.1.55.bindings.cpp: exposeNLP_ENGINE::close()via nanobind:NLPPlus/__init__.pyEngine:close()method — callsself.engine.close(), thenself.tmpdir.cleanup()if applicable. Idempotent viaself._closedguard.__enter__/__exit__so users can dowith NLPPlus.Engine() as e: ...and get deterministic cleanup.__del__that best-effort callsclose()(swallows exceptions because the bindings module may be torn down during interpreter shutdown).AttributeErrorfallback inclose()if someone runs new NLPPlus against an older engine binding without.close()exposed — degrades to the old behaviour.ignore_cleanup_errors=Truestays as defense-in-depth, not as the load-bearing fix:with/ explicitclose()→ engine closes, tempdir rmtree's cleanly. No PermissionError, no leaked tempdir.del e→__del__best-effort closes too. Usually clean.os._exit/ shutdown ordering preventing__del__from running → flag catches it, stderr stays quiet, OS reclaims tempdir at process exit anyway.Test plan
On Windows: verify with
Process Monitorthat<workfolder>is gone afterclose()returns, and that there's no stalePermissionErrortraceback ondel e.Versioning
Ready for the next NLPPlus release (would be v2.0.4) once this merges:
The bundled engine is now v3.1.55, so PyPI consumers get the deterministic-close path automatically.