-
Notifications
You must be signed in to change notification settings - Fork 9
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
Make fade_brightness()
thread stoppable for concurrent requests
#38
Conversation
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.
Thanks for this. I left some comments and if you could add some test coverage as well, that would be great
|
||
return self.get_brightness() | ||
|
||
def fade_brightness_thread( |
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.
Not sure there's a use case for users invoking this function directly so it might make more sense as an internal function (eg: __fade_brightness
). Also means we wouldn't have to maintain a near identical docstring compared to fade_brightness
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.
Double underscores in Python cause name mangling, which changes the attribute's name internally. This made pytest fail because it couldn't find the attribute. So, I will use a single underscore instead. It allows pytest to access the attribute while still suggesting it's not for direct access.
@@ -454,7 +531,8 @@ def fade_brightness( | |||
else: | |||
# As `value` doesn't hit `finish` in loop, we explicitly set brightness to `finish`. | |||
# This also avoids an unnecessary sleep in the last iteration. | |||
self.set_brightness(finish, force=force) | |||
if not stoppable or threading.current_thread() == self._fade_thread: | |||
self.set_brightness(finish, force=force) | |||
|
|||
return self.get_brightness() |
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.
Both fade_brightness
and fade_brightness_thread
return the new brightness, resulting in two calls to get_brightness
.
Since returning the new brightness is deprecated for fade_brightness
anyway, this one should probably return None
@@ -380,6 +383,21 @@ class Display(): | |||
'''The serial number of the display or (if serial is not available) an ID assigned by the OS''' | |||
|
|||
_logger: logging.Logger = field(init=False, repr=False) | |||
_instances: Dict[FrozenSet[Tuple[Any, Any]], 'Display'] = field(default_factory=dict) |
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.
If a display can be accessed via multiple methods (eg: via linux.I2C
and linux.DDCUtil
), the args/kwargs would largely be the same except from the method
and maybe index
. In that case you could initialise two Display
instances for the same physical monitor and run into the same flickering issue, especially if you're using allow_duplicates
.
Maybe you could use the result from get_identifier
as the dict keys?
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.
Sorry that I didn't know index
is strictly associated with method
.
If a display can be accessed via multiple methods, filter_monitor(allow_duplicates=True)
will return duplicate indices, and the actual duplicate displays info have different method
and maybe index
. Am I right?
If yes, there must be duplicate indices, and my PR about filter_monitor()
can't handle this properly, and I think there is a discrepancy in the definition of "duplicates".
Is there any preference when choosing a display from two different 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.
If a display can be accessed via multiple methods,
filter_monitor(allow_duplicates=True)
will return duplicate indices, and the actual duplicate displays info have different method and maybe index. Am I right?
Yep that's right. They should have most of the same other info, which is how we try to filter them out in filter_monitors
my PR about
filter_monitor()
can't handle this properly, and I think there is a discrepancy in the definition of "duplicates"
I'm not sure what you mean about the definition of "duplicate". To me, "duplicate" is when we detect the same display twice (or more), usually via different method
s.
In the case of your two monitors, there's no way to tell them apart based on their reported information. As far as the library knows, they are the same display but it's detected it twice.
Is there any preference when choosing a display from two different
method
?
The _OS_METHODS
tuple has them in order. Doesn't matter as much on Windows but does on Linux
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.
When duplicates are allowed, current Display.get_identifier()
cannot provide a valid identifier, which presents a significant systematic issue and will break the code. However, the potential flickering issues that may arise from current implementation can be manually avoided by the user. Here's a more detailed explanation:
Duplicate displays can occur due to:
- the same display can be accessed via multiple methods;
- The same monitor is connected through different interfaces at the same time;
- Different monitors share identical identifiers, including EDID, due to careless manufacturer.
The first scenario leads to duplicate index
properties and the last two lead to duplicate edid
. This means when duplicates are allowed, none of existing identifiers can guarantee uniqueness. method
+index
might be a potential solution, which requires further discussion, but Display.get_identifier()
seems to be less necessary than it used to be.
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.
When duplicates are allowed, current
Display.get_identifier()
cannot provide a valid identifier
Duplicates being allowed is very much an edge case and is not fully supported. In my mind it's like saying rm -rf
instead of rm
. At that point, the user has said they know best and they will handle the consequences.
Looking at the scenarios mentioned:
- Solved by disallowing duplicates and allowing
filter_monitors
to do its thing - Not considered a valid use case
- Not sure if there's a good reason to connect a single display to a machine via 2 different cables?
- These displays cannot be told apart. As far as the software knows, they are the same display
The library cannot enforce a singleton pattern if it can't uniquely identify the displays, and it can't do that when allow_duplicates
is enabled. This is not a scenario I want to support and at that point, it's down to the user to sort that out manually.
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.
Thank you for your feedback.
The only usage of singleton pattern is for _fade_thread
, the instance variable within sbc.Display
. It doesn't modify any existing user behaviors or introduce any new potential issues.
In terms of uniqueness, neither the existing code nor this PR can guarantee it. However, I believe that ensuring uniqueness should be a goal for future development.
- These displays cannot be told apart. As far as the software knows, they are the same display
Regarding the third point, it's important to note that Windows does not distinguish displays based on their hardware information, but rather by the interface they are connected to. Therefore, in the last two scenarios, these are neither duplicates nor same display from a Windows perspective.
Singleton pattern can surely be replaced with other implementations. I chose it for the potential benefits it could provide in the future.
Duplicates being allowed is very much an edge case
I understand that allowing duplicates is considered an edge case. However, I believe there are users, like me, who find this feature useful. I appreciate your patience with my previous PR. Besides, I've come to realize that the current implementations around duplicates are not sufficient and I agree that a more incremental approach is a better choice for now.
For this PR, could you please specify what changes you would like to see in the next commit?
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.
these are neither duplicates nor same display from a Windows perspective
If it's possible to get a useful unique identifier from windows, that's great. We can use that in get_identifier
instead. Last time I looked at this I couldn't find anything that fit the bill, but that was a while ago and this should be re-evaluated. I'll have another read of the MS docs.
Ideally Linux would have something similar, but there's no guarantees.
I believe there are users, like me, who find this feature useful
I agree. The stoppable threads bit is really nice. The singleton bit would be nice to have, but it requires us to be able to reliably uniquely identify monitors.
the current implementations around duplicates are not sufficient
Current implementations for detecting and de-duplicating monitors? Alright but not great.
Current implementation for allowing duplicates? Not supported and not planned to be supported. My approach instead would be to better identify monitors so that we can tell them apart (maybe by using some windows GUID in the future).
For this PR, could you please specify what changes you would like to see in the next commit?
Keeping track of the thread within the instance is a good safeguard, but the user will have to manage the class instance on their own without a singleton.
The issue is that we can't enforce this pattern reliably if we can't uniquely identify monitors reliably.
If ALLOW_DUPLICATES==False
and get_identifier
falls back to the index
, that doesn't work.
If ALLOW_DUPLICATES==False
and one method can fetch the display's serial, whereas another can only fetch the EDID, that doesn't work.
If ALLOW_DUPLICATES==True
and one display can be addressed via multiple methods, that doesn't work either.
I think implementing a safe guard that sometimes works and sometimes doesn't will cause issues, and if I were a user I would have a hard time trusting it.
I like the singleton idea and would love to revisit it in the future, but, at the moment the foundations for it aren't there.
Make `fade_brightness()` thread stoppable
Thank you for your feedback. |
Thanks for this, and thanks for your patience during the review process |
PR Summary
This PR introduces enhancements to the
fade_brightness()
method in theDisplay
class, aimed at improving how multiple brightness fade requests are handled. Previously, executing severalfade_brightness()
calls in rapid succession risked initiating overlapping non-blocking threads, potentially causing display flickering. The updates ensure that only the most recent fade request is executed, while all earlier requests are efficiently terminated.Key Changes
One Physical Display -> One
Display
Instance -> One Fade ThreadSingleton Pattern for Display Instances:
Display
instances, ensuring that each physical display is associated with a uniqueDisplay
instance. These instances are managed within a dictionary,_instances
, centralizing the control of fade operations and ensuring coordinated execution across displays.Thread Management with
_fade_thread
:_fade_thread
attribute has been introduced within eachDisplay
instance, allowing for the tracking of the thread currently handling the fade operation. This mechanism is crucial for identifying and halting any preceding fade operations when a new fade request is made, ensuring that only the latest request remains active.Method Refactoring:
fade_brightness()
method has been renamed tofade_brightness_thread()
, which now solely carries out the fade logic within a dedicated thread.Unified Fade Adjustment Invocation:
sbc.fade_brightness()
andsbc.Display.fade_brightness()
. Regardless of the method used, both share the same exclusive thread for a given display.Example Program Demonstrating the PR Benefits