blob: 4c23abd985f6daa4c7b151ca66b335b8e21e6098 [file] [log] [blame] [view] [edit]
# input_pipeline > Coding
Reviewed on: 2022-03-22
## Shared-mutable data structures
Rust provides multiple options for how to manage ownership of shared data structures
(e.g. `Rc`, `Arc`), and multiple options for how to ensure memory safety when mutating
shared data structures (e.g. `RefCell`, `fuchsia_sync::Mutex`).
This section describes the recommended options for code within this library.
If these options seem unsuitable for your use case, please ask a reviewer _before_
writing code that uses some other option.
1. `InputHandler`s _should_ be wrapped in `Rc` instead of `Arc`. This is primarily
for semantic consistency with the fact that `InputHandler`s are not guaranteed
to be `Send`, but may have some performance benefits as well.
1. Implementations of `InputHandler` _should_ wrap mutable state in a `RefCell`,
rather than a `fuchsia_sync::Mutex` or `futures::lock::Mutex`.
* The primary benefit of `RefCell` over the mutexes is that, if a program
tries to concurrently access the data in incompatible ways
(`borrow()` + `borrow_mut()`), the program will panic instead of deadlocking.
* A secondary benefit is that there's no confusion over which wrapper is
being used. (`Mutex` is very confusing given that there are three commonly
used implementations.)
## Background tasks
In some cases, a pipeline stage wants to do work independently of newly arriving
input events. For example, the `autorepeater` wants to periodically send new key
events when a key has been pressed but _not_ released.
Because the work is independent of newly arriving events, there's no natural way
to integrate the autorepeat logic (wait for a timer, send an event) with
`InputHandler::handle_input_event()`.
For this reason, the autorepeat logic uses the Fuchsia Async library's `Task`
facility.
This section describes the recommended way to use background tasks within this
library. If this seems unsuitable for your use case, please ask a reviewer
_before_ writing code that uses some other option.
1. Use `fuchsia_async::Task::local()`, rather than `fuchsia_async::Task::spawn()`.
Given that the input pipeline code [runs on a `LocalExecutor`](parallelism.md),
the two functions for creating a task will behave identically. However,
`Task::local()` better documents the fact that the code was not written, reviewed,
or tested with multithreaded use in mind.
1. Store the `Task` within a data structure (e.g. the `struct` that created the `Task`),
rather than `detach()`-ing the `Task`. Having the `Task` within the data structure
is a convenient and low-effort way of documenting that the struct is active, rather
than inert. Said differently: the `Task` field in the struct makes it obvious that
the struct may act spontaneously, rather than, e.g. only responding to function calls.
Note: some existing code does not follow these recommendations. Such code will be
migrated over time.
## Re-entrancy
Some `InputHandler`s need to maintain state which depends on the order in which
events are received. If [`handle_input_event()`](https://cs.opensource.google/fuchsia/fuchsia/+/main:src/ui/lib/input_pipeline/src/input_handler.rs;drc=736d1cff60799806705e26b3473457acbfb31bb7;l=30) could be invoked re-entrantly, a handler would probably need to buffer the re-entrant
calls internally, to avoid corrupting mutable state.
Instead, the `handle_input_event()` API is [documented as _not_ being reentrant-safe](https://cs.opensource.google/fuchsia/fuchsia/+/main:src/ui/lib/input_pipeline/src/input_handler.rs?q=%22should%20not%20be%20invoked%20concurrently%22),
and the `InputPipeline` struct [always waits for an `InputHandler` to complete
processing](https://cs.opensource.google/fuchsia/fuchsia/+/main:src/ui/lib/input_pipeline/src/input_pipeline.rs?q=handle_input_event%5C(event%5C).await&ss=fuchsia%2Ffuchsia) an `InputEvent`,
before invoking `handle_input_event()` again on the same handler.
Consequently, `InputHandler`s _should_ assume that `handle_input_event()`
is not invoked concurrently.
In addition to avoiding the need for internal buffering, this may also simplify
reasoning about the correctness of the use of `RefCell::borrow()` and
`RefCell::borrow_mut()`.
For example, for some handlers, code in `handle_input_event()` and its callees
_may_ be able to assume that data read while holding a `RefCell::borrow()` or
`RefCell::borrow_mut()` guard remains unchanged even after releasing the guard
and yielding (via the `await` operator).
> NOTE: Such an assumption is _only_ appropriate if
> 1. The handler does _not_ create `fuchsia_async::Task`s which can modify the
> mutable state behind the `RefCell`, OR
> 2. It is semantically correct for the `handle_input_event()` `Future`
> to use the old value.
>
> Otherwise, the other `Task`s may execute while `handle_input_event()` is
> suspended, and the state cached in `handle_input_event()` `Future` will be
> (incorrectly) used instead of the value updated by the other `Task`s.
## Error handling (implementation code)
The input pipeline library runs in the same process (`scene_manager`) as the code
that sets up Scenic. Hence, if an `InputHandler` calls `panic!()` (or methods
such as `unwrap()` or `expect()` on `None`/`Err` variants), all graphical programs
will terminate.
Hence, implementation code should avoid aborting the program, and should,
instead, propagate errors to its caller (e.g. for callees of `handle_input_event()`),
or log errors messages (for `Task`s that run independently of `handle_input_event()`).
Note that keeping the program running allows the user has the opportunity to
continue their work using other input modalities, which don't depend on the
erroneous handler.
Some existing handlers use `panic!()`, `unwrap()`, and `expect()`. Such code
will be migrated over time.
## Error handling (test code)
When a test aborts, the test runner outputs a backtrace, which is helpful
for debugging the test. When a test returns an `Err`, however, the test
runner does not print a backtrace.
Hence, test code, should _prefer_ to abort, rather than propagating errors.
## Blocking
Implementations of [`InputHandler::handle_input_event()`][handle-input-event]
should avoid blocking unnecessarily, as that prevents [`InputEvent`s][input-event]
from propagating to downstream handlers in a timely manner.
For example, consider [`MediaButtonsHandler`][media-buttons-handler], which
handles [`ConsumerControlsEvent`][consumer-controls-event]s by sending events to
[`fuchsia.ui.policy.MediaButtonsListener`][media-buttons-listener] channels.
After https://fxbug.dev/42058162 is resolved, we will consider a [`ConsumerControlsEvent`][consumer-controls-event]
handled when the [`MediaButtonsHandler`][media-buttons-handler]
has invoked the [`OnEvent()`][on-event-fidl] method on all listeners, even if the
listeners have not yet acknowledged the call.
By doing so, we will unblock handling of events intended for handlers downstream
of [`MediaButtonsHandler`][media-buttons-handler]. In particular, [`TouchInjectorHandler`][touch-injector-handler]
will be able to process [`TouchScreenEvent`s][touchscreen-event]
before the [`MediaButtonsListener`s][media-buttons-listener] have acknowledged
the media buttons event.
Handlers _should_ still listen for acknowledgements, to prevent unread ACKs from
accumulating in the associated Zircon channel. However, that work can be done off
of the critical path of `handle_input_event()`.
## Unresponsive clients in general
Beyond the advice about blocking above, we don't have any advice about how to deal with
unresponsive clients. For example, we do not yet have enough experience to recommend
that handlers should, or should not, time out a client that fails to acknowledge a
message after a set period of time.
[consumer-controls-event]: https://cs.opensource.google/fuchsia/fuchsia/+/main:src/ui/lib/input_pipeline/src/consumer_controls_binding.rs;l=17
[handle-input-event]: https://cs.opensource.google/fuchsia/fuchsia/+/main:src/ui/lib/input_pipeline/src/input_handler.rs;l=30
[input-event]: https://cs.opensource.google/fuchsia/fuchsia/+/main:src/ui/lib/input_pipeline/src/input_device.rs;l=31
[media-buttons-handler]: https://cs.opensource.google/fuchsia/fuchsia/+/main:src/ui/lib/input_pipeline/src/media_buttons_handler.rs;l=20
[media-buttons-listener]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/fidl/fuchsia.ui.policy/device_listener.fidl;l=32
[on-event-fidl]: https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/fidl/fuchsia.ui.policy/device_listener.fidl;l=40
[touch-injector-handler]: https://cs.opensource.google/fuchsia/fuchsia/+/main:src/ui/lib/input_pipeline/src/touch_injector_handler.rs;l=25
[touchscreen-event]: https://cs.opensource.google/fuchsia/fuchsia/+/main:src/ui/lib/input_pipeline/src/touch_binding.rs;l=25