BoboTiG / python-mss

An ultra fast cross-platform multiple screenshots module in pure Python using ctypes.
https://pypi.org/project/mss/
MIT License
998 stars 88 forks source link

Xvfb error while trying to open multiple xvfb screens in single python session #210

Closed mudassirkhan19 closed 1 year ago

mudassirkhan19 commented 2 years ago

General information:

For GNU/Linux users:

Description of the warning/error

The error comes when I try to call mss.mss() in a single python session with two different Xvfb screens.

Full message

XIO: fatal IO error 0 (Success) on X server ":3" after 8 requests (8 known processed) with 0 events remaining.

Other details

Steps to reproduce:

from xvfbwrapper import Xvfb
import mss
import os

xvfb = Xvfb(width=1920, height=1080, colordepth=24)
xvfb.start()

with mss.mss() as sct:
    pass
xvfb.stop()

xvfb = Xvfb(width=1920, height=1080, colordepth=24)
xvfb.start()

with mss.mss() as sct:
    pass

The error pops up exactly on this line

BoboTiG commented 2 years ago

Could you try to reset that attribute before the second use of MSS?

from mss.linux import MSS

# (...)
xvfb.stop()

MSS._display_dict.clear()

xvfb = Xvfb(width=1920, height=1080, colordepth=24)
# (...)
mudassirkhan19 commented 2 years ago

thanks @BoboTiG ! I have been trying to resolve this bug since two days.

BoboTiG commented 2 years ago

Great! I could introduce a mss.reset_internal_state() to hide implementation details. Or maybe shorter: mss.reset(). With a good documentation it should help with such situation.

mudassirkhan19 commented 2 years ago

Shouldn't the context manager take care of this? this can just happen behind the scenes, just a thought.

BoboTiG commented 2 years ago

Actually those internal details were added for performance and memory leak reasons. Your situation is not so frequent, so I would go with the new function to clear the internal state.

mudassirkhan19 commented 2 years ago

Encountered another issue with this, this time its a too many open files error, the open connection originates here: self.xlib.XOpenDisplay(disp) So basically we aren't closing our connection to the X11 server, a little digging led me to an XCloseDisplay function from here. So wrote a wrapper for my usecase:

from contextlib import contextmanager
import sys

@contextmanager
def mss_wrapper():
    try:
        with mss.mss() as sct:
            yield sct
    finally:
        if sys.platform == "linux":
            from mss.linux import MSS
            import ctypes

            x11 = ctypes.util.find_library("X11")  # type: ignore
            xlib = ctypes.cdll.LoadLibrary(x11)
            for _, display in MSS._display_dict.items():
                xlib.XCloseDisplay(display)
            MSS._display_dict.clear()
dp-alvarez commented 2 years ago

I've run into the same issue as @mudassirkhan19 (thanks for the wrapper by the way!).

I feel like it makes a lot of sense for the display to get closed and the dict cleared on the context exit, in which case would someone avoid this?

Also, out of curiosity, why share the display among all MSS instances in a thread instead of each MSS instance having it's own? Right now one can only use MSS to connect to one display per thread, clearing the dict solves this partially but you can't have two MSS instances connected to different displays in the same thread. X apparently doesn't mind multiple connections to the same display per thread since I'm using other libraries each with their own display.

BoboTiG commented 1 year ago

@dp-alvarez

Also, out of curiosity, why share the display among all MSS instances in a thread instead of each MSS instance having it's own?

At first, it was to workaround resource leaks. But I now see that it's not a good way, and there may be no way to handle all cases. So I refactored the whole part in #234, and it should be better for most of us.