Skip to content

Conversation

@jholveck
Copy link
Owner

@jholveck jholveck commented Jan 4, 2026

The global lock just provides too much of a surface for contention and
deadlocks. Global locking is still needed by the Xlib backend, since
Xlib is not thread-safe and we're not enabling the parts that make it
slightly more thread-safe, so it now has its own lock object.

The docs were also updated to spell out our multithreading guarantees.

This PR is not actually going to be merged into jholveck/python-mss:main. It exists to give GitHub Copilot a place to review the changes before we send them upstream to BoboTiG/python-mss (currently pending as BoboTiG#452 )

Locking is very tricky to get right, and it's easy to miss bugs. Copilot, I would appreciate it if you could take a look over this and see if I've overlooked anything. Thank you!

The global lock just provides too much of a surface for contention and
deadlocks.  Global locking is still needed by the Xlib backend, since
Xlib is not thread-safe and we're not enabling the parts that make it
slightly more thread-safe, so it now has its own lock object.

The docs were also updated to spell out our multithreading guarantees.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the locking mechanism in MSS to improve multithreading support and reduce contention. The global lock that was previously used for all screenshot operations has been replaced with per-object locks, allowing multiple MSS objects to be used concurrently from different threads and enabling a single MSS object to be safely shared across threads.

Key changes:

  • Replaced global lock with per-object locks in the base class, improving concurrency
  • Converted Windows backend from thread-local storage to instance variables protected by the per-object lock
  • Introduced a separate global lock for the Xlib backend (which requires it due to Xlib's lack of thread-safety)
  • Added comprehensive documentation about multithreading guarantees and constraints

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/mss/base.py Added per-object _lock attribute, deprecated global lock, updated all methods to use per-object lock instead of global lock
src/mss/windows.py Removed thread-local storage (local()), converted _handles to individual instance variables (_bmp, _bmi, _data, etc.) protected by per-object lock
src/mss/linux/xlib.py Added module-level _lock for Xlib thread-safety, wrapped all Xlib calls with this lock while respecting lock ordering (per-object lock first, then global Xlib lock)
src/tests/test_windows.py Updated tests to access instance variables directly (_bmp) instead of through thread-local storage (_handles.bmp)
docs/source/usage.rst Added "Multithreading" section documenting thread-safety guarantees, constraints on custom ScreenShot classes, and warnings about Xlib backend limitations
docs/source/examples.rst Added anchor reference for custom_cls_image to support cross-referencing from usage documentation
CHANGELOG.md Added entry describing the multithreading improvements

Review summary: I conducted a thorough review of the locking implementation, focusing on potential deadlocks, race conditions, and lock ordering issues. The implementation is well-designed and correctly handles:

  1. Lock ordering: The Xlib backend properly enforces that the per-object lock is acquired before the global Xlib lock, preventing deadlocks
  2. Thread safety: All shared mutable state is protected by appropriate locks
  3. Documentation: Clear warnings about the limitations of the Xlib backend and constraints on custom ScreenShot classes
  4. Test coverage: Existing thread safety tests validate the new implementation

No issues were found. The locking design is sound and the implementation follows best practices for concurrent programming.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

I asked GitHub Copilot to review my changes.  It didn't see any
problems, but I looked at its thought process and cleaned up things
that it had to think carefully about.

* Fix typo in an older CHANGELOG entry.
* Add comments about why each documented public attribute of MSSBase,
  and a few undocumented but not _-prefixed attributes of MSS
  implementations, are safe to not be protected under the lock.
* Document that the user isn't supposed to change with_cursor.  Also
  document that MSS might ignore it.
* Clarify that the comment "The attributes below are protected by
  self._lock" is meant to contrast with the attributes above that
  comment.
* In MSSBase.monitors, read the return value self._monitors under the
  lock.  Current code doesn't ever change self._monitors once it's
  created, so it's safe to return outside the lock, but verifying that
  requires inspection of the rest of the codebase.  This makes it clear
  and future-proof.
* Add a test that we can use the same MSS object on multiple threads.
@jholveck jholveck closed this Jan 5, 2026
@jholveck jholveck deleted the feat-locks branch January 6, 2026 01:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants