11/*! The [`Sender`] and [`Receiver`] traits, for wgpu transports.
22
33This module defines the [`Sender`] and [`Receiver`] traits, representing the
4- endpoints of a one-way communications channel between a wgpu client and server.
5- A single `Sender` sends messages to a single `Receiver`. A `Sender` can also
6- create memory segments shared with the `Receiver` to hold message content and
7- other common data.
8-
9- Remote wgpu access requires two `Sender`/`Receiver` pairs, providing
10- communication in each direction between client and server. This documentation
11- refers to an interacting client and server as "counterparts".
12-
13- `Sender` and `Receiver` are low-level traits. Using them entails working with
14- raw pointers to shared memory segments, so users will generally need to build
15- higher-level abstractions around them them to provide well-typed interfaces,
16- prevent data races, batch messages to reduce IPC overhead, and so on.
17-
18- For example, to build a communications channel carrying typed messages that was
19- generic over `Sender` implementations, you could use `Sender` to create and map
20- a shared memory segment, and then serialize the messages into the shared memory.
21- When it became full, or a response was needed, you would call
22- [`Sender::send_message`] to alert your counterpart to the whole batch of
23- messages. Your [`Receiver::receive_message`] implementation in the counterpart
24- would then map the shared memory segment, deserialize the messages from the
25- given range, and process them.
26-
27- ## Senders
28-
29- A [`Sender`] implementation is responsible for interacting with the operating
30- system to create memory segments that are shared with its `Receiver`, and
31- sending unbuffered messages to the `Receiver`. A `Sender` should be agnostic to
32- the actual content of those memory segments and messages; only the `Sender`'s
33- user knows their interpretation.
4+ endpoints of a one-way communications channel between a wgpu client and server,
5+ based on shared memory. A single `Sender` sends messages to a single `Receiver`.
6+ For two-way communication, each side needs its own `Sender` and `Receiver`. This
7+ documentation refers to the two sides of a connection as "counterparts".
8+
9+ The [`Sender`] trait abstracts over platform APIs to define a portable but
10+ low-level interface for communication based on shared memory. To keep [`Sender`]
11+ easy to implement, it is a low-level interface: byte-oriented, unbuffered, and
12+ with no enforcement of synchronized access to shared memory. However, it should
13+ be sufficient for applications to build well-typed, thread-safe abstractions
14+ that are generic over any `Sender` implementation.
15+
16+ As messages arrive from the counterpart, a `Sender` implementation passes them
17+ to a [`Receiver`] implementation provided by the user, which serves the role of
18+ "callback" or "event handler". Like [`Sender`], [`Receiver`] is a low-level,
19+ byte-oriented interface; interpretation of the contents as data meaningful to
20+ the application is left to the [`Receiver`] implementation.
3421
3522The `Sender` trait is meant to be easy to implement in terms of a wide range of
3623operating system mechanisms:
3724
38- - A Unix implementation might use [`mmap`] to create memory segments, and then
39- send `SCM_RIGHTS` messages over [`AF_UNIX`] sockets to share them with their
40- counterpart.
25+ - A Unix implementation might use [`AF_UNIX`] address family sockets
26+ (also known as "Unix domain sockets") to exchange messages. Shared
27+ memory segments would be created with [`memfd_create`], conveyed to
28+ the counterpart over the socket using `SCM_RIGHTS` ancillary
29+ messages, and mapped into each side's address space with [`mmap`].
4130
42- - A Windows implementation might use [`CreateFileMappingW`] to create memory
43- segments, and then use [`DuplicateHandle`] to share them.
31+ - A Windows implementation might use ordinary sockets for
32+ communication, [`CreateFileMappingW`] to create memory segments, and
33+ then use [`DuplicateHandle`] to share them.
4434
4535- For testing, the [`transport::local`] module provides a [`Sender`]
4636 implementation in which both sides of the connection live in the
4737 same process. "Shared" memory is simply an ordinary block of memory.
4838
49- ## Receivers
50-
51- An implementation of the [`Receiver`] trait serves as the callback invoked when
52- messages are received. It is meant to be implemented by users of the transport.
53-
54- Exactly how `Receiver`s get called when messages arrive is specific to the
39+ Exactly how [`Receiver`]s get called when messages arrive is specific to the
5540`Sender` implementation:
5641
5742- A `Sender` implementation might spawn a thread to read messages from a socket
@@ -61,13 +46,10 @@ Exactly how `Receiver`s get called when messages arrive is specific to the
6146 some sort of platform event loop, and have that listener call the
6247`Receiver` when appropriate.
6348
64- Whatever the case, the `Sender` implementation should document this behavior.
65- For example, users may need to know which thread the `Receiver` is invoked on to
66- avoid deadlocks.
67-
68- The [`Sender`] and [`Receiver`] traits are meant to integrate smoothly with
69- existing interprocess communication mechanisms and event loops, like Firefox's
70- [`IPDL`] and [`nsISerialEventTarget`].
49+ While the [`Sender`] and [`Receiver`] traits can be implemented directly in
50+ terms of operating system facilities, they are also meant to integrate smoothly
51+ with existing interprocess communication mechanisms and event loops, like
52+ Firefox's [`IPDL`] and [`nsISerialEventTarget`].
7153
7254Although these traits are designed for use with `wgpu`, this module attempts to
7355fully specify the contract between a transport and its user, independently of
7759
7860## Shared memory
7961
80- The [`Sender`] and [`Receiver`] traits are meant for use in situations where the
81- endpoints can share memory with each other, such that writes to a shared memory
82- segment on one side are immediately visible on the other.
62+ The [`Sender`] and [`Receiver`] traits are intended for use in situations where
63+ the endpoints can share memory with each other, such that writes to a shared
64+ memory segment on one side are immediately visible on the other.
8365
8466- [`Sender::allocate_shared_memory`] creates a shared memory segment, and
8567 returns an id by which both counterparts can refer to it.
8668
8769- [`Sender::send_message`] sends the counterpart a message whose content resides
88- in a shared memory segment.
70+ in a given shared memory segment.
8971
9072- [`Sender::map_shared_memory`] takes a given shared memory segment and makes
9173 it visible in the caller's address space.
9274
9375- [`Sender::close_shared_memory`] frees a shared memory segment.
9476
95- However, it is possible to implement `Sender` without using shared memory (for
96- example, over a network connection) if the transport's user is willing to make
97- calls to [`Sender::flush_shared_memory_range`] to explicitly indicate which
98- regions of its shared memory segments have new content that must be copied to
99- the counterpart. If used correctly, this interface allows the transport to
100- behave as expected whether or not shared memory is used; the shared memory
101- becomes merely a transparent optimization, not an architectural feature.
77+ The application can create as many shared memory segments as it needs. Shared
78+ memory handles are transparent newtypes around integers, so they are easy to
79+ refer to in messages or data structures held in other shared memory segments.
80+
81+ For example, a WebGPU implementation might create a shared memory segment to
82+ hold a queue of API calls made by web content that are waiting to be conveyed to
83+ a GPU sandbox process for execution; and it might create additional shared
84+ memory segments representing mappable buffer contents.
85+
86+ ## Using `Sender` without shared memory
87+
88+ It is possible to implement `Sender` without using shared memory (for example,
89+ over a network connection), if the transport's user is willing to make calls to
90+ [`Sender::flush_shared_memory_range`] to explicitly indicate which regions of
91+ its shared memory segments have new content that must be copied to the
92+ counterpart. Although this interface is trickier to use, it allows the transport
93+ to behave as expected whether or not it can actually create memory segments
94+ shared with the counterpart, which in turn allows the application to work over a
95+ broader range of transports. Shared memory becomes merely a transparent
96+ optimization, not an architectural feature.
10297
10398The requirement to flush modified regions is not as onerous as one might expect.
104- In practice, the steps needed to ensure that a shared-memory interaction is free
105- of data races often also make it apparent where flushes are necessary. For
106- example, to ensure consistent behavior between browsers, WebGPU's buffer API
107- fully separates CPU access from GPU access; WebGPU's mapping and unmapping steps
108- indicate where flushes on some underlying transport would need to occur.
99+ In practice, it is often the case that, by the time one has ensured that the
100+ application's interactions with shared memory segments are free of data races,
101+ it is also apparent where flushes would be necessary.
102+
103+ For example, although WebGPU's buffer mapping behavior is intended to be
104+ implemented using memory regions that are shared between the web content process
105+ and a sandboxed process that interacts directly with the GPU, it is also
106+ possible to implement WebGPU without shared memory. To ensure consistent
107+ behavior across browsers and GPUs, WebGPU's buffer API segregates web content
108+ access from GPU access: web content can access a buffer only after mapping it,
109+ and the GPU can access a buffer only when it is unmapped. These ownership
110+ transitioning operations are where flushes would need to occur, in the case that
111+ the web content and GPU process do not actually share memory:
112+
113+ - Before a buffer is mapped by web content, the GPU process must flush any
114+ regions of the buffer it may have written to.
115+
116+ - When web content unmaps a buffer, the content process must flush any regions
117+ of the buffer web content modified (conservatively, the entire buffer).
109118
110119An implementation of [`Sender`] may guarantee that it uses shared memory. Users
111- of such an implementation need not call [`flush_shared_memory_range`].
112- Naturally , taking advantage of this looser contract limits which transport
120+ of such an implementation need not call [`flush_shared_memory_range`], but
121+ naturally , taking advantage of this looser contract limits which transport
113122implementations they can use.
114123
115124[`Sender`] implementations may even decide whether or not to use shared memory
116- dynamically. Users of such implementations should assume the worst, and call
125+ dynamically. Users of such implementations mustx assume the worst, and call
117126[`flush_shared_memory_range`] as described in its documentation.
118127
119128In Rust, data races are undefined behavior. Users of these traits are
0 commit comments