Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1c914df

Browse files
committed
MSS: ensure calls without context manager will not leak resources or document them (BoboTiG#72, BoboTiG#85)
- Added test_leaks.py to ensure fixes are good and prevent regressions
1 parent 2c53af9 commit 1c914df

File tree

11 files changed

+223
-61
lines changed

11 files changed

+223
-61
lines changed

CHANGELOG

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ History:
66
- MSS: renamed MSSBase to MSSMixin in base.py
77
- Linux: ensure resources are freed in grab()
88
- Windows: avoid unecessary class attributes
9+
- MSS: ensure calls without context manager will not leak resources or document them (fixes #72 and #85)
910
- MSS: fix Flake8 C408: Unnecessary dict call - rewrite as a literal, in exceptions.py
1011
- MSS: fix Flake8 I100: Import statements are in the wrong order
1112
- MSS: fix Flake8 I201: Missing newline before sections or imports

CHANGES.rst

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@
33

44
base.py
55
-------
6-
- Renamed ``MSSBase`` class to ``MSSMixin``.
6+
- Renamed ``MSSBase`` class to ``MSSMixin``
7+
8+
linux.py
9+
--------
10+
- Renamed ``__del__()`` method to ``close()``
11+
12+
windows.py
13+
----------
14+
- Renamed ``__exit__()`` method to ``close()``
715

816

917
3.3.0 (2018-09-04)

docs/source/api.rst

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ GNU/Linux
1919

2020
GNU/Linux initializations.
2121

22+
.. method:: close()
23+
24+
Disconnect from the X server.
25+
26+
.. versionadded:: 4.0.0
27+
2228
.. method:: grab(monitor)
2329

2430
:rtype: :class:`mss.base.ScreenShot`
@@ -41,6 +47,21 @@ GNU/Linux
4147

4248
.. versionadded:: 3.3.0
4349

50+
51+
Windows
52+
-------
53+
54+
.. module:: mss.windows
55+
56+
.. class:: MSS
57+
58+
.. method:: close()
59+
60+
Close GDI handles and free DCs.
61+
62+
.. versionadded:: 4.0.0
63+
64+
4465
Methods
4566
=======
4667

@@ -50,7 +71,11 @@ Methods
5071

5172
The parent's class for every OS implementation.
5273

53-
.. versionchanged:: 4.0.0
74+
.. method:: close()
75+
76+
Clean-up method. Does nothing by default.
77+
78+
.. versionadded:: 4.0.0
5479

5580
.. method:: grab(region)
5681

@@ -148,6 +173,8 @@ Methods
148173
Properties
149174
==========
150175

176+
.. module:: mss.base
177+
151178
.. class:: MSSMixin
152179

153180
.. attribute:: monitors

docs/source/examples/fps.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def screen_record_efficient():
6161
cv2.destroyAllWindows()
6262
break
6363

64+
sct.close()
6465
return fps
6566

6667

docs/source/usage.rst

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,35 +30,38 @@ So the module can be used as simply as::
3030
with mss() as sct:
3131
# ...
3232

33-
Or::
33+
.. note::
3434

35-
sct = mss()
35+
On GNU/Linux and Windows, if you are using this kind of code::
3636

37+
sct = mss()
38+
sct.shot()
39+
sct.grab()
40+
# or any attribute/method of sct
3741

38-
GNU/Linux
39-
---------
42+
Then you will have to **manually** call :meth:`mss.linux.MSSMixin.close()` to free resources.
4043

41-
On GNU/Linux, you can specify which display to use (useful for distant screenshots via SSH)::
44+
.. warning::
4245

43-
with mss(display=':0.0') as sct:
44-
# ...
46+
This code is **highly** unadvised as there will be **resources leaks** without possibility to do something to clean them::
4547

46-
A more specific example to only target GNU/Linux:
47-
48-
.. literalinclude:: examples/linux_display_keyword.py
49-
:lines: 9-
48+
mss().shot()
49+
mss().grab()
50+
# or any mss().xxx or mss().xxx()
5051

5152

5253
Intensive Use
5354
=============
5455

55-
If you plan to integrate MSS inside your own module or software, pay attention to using it wisely. This is a bad usage::
56+
If you plan to integrate MSS inside your own module or software, pay attention to using it wisely.
57+
58+
This is a bad usage::
5659

5760
for _ in range(100):
5861
with mss() as sct:
5962
sct.shot()
6063

61-
This is a better usage, memory efficient::
64+
This is a much better usage, memory efficient::
6265

6366
with mss() as sct:
6467
for _ in range(100):
@@ -67,14 +70,28 @@ This is a better usage, memory efficient::
6770
Also, it is a good thing to save the MSS instance inside an attribute of you class and calling it when needed.
6871

6972

73+
GNU/Linux
74+
---------
75+
76+
On GNU/Linux, you can specify which display to use (useful for distant screenshots via SSH)::
77+
78+
with mss(display=":0.0") as sct:
79+
# ...
80+
81+
A more specific example to only target GNU/Linux:
82+
83+
.. literalinclude:: examples/linux_display_keyword.py
84+
:lines: 9-
85+
86+
7087
Command Line
7188
============
7289

73-
You can use ``mss`` via the CLI:
90+
You can use ``mss`` via the CLI::
7491

7592
mss --help
7693

77-
Or via direct call from Python:
94+
Or via direct call from Python::
7895

7996
python -m mss --help
8097

mss/base.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ def __exit__(self, *_):
2727
# type: (*str) -> None
2828
""" For the cool call `with MSS() as mss:`. """
2929

30+
self.close()
31+
32+
def close(self):
33+
# type: () -> None
34+
""" Clean-up. """
35+
36+
pass
37+
3038
def grab(self, monitor):
3139
# type: (Dict[str, int]) -> ScreenShot
3240
"""

mss/linux.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -146,16 +146,6 @@ class MSS(MSSMixin):
146146

147147
last_error = None
148148

149-
def __del__(self):
150-
# type: () -> None
151-
""" Clean-up. """
152-
153-
# Disconnect from the X server
154-
try:
155-
self.xlib.XCloseDisplay(self.display)
156-
except AttributeError:
157-
pass
158-
159149
def __init__(self, display=None):
160150
# type: (bytes) -> None
161151
""" GNU/Linux initialisations. """
@@ -341,6 +331,20 @@ def validate(retval, func, args):
341331
if errcheck:
342332
meth.errcheck = validate
343333

334+
def close(self):
335+
# type: () -> None
336+
"""
337+
Disconnect from the X server to prevent:
338+
Maximum number of clients reached. Segmentation fault (core dumped)
339+
"""
340+
341+
try:
342+
self.xlib.XCloseDisplay(self.display)
343+
# Delete the attribute to prevent interpreter crash if called twice
344+
del self.display
345+
except AttributeError:
346+
pass
347+
344348
@property
345349
def monitors(self):
346350
# type: () -> List[Dict[str, int]]

mss/tools.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def to_png(data, size, level=6, output=None):
2424
line = width * 3
2525
png_filter = struct.pack(">B", 0)
2626
scanlines = b"".join(
27-
[png_filter + data[y * line:y * line + line] for y in range(height)]
27+
[png_filter + data[y * line : y * line + line] for y in range(height)]
2828
)
2929

3030
magic = struct.pack(">8B", 137, 80, 78, 71, 13, 10, 26, 10)

mss/windows.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,16 +93,27 @@ def __init__(self):
9393
bmi.bmiHeader.biClrImportant = 0 # See grab.__doc__ [3]
9494
self._bmi = bmi
9595

96-
def __exit__(self, *args):
97-
# type: (*str) -> None
98-
""" Cleanup. """
96+
def close(self):
97+
# type: () -> None
98+
""" Close GDI handles and free DCs. """
9999

100-
for attribute in {"_bmp", "_memdc", "_srcdc"}:
101-
attr = getattr(self, attribute, None)
102-
if attr:
103-
ctypes.windll.gdi32.DeleteObject(attr)
100+
try:
101+
ctypes.windll.gdi32.DeleteObject(self._bmp)
102+
del self._bmp
103+
except AttributeError:
104+
pass
105+
106+
try:
107+
ctypes.windll.gdi32.DeleteDC(self._memdc)
108+
del self._memdc
109+
except (OSError, AttributeError):
110+
pass
104111

105-
super(MSS, self).__exit__(*args)
112+
try:
113+
ctypes.windll.user32.ReleaseDC(0, self._srcdc)
114+
del self._srcdc
115+
except AttributeError:
116+
pass
106117

107118
@property
108119
def monitors(self):
@@ -235,6 +246,10 @@ def set_argtypes(callback):
235246
ctypes.wintypes.LPARAM,
236247
]
237248
ctypes.windll.user32.GetWindowDC.argtypes = [ctypes.wintypes.HWND]
249+
ctypes.windll.user32.ReleaseDC.argtypes = [
250+
ctypes.wintypes.HWND,
251+
ctypes.wintypes.HGDIOBJ,
252+
]
238253
ctypes.windll.gdi32.GetDeviceCaps.argtypes = [
239254
ctypes.wintypes.HWND,
240255
ctypes.wintypes.INT,
@@ -279,6 +294,7 @@ def set_restypes():
279294
ctypes.windll.user32.GetSystemMetrics.restype = ctypes.wintypes.INT
280295
ctypes.windll.user32.EnumDisplayMonitors.restype = ctypes.wintypes.BOOL
281296
ctypes.windll.user32.GetWindowDC.restype = ctypes.wintypes.HDC
297+
ctypes.windll.user32.ReleaseDC.restype = ctypes.wintypes.INT
282298
ctypes.windll.gdi32.GetDeviceCaps.restype = ctypes.wintypes.INT
283299
ctypes.windll.gdi32.CreateCompatibleDC.restype = ctypes.wintypes.HDC
284300
ctypes.windll.gdi32.CreateCompatibleBitmap.restype = ctypes.wintypes.HBITMAP

tests/leaks.py

Lines changed: 0 additions & 25 deletions
This file was deleted.

0 commit comments

Comments
 (0)