WebRTC streamer and vhost-user-input backend processes communicate with a client-server protocol - #3111
WebRTC streamer and vhost-user-input backend processes communicate with a client-server protocol#3111jemoreira wants to merge 6 commits into
Conversation
The webRTC streamer can connect to a unix socket for each input device to inject input events and receive status feedback, in addition to the existing approach based on receiving the connection as an inherited file descriptor. The behavior is chosen base on command line flags passed by run_cvd. Bug: b/552079861
and implement it with the existing socket pair connected to stdin. Bug: b/552079861
The vhost-user-input process accepts a UNIX socket's fd via command line flag. When given, events are not read from stdin, but instead from clients connected to that unix socket. Multiple clients can connect at the same time and the server guarantees that each group of events ending in a SYN_REPORT event is delivered to the VMM atomically. Bug: b/552079861
webrtc connects to a unix socket hosted by vhu-input instead of through a socket pair inherited from run_cvd. run_cvd unconditionally enables this behavior, assuming that webRTC and cf_vhost_user_input are already substituted everywhere run_cvd is substituted. Bug: b/552079861
| rotary_sockets_ = | ||
| CF_EXPECT(NewDeviceSockets(RotaryEventsServerPath(instance_), | ||
| RotarySocketPath(instance_)), | ||
| "Failed to setup sockets for rotary device"); |
There was a problem hiding this comment.
Confirming my understanding: run_cvd creates the unix server sockets and then launches webrtc with command line flags with the server path. This preserves the ordering guarantee that the server sockets are created before the client sockets try to connect to them.
What is gained by passing paths rather than file descriptors to webrtc? Does webrtc now retry connections to the server?
There was a problem hiding this comment.
Your understanding is correct.
WebRTC doesn't retry connections. The main benefit is that if an instance of webRTC crashes mid write it will corrupt that connection since all future events will be read misaligned by the server. The problem goes away by making each instance of webRTC have their own independent connection. This isn't that big a deal right now because a crashed webRTC brings down the entire device, but that may not be the case in the future.
| .events_server = SharedFD::SocketLocalServer(evt_server_path, false, | ||
| SOCK_STREAM, 0600), | ||
| .vhu_server = SharedFD::SocketLocalServer(vhu_server_path, false, | ||
| SOCK_STREAM, 0600), |
There was a problem hiding this comment.
nit: this could be written as
.events_server = CF_EXPECT(Fd::SocketLocalServer(...)),
.vhu_server = CF_EXPECT(Fd::SocketLocalServer(...)),saving the ->IsOpen() calls later.
| std::vector<std::string> touch_paths = | ||
| input_paths_provider_.TouchscreenPaths(); | ||
| for (const std::string& touchpad_path : | ||
| input_paths_provider_.TouchpadPaths()) { | ||
| touch_paths.push_back(touchpad_path); | ||
| } | ||
| cmd.AddParameter("-touch_fds=", touch_connections[0]); | ||
| for (int i = 1; i < touch_connections.size(); ++i) { | ||
| cmd.AppendToLastParameter(",", touch_connections[i]); | ||
| cmd.AddParameter("-touch_server_paths=", touch_paths[0]); | ||
| for (int i = 1; i < touch_paths.size(); ++i) { | ||
| cmd.AppendToLastParameter(",", touch_paths[i]); | ||
| } |
There was a problem hiding this comment.
This had an awkward construction before to take advantage of Command's special handling of SharedFD, but that is no longer needed. This can now be
cmd.AddParameter("-touch_server_paths=", absl::StrJoin(touch_paths, ","));
| /// Reads available bytes from the underlying reader. Returns number of bytes read during this | ||
| /// call (which may be less than the resulting size of the buffer) or 0 on EOF. | ||
| pub fn read_ahead(&mut self) -> Result<usize> { |
There was a problem hiding this comment.
read_ahead and consume look very similar to methods in std::io::BufRead. Is it possible to implement this trait instead of defining a custom API for these methods?
Implementing BufRead is not mutually exclusive with exposing additional pub functions in a separate impl, so buffer and reader can still be exposed.
| use crate::buf_reader::BufReader; | ||
| use crate::vio_input::{trim_to_event_size_multiple, VIRTIO_INPUT_EVENT_SIZE}; | ||
|
|
||
| /// Provides input events to the vhost-user backend. Implementations may can get these events from |
There was a problem hiding this comment.
nit: "may" or "can" but not both.
| fn get_events(&mut self) -> Vec<u8>; | ||
| /// Send status feedback such as keyboard LED states back to the source. | ||
| fn send_status_feedback(&mut self, status_buffer: Vec<u8>); |
There was a problem hiding this comment.
It seems like a deficiency of the callers that they can't handle these functions returning errors. Rather than force every implementation to panic or log, can the implementations return errors normally and have the one caller deal with converting the errors to aborts or logs?
In particular the one caller of send_status_feedback already returns a Result.
| panic!("Background thread's channel closed unexpectedly"); | ||
| } | ||
| } | ||
| std::mem::take(&mut *self.events.lock().unwrap()) |
| mut events_fd: UnixStream, | ||
| events: Arc<Mutex<Vec<u8>>>, | ||
| status: Arc<Mutex<Vec<u8>>>, | ||
| ) { |
There was a problem hiding this comment.
anyhow::Error is Send and Sync so it is possible to return error values rather than aborting everywhere.
| let events_clone = events.clone(); | ||
| let status = Arc::new(Mutex::new(Vec::new())); | ||
| let status_clone = events.clone(); | ||
| std::thread::spawn(move || server_loop(listener, bg_events_fd, events_clone, status_clone)); |
There was a problem hiding this comment.
Should the JoinHandle for this thread be associated with the UnixSocketEventSource? Should this thread outlive the UnixSocketEventSource?
| // These events were checked on previous calls. | ||
| let skip_len = trim_to_event_size_multiple(reader.buffer().len() - read_len); |
There was a problem hiding this comment.
Shouldn't events on previous calls be removed from consideration using consume?
There was a problem hiding this comment.
Events from previous reads are only removed from the buffer after receiving the SYN_REPORT event. If this code is running on a following read, that means SYN_REPORT was not found in the previously read events so those are skipped in the search.
cf_vhost_user_inputlistens on a UNIX socket server it inherits fromrun_cvd.webRTCconnects to this server to inject input events (mouse, touch, keyboard, etc) to the device backends. This enables the input backend to receive events from sources outside the streamer process.In addition to the protocol change, the device backend ensures all groups of events sent to the guest driver end in a
SYN_REPORT. TheseSYN_REPORTevents are used to group events from multiple sources so that the driver receives a valid flow of events.The behavior is enabled by run_cvd by the flags it launches
webRTCandcf_vhost_user_inputwith. When therun_cvdbinary is not substituted, events are sent via a socket pair as before. It would be a problem ifrun_cvdwas substituted butwebRTCorcf_vhost_user_inputwas not, but this situation is extremely unlikely since the latter have been substituted for a while now.Bug: b/552079861