-
Notifications
You must be signed in to change notification settings - Fork 307
Fix VMM handle leaks in virtual memory paths #2235
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b2f881b
98265d6
76efd95
2e8eaf7
d91108d
6de0c36
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -306,24 +306,26 @@ def is_nested_sequence(obj: object) -> bool: | |
|
|
||
| class Transaction: | ||
| """ | ||
| A context manager for transactional operations with undo capability. | ||
| A context manager for transactional operations with failure and exit callbacks. | ||
|
|
||
| The Transaction class allows you to register undo actions (callbacks) that will be executed | ||
| if the transaction is not committed before exiting the context. This is useful for managing | ||
| resources or operations that need to be rolled back in case of errors or early exits. | ||
| Failure callbacks are executed in LIFO order if the transaction exits without being committed. | ||
| Exit callbacks always run: in LIFO order on rollback or FIFO order during commit. | ||
|
|
||
| Usage: | ||
| with Transaction() as txn: | ||
| txn.append(some_cleanup_function, arg1, arg2) | ||
| txn.on_failure(some_cleanup_function, arg1, arg2) | ||
| txn.on_exit(some_finalize_function, arg1, arg2) | ||
| # ... perform operations ... | ||
| txn.commit() # Disarm undo actions; nothing will be rolled back on exit | ||
| txn.commit() | ||
|
|
||
| Methods: | ||
| append(fn, *args, **kwargs): Register an undo action to be called on rollback. | ||
| commit(): Disarm all undo actions; nothing will be rolled back on exit. | ||
| on_failure(fn, *args, **kwargs): Register a callback to be called on rollback. | ||
| on_exit(fn, *args, **kwargs): Register a callback to be called on rollback or commit. | ||
| commit(): Disarm failure callbacks and run exit callbacks. | ||
| """ | ||
| def __init__(self) -> None: | ||
| self._stack = ExitStack() | ||
| self._on_exit: list[Callable[[], Any]] = [] | ||
| self._entered = False | ||
|
|
||
| def __enter__(self): | ||
|
|
@@ -334,23 +336,38 @@ class Transaction: | |
| def __exit__(self, exc_type, exc, tb): | ||
| # If exit callbacks remain, they'll run in LIFO order. | ||
| self._entered = False | ||
| self._on_exit.clear() | ||
| return self._stack.__exit__(exc_type, exc, tb) | ||
|
|
||
| def append(self, fn: Callable[..., Any], /, *args: Any, **kwargs: Any) -> None: | ||
| def on_failure(self, fn: Callable[..., Any], /, *args: Any, **kwargs: Any) -> None: | ||
| """ | ||
| Register an undo action (runs if the with-block exits without commit()). | ||
| Register a failure callback (runs if the with-block exits without commit()). | ||
| Values are bound now via partial so late mutations don't bite you. | ||
| """ | ||
| if not self._entered: | ||
| raise RuntimeError("Transaction must be entered before append()") | ||
| raise RuntimeError("Transaction must be entered before on_failure()") | ||
| self._stack.callback(partial(fn, *args, **kwargs)) | ||
|
|
||
| def on_exit(self, fn: Callable[..., Any], /, *args: Any, **kwargs: Any) -> None: | ||
| """ | ||
| Register an exit callback (runs exactly once, on rollback or during commit()). | ||
| Values are bound now via partial so late mutations don't bite you. | ||
| """ | ||
| if not self._entered: | ||
| raise RuntimeError("Transaction must be entered before on_exit()") | ||
| callback = partial(fn, *args, **kwargs) | ||
| self._stack.callback(callback) | ||
| self._on_exit.append(callback) | ||
|
Comment on lines
+351
to
+360
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-blocking nit before merge: def _register(self, callback: Callable[[], Any], on_commit: bool) -> None:
if not self._entered:
raise RuntimeError("Transaction must be entered before registering callbacks")
# The ExitStack copy runs on rollback (LIFO, interleaved with the failure
# callbacks); the _on_exit copy runs at commit(). commit() disarms the stack
# before running _on_exit, so exactly one of the two ever fires.
self._stack.callback(callback)
if on_commit:
self._on_exit.append(callback)
def on_failure(self, fn: Callable[..., Any], /, *args: Any, **kwargs: Any) -> None:
"""Register a failure callback (runs if the with-block exits without commit())."""
self._register(partial(fn, *args, **kwargs), on_commit=False)
def on_exit(self, fn: Callable[..., Any], /, *args: Any, **kwargs: Any) -> None:
"""Register an exit callback (runs exactly once, on rollback or during commit())."""
self._register(partial(fn, *args, **kwargs), on_commit=True)Besides the dedup, the comment at the dual-registration site is the important part — a reviewer already tripped on why
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, I can fix this in a few hours. @leofang |
||
|
|
||
| def commit(self) -> None: | ||
| """ | ||
| Disarm all undo actions. After this, exiting the with-block does nothing. | ||
| Disarm all failure callbacks, then run exit callbacks in FIFO order. | ||
| """ | ||
| # pop_all() empties this stack so no callbacks are triggered on exit. | ||
| self._stack.pop_all() | ||
| for fn in self._on_exit: | ||
| fn() | ||
| self._on_exit.clear() | ||
|
|
||
|
|
||
| # Track whether we've already warned about fork method | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note to myself:
append()is gone but this is not public API.