Skip to content

Commit 3d73f79

Browse files
authored
Merge pull request #1762 from minrk/destroy-del
changelog for 24
2 parents ab80a56 + 99f0503 commit 3d73f79

File tree

2 files changed

+48
-14
lines changed

2 files changed

+48
-14
lines changed

docs/source/changelog.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,28 @@
55
This is a coarse summary of changes in pyzmq versions.
66
For a full changelog, consult the [git log](https://github.com/zeromq/pyzmq/commits).
77

8+
## 24
9+
10+
pyzmq 24 has two breaking changes (one only on Windows), though they are not likely to affect most users.
11+
12+
Breaking changes:
13+
14+
- Due to a libzmq bug causing unavoidable crashes for some users,
15+
Windows wheels no longer bundle libzmq with AF_UNIX support.
16+
In order to enable AF_UNIX on Windows, pyzmq must be built from source,
17+
linking an appropriate build of libzmq (e.g. `libzmq-v142`).
18+
AF_UNIX support will be re-enabled in pyzmq wheels
19+
when libzmq published fixed releases.
20+
21+
- Using a {class}`zmq.Context` as a context manager or deleting a context without closing it now calls {meth}`zmq.Context.destroy` at exit instead of {meth}`zmq.Context.term`.
22+
This will have little effect on most users,
23+
but changes what happens when user bugs result in a context being _implicitly_ destroyed while sockets are left open.
24+
In almost all cases, this will turn what used to be a hang into a warning.
25+
However, there may be some cases where sockets are actively used in threads,
26+
which could result in a crash.
27+
To use sockets across threads, it is critical to properly and explicitly close your contexts and sockets,
28+
which will always avoid this issue.
29+
830
## 23.2.1
931

1032
Improvements:

zmq/sugar/context.py

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,23 @@ class Context(ContextBase, AttributeSetter, Generic[ST]):
4040
.. versionchanged:: 24
4141
4242
When using a Context as a context manager (``with zmq.Context()``),
43-
exiting the context calls `ctx.destroy()`,
43+
or deleting a context without closing it first,
44+
``ctx.destroy()`` is called,
4445
closing any leftover sockets,
4546
instead of `ctx.term()` which requires sockets to be closed first.
46-
This makes contexts-as-context-managers
47-
not safe for use with sockets managed in other threads.
47+
48+
This prevents hangs caused by `ctx.term()` if sockets are left open,
49+
but means that unclean destruction of contexts
50+
(with sockets left open) is not safe
51+
if sockets are managed in other threads.
4852
"""
4953

5054
sockopts: Dict[int, Any]
5155
_instance: Any = None
5256
_instance_lock = Lock()
5357
_instance_pid: Optional[int] = None
5458
_shadow = False
59+
_warn_destroy_close = False
5560
_sockets: WeakSet
5661
# mypy doesn't like a default value here
5762
_socket_class: Type[ST] = Socket # type: ignore
@@ -66,19 +71,26 @@ def __init__(self: "Context[Socket]", io_threads: int = 1, **kwargs: Any) -> Non
6671
self._sockets = WeakSet()
6772

6873
def __del__(self) -> None:
69-
"""deleting a Context should terminate it, without trying non-threadsafe destroy"""
74+
"""Deleting a Context without closing it destroys it and all sockets.
75+
76+
.. versionchanged:: 24
77+
Switch from threadsafe `term()` which hangs in the event of open sockets
78+
to less safe `destroy()` which
79+
warns about any leftover sockets and closes them.
80+
"""
7081

7182
# Calling locals() here conceals issue #1167 on Windows CPython 3.5.4.
7283
locals()
7384

7485
if not self._shadow and not _exiting and not self.closed:
86+
self._warn_destroy_close = True
7587
warnings.warn(
7688
f"unclosed context {self}",
7789
ResourceWarning,
7890
stacklevel=2,
7991
source=self,
8092
)
81-
self.term()
93+
self.destroy()
8294

8395
_repr_cls = "zmq.Context"
8496

@@ -102,15 +114,8 @@ def __enter__(self: T) -> T:
102114
return self
103115

104116
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
105-
for socket in list(self._sockets):
106-
if socket and not socket.closed:
107-
warnings.warn(
108-
f"unclosed socket {socket}",
109-
ResourceWarning,
110-
stacklevel=2,
111-
source=socket,
112-
)
113-
117+
# warn about any leftover sockets before closing them
118+
self._warn_destroy_close = True
114119
self.destroy()
115120

116121
def __copy__(self: T, memo: Any = None) -> T:
@@ -254,6 +259,13 @@ def destroy(self, linger: Optional[float] = None) -> None:
254259
self._sockets = WeakSet()
255260
for s in sockets:
256261
if s and not s.closed:
262+
if self._warn_destroy_close:
263+
warnings.warn(
264+
f"Destroying context with unclosed socket {s}",
265+
ResourceWarning,
266+
stacklevel=3,
267+
source=s,
268+
)
257269
if linger is not None:
258270
s.setsockopt(SocketOption.LINGER, linger)
259271
s.close()

0 commit comments

Comments
 (0)