Skip to content
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

API Thread Safety #49

Open
arcnmx opened this issue Feb 25, 2018 · 5 comments
Open

API Thread Safety #49

arcnmx opened this issue Feb 25, 2018 · 5 comments
Labels

Comments

@arcnmx
Copy link

arcnmx commented Feb 25, 2018

From writing a rust wrapper around the c api I've realised that there's no documentation on the thread-safety (or lack thereof) for any of the API calls. A few specific scenarios I'm wondering about, and whether they're safe and/or guaranteed:

  • is it safe to enumerate/create display handles on one thread, then spawn another thread and run operations there? Can you move handles to other threads between operations without issue?
    • I think this mostly is a question of whether there's any use of hidden background threads or TLS when API operations aren't running?
  • is it safe to concurrently run operations on different threads with different/distinct displays?
    • the CLI has a --async flag that indicates this could maybe be okay?
  • is it safe to concurrently run operations on the same handle from different threads?
    • this one seems useless because you'd have to wait for one operation to complete before another can start, but it's useful to know whether there's any inherent synchronization in the implementation or not.
@rockowitz
Copy link
Owner

rockowitz commented Feb 26, 2018 via email

@arcnmx
Copy link
Author

arcnmx commented Feb 26, 2018

Thanks for clearing that up! My use case is a pretty niche application for sharing a monitor with a QEMU VM - an X program that manages input redirection and switching the monitor back and forth when interaction with a VM is desired (the code in that repo using ddcutil has not been committed yet). I'm aiming to keep the bindings at feature parity with the C API, and it is pretty feature-complete so far: a messy short example that replicates "ddcutil getvcp ALL" covers most of the capability parsing and screen control.

As for how I'm handling threading, most of my application uses nonblocking IO and futures-rs. libddcutil API calls are just done on a secondary thread and message passing is used to indicate completion/failure/etc. back to the application. Regarding feedback on the API:

  • A nonblocking API that integrates into an event loop using epoll or similar would be ideal, but that seems complicated to design. A thread per display mostly just blocking on syscalls isn't a huge waste of resources anyway, as DDC events are rather infrequent, so I think the current blocking API is perfectly fine.
  • An async variant of the API such as the experimental ddca_start_get_any_vcp_value that just spawns threads internally is not useful for a Rust API, as it's better for the threading and synchronization to be managed in Rust anyway. This is probably the same conclusion you've come to in using Qt.
  • I believe something like ddca_free_any_vcp_value() is missing, as currently you have to check the param type and if it's a table, there's an extra free() to be done.
  • Perhaps a struct versioning scheme could be used to enable backwards compatibility once the API stabilizes. There are tons of breaking changes being made, and a way to mitigate breakage in the future after the library hits a "1.0" release may be helpful. This is probably not a priority right now.

What we don't want:

  • Using multiple display handles to access the same display. The API
    needs to be extended to prevent this.
  • Making multiple overlapping requests (from different threads) to the
    same display handle.

In practice, you probably can't prevent the application (or another application) from just opening the I2C device anyway and interfering, so I'm not sure there's much advantage to this. Managing global state to prevent this may end up complicating thread-safety as well, and adding locks could be unneeded overhead for single-threaded applications. A KISS API that simply forbids this and requires downstream library users to manage their own synchronization if they wish to share handles across threads is preferable, because the Rust type system can prevent handles from being used across threads without needing runtime locks or checks.

@rockowitz
Copy link
Owner

rockowitz commented Mar 4, 2018 via email

@arcnmx
Copy link
Author

arcnmx commented Mar 4, 2018

So you have to look at the identifier fields in the EDID to fully check if monitors are the same

Hm, I object to that change and this sounds like trouble, I don't think this is something ddcutil should handle. Sounds like a lot of complexity and too many edge cases for no benefit:

  1. What if you have two identical monitor models that do not contain a unique serial number or other identifying information? Would this prevent you from controlling both because ddcutil thinks they're the same monitor?
  2. It seems like it should be valid to have two DDC connections to the same monitor on different inputs. This is a totally expected and normal use case when using a TV or monitor with multiple devices, and my own monitors block off DDC temporarily on all inputs except the currently active one. My own use-case for ddcutil actually relies on this behaviour and expects there to be two video connections to the same monitor, and keeps an active display handle open to control it and switch between them.

If multiple applications have multiple copies of the ddcutil library, then there is no cross-checking. The only real solution is for the ddcutil library to become a service daemon.

Yeah, this is again something you cannot feasibly guard against, because someone could be using ddccontrol or something else instead. It is not ddcutil's responsibility to guard against every possible wrong use of DDC, and as long as those two applications cooperate and serialize their requests to the underlying monitor they're not even doing anything wrong by holding multiple handles open.

This seems like limiting flexibility of ddcutil while trying to guard against something that shouldn't be a problem. Documenting that "you should not make multiple requests to the same display without expecting to encounter I2C communication errors" (whether that's through the same handle or multiple handles to the same display) was really the most I was looking to clarify here - anything else sounds like complex and fallible application logic. Using an atomic in the display info pointer to prevent multiple handles from being opened is probably the most ddcutil should do, but it doesn't even sound like that would be necessary or effective.

@rockowitz
Copy link
Owner

rockowitz commented Mar 5, 2018 via email

@rockowitz rockowitz added the API label Sep 18, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants