11/*! The [`Sender`] and [`Receiver`] traits, for wgpu transports.
22
3- This module defines the [`Sender`] and [`Receiver`] traits,
4- representing the endpoints of a communications channel between a wgpu
5- client and server. A `Sender` shares memory with and sends messages to
6- a single `Receiver`. Remote wgpu access requires a pair of
7- `Sender`/`Receiver` pairs that provide communication in each direction
8- between client and server.
3+ This 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`. The two sides use
6+ shared memory segments to hold message content and other common data. Remote
7+ wgpu access requires two `Sender`/`Receiver` pairs, providing communication in
8+ each direction between client and server. This documentation refers to an
9+ interacting client and server as "counterparts".
910
10- These are low-level traits, meant to be easy to implement in terms of
11- a wide range of operating system mechanisms:
11+ `Sender` and `Receiver` are low-level traits, meant to be easy to implement in
12+ terms of a wide range of operating system mechanisms:
1213
13- - A Unix implementation might use [`mmap`] to create memory segments,
14- and then send `SCM_RIGHTS` messages over [`AF_UNIX`] sockets to
15- share them with their counterpart.
14+ - A Unix implementation might use [`mmap`] to create memory segments, and then
15+ send `SCM_RIGHTS` messages over [`AF_UNIX`] sockets to share them with their
16+ counterpart.
1617
17- - A Windows implementation might use [`CreateFileMappingW`] to create
18- memory segments, and then use [`DuplicateHandle`] to share them.
18+ - A Windows implementation might use [`CreateFileMappingW`] to create memory
19+ segments, and then use [`DuplicateHandle`] to share them.
1920
2021- For testing, the [`local]` module this crate provides a [`Sender`]
21- implementation in which both sides of the connection live in the
22- same process. "Shared" memory is simply an ordinary block of memory.
22+ implementation in which both sides of the connection live in the same process.
23+ "Shared" memory is simply an ordinary block of memory.
2324
24- The [`Sender`] and [`Receiver`] traits are meant to integrate smoothly
25- with existing interprocess communication mechanisms and event loops,
26- like Firefox's [`IPDL`] and [`nsISerialEventTarget`].
25+ The [`Sender`] and [`Receiver`] traits are meant to integrate smoothly with
26+ existing interprocess communication mechanisms and event loops, like Firefox's
27+ [`IPDL`] and [`nsISerialEventTarget`].
28+
29+ Although these traits are designed for use with `wgpu`, this module attempts to
30+ fully specify the contract between a transport and its user, independently of
31+ the details of `wgpu` or WebGPU. This is meant to help developers implement and
32+ test transports in isolation from graphics APIs, browsers, applications, and so
33+ on.
34+
35+ ## Shared memory
36+
37+ The [`Sender`] and [`Receiver`] traits are meant for use in situations where the
38+ endpoints can share memory with each other, such that writes to a shared memory
39+ segment on one side are immediately visible on the other.
40+
41+ - [`Sender::allocate_shared_memory`] creates a shared memory segment, and
42+ returns an id by which both counterparts can refer to it.
43+
44+ - [`Sender::send`] sends the counterpart a message whose content resides in a
45+ shared memory segment.
46+
47+ - [`Sender::map_shared_memory`] takes a given shared memory segment and makes
48+ it visible in the caller's address space.
49+
50+ - [`Sender::close_shared_memory`] frees a shared memory segment.
51+
52+ However, it is possible to implement `Sender` without using shared memory (for
53+ example, over a network connection) if the transport's user is willing to make
54+ calls to [`Sender::flush_shared_memory_range`] to explicitly indicate which
55+ regions of its shared memory segments have new content that must be copied to
56+ the counterpart. If used correctly, this interface allows the transport to
57+ behave as expected whether or not shared memory is used; the shared memory
58+ becomes merely a transparent optimization, not an architectural feature.
59+
60+ The requirement to flush modified regions is not as onerous as one might expect.
61+ In practice, the steps needed to ensure that a shared-memory interaction is free
62+ of data races often also make it apparent where flushes are necessary. For
63+ example, to ensure consistent behavior between browsers, WebGPU's buffer API
64+ fully separates CPU access from GPU access; WebGPU's mapping and unmapping steps
65+ indicate where flushes on some underlying transport would need to occur.
66+
67+ An implementation of [`Sender`] may guarantee that it uses shared memory. Users
68+ of such an implementation need not call [`flush_shared_memory_range`].
69+ Naturally, taking advantage of this looser contract limits which transport
70+ implementations they can use.
71+
72+ [`Sender`] implementations may even decide whether or not to use shared memory
73+ dynamically. Users of such implementations should assume the worst, and call
74+ [`flush_shared_memory_range`] as described in its documentation.
75+
76+ In Rust, data races are undefined behavior. Users of these traits are
77+ responsible for using shared memory in a way that is free of data races. If a
78+ [`Sender`] implementation does not use shared memory, its users must assume that
79+ calls to [`flush_shared_memory_range`] and [`send`] are the only
80+ synchronization operations that establish an ordering between memory accesses
81+ for the purposes of avoiding data races.
2782
2883[`mmap`]: https://man7.org/linux/man-pages/man2/mmap.2.html
2984[`AF_UNIX`]: https://man7.org/linux/man-pages/man7/unix.7.html
3085[`CreateFileMappingW`]: https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-createfilemappingw
3186[`DuplicateHandle`]: https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle
3287[`IPDL`]: https://firefox-source-docs.mozilla.org/ipc/ipdl.html
3388[`nsISerialEventTarget`]: https://searchfox.org/mozilla-central/rev/7f7e8f6e4b8e09b145d29a57a1b341c6b11f4225/xpcom/threads/nsISerialEventTarget.idl#25
89+ [`flush_shared_memory_range`]: Sender::flush_shared_memory_range
90+ [`send`]: Sender::send
3491
3592*/
3693
@@ -41,100 +98,173 @@ pub use crate::Block;
4198
4299use std:: ops:: Range ;
43100
44- /// The sending side of a connection between wgpu client and server
45- /// processes.
101+ /// The sending side of a connection between wgpu client and server processes.
46102///
47- /// The client and server each use an implementation of this trait to
48- /// send mesages to the other side, which we refer to as their
49- /// "counterpart". Each `Sender` sends messages to a paired
50- /// [`Receiver`] owned by the counterpart.
103+ /// When a `Sender` is dropped, all shared memory handles created with it or
104+ /// received from the counterpart, are closed. This does not affect the
105+ /// counterpart's ability to use these handles, as they are closed separately.
51106///
52107/// Note that, even after dropping a `Sender`:
53108///
54- /// - The corresponding `Receiver` may still receive messages.
109+ /// - The `Receiver` on the same side as the dropped `Sender` may still receive
110+ /// messages.
55111///
56- /// - Any shared memory mappings made via this `Sender` remain
57- /// accessible until their [`Block`]s are dropped.
112+ /// - Shared memory mappings made via this `Sender` remain accessible until
113+ /// their [`Block`]s are dropped.
58114pub trait Sender {
59- /// Allocate a block of memory shared with the counterpart.
115+ /// Allocate a segment of memory shared with the counterpart.
60116 ///
61117 /// `size` is rounded up to the next multiple of
62118 /// [`SHARED_MEMORY_ALIGNMENT`].
63119 ///
64- /// The returned handle is valid to use with both this `Sender`
65- /// and the counterpart's. It is an ordinary integer, so it can be
66- /// stored in blocks passed via [`Sender::send`],
120+ /// The returned handle is an ordinary integer, so it can be included in
121+ /// messages passed via [`Sender::send`].
122+ ///
123+ /// The handle is immediately valid to use with this `Sender`.
124+ ///
125+ /// The handle is valid to use with the counterpart's `Sender` by the time
126+ /// the counterpart receives the next message sent on this `Sender`.
67127 fn allocate_shared_memory ( & mut self , size : usize ) -> std:: io:: Result < SharedMemoryHandle > ;
68128
69129 /// Close `handle`.
70130 ///
71131 /// After this call, `handle` may no longer be passed to
72- /// [`map_shared_memory`] in this `Sender`. However, our counterpart is
73- /// still free to use `handle` with their `Sender`, until they also close
74- /// it.
132+ /// [`map_shared_memory`] or [`send`] in this `Sender`. This has no effect
133+ /// on our counterpart's ability to use `handle` with their `Sender`.
75134 ///
76135 /// Closing `handle` does not affect any [`Block`]s mapping it.
77136 ///
137+ /// When a `Sender` is dropped, all open handles created with it are
138+ /// automatically closed.
139+ ///
78140 /// [`map_shared_memory`]: Self::map_shared_memory
141+ /// [`send`]: Self::send
79142 fn close_shared_memory ( & mut self , handle : SharedMemoryHandle ) ;
80143
81144 /// Map `handle` into the caller's address space.
82145 ///
83- /// Make the shared memory represented by `handle` visible in the
84- /// caller's address space, and return a [`Block`] managing it.
146+ /// Make the shared memory represented by `handle` visible in the caller's
147+ /// address space, and return a [`Block`] managing it.
85148 ///
86149 /// When the returned [`Block`] is dropped, the mapping is removed. The
87150 /// `Block` is allowed to outlive `self` and `handle`.
88151 ///
89152 /// The address of the mapped memory is a multiple of
90153 /// [`SHARED_MEMORY_ALIGNMENT`].
91154 ///
155+ /// You may map a given shared memory multiple times; each call returns a
156+ /// distinct [`Block`]. Calling [`Block::bytes_mut`] on those blocks may or
157+ /// may not return aliasing slices.
158+ ///
92159 /// Since the returned [`Block`] refers to shared memory, you must generally
93- /// assume that it is visible to many threads simultaneously. It is up to
94- /// you to ensure proper synchronization among its users.
160+ /// assume that it is visible to many threads in many processes
161+ /// simultaneously. It is up to you to ensure proper synchronization among
162+ /// all its users.
95163 fn map_shared_memory ( & mut self , handle : SharedMemoryHandle ) -> Block ;
96164
97165 /// Send a message to the counterpart.
98166 ///
99167 /// Send a message to the counterpart, consisting of a shared memory handle
100- /// and a subrange of its contents. The meaning of these values is entirely
101- /// up to the user of the connection .
168+ /// and a subrange of its contents, to be passed to counterpart by calling
169+ /// its [`Receiver`]'s [`receive_message`] method .
102170 ///
103171 /// The message is sent immediately, without buffering. If the counterpart
104172 /// is not receiving messages, this may block.
105173 ///
106174 /// If this returns an error, the `Sender` implementation is not obliged to
107- /// handle any future calls correctly, although it must not cause undefined
108- /// behavior.
175+ /// handle any future calls correctly, although such calls must not cause
176+ /// undefined behavior.
177+ ///
178+ /// The exact interpretation of `range` is entirely up to the user of the
179+ /// transport. In typical use, `range` refers to a byte range of `handle`
180+ /// that holds serialized messages of some sort, but the transport itself
181+ /// doesn't assume that. Shared memory handles are meant to be conveyed
182+ /// by serializing them the content of messages
183+ ///
184+ /// [`receive_message`]: Receiver::receive_message
109185 fn send ( & mut self , handle : SharedMemoryHandle , range : Range < usize > ) -> std:: io:: Result < ( ) > ;
186+
187+ /// Ensure writes to a region of shared memory are visible to the counterpart.
188+ ///
189+ /// Read the contents of the subrange `range` of the memory segment referred
190+ /// to by `handle` in this process, and then write those contents to the
191+ /// corresponding shared memory range in our counterpart. The `handle` may
192+ /// have been created by either counterpart.
193+ ///
194+ /// The user must ensure that these copies are free of data races. This rule
195+ /// is meant to allow consistent behavior over a range of different
196+ /// implementations:
197+ ///
198+ /// - In implementations based on shared memory, the memory's contents are always
199+ /// equal in both counterparts, so this call may do nothing; such an
200+ /// implementation effectively performs both the read and write right now.
201+ ///
202+ /// - Implementations without shared memory can transmit the contents of the
203+ /// given range to the counterpart, and have the freedom to defer and coalesce
204+ /// flushes, as long as the contents are there by the time the message is
205+ /// delivered.
206+ ///
207+ /// An implementation of [`Sender`] may guarantee that it uses shared memory.
208+ /// Users of such implementations need not call this function. However, they
209+ /// should keep in mind that doing so limits which transport implementations they
210+ /// can use.
211+ ///
212+ /// [`send`]: Self::send
213+ fn flush_shared_memory_range (
214+ & mut self ,
215+ handle : SharedMemoryHandle ,
216+ range : Range < usize > ,
217+ ) -> std:: io:: Result < ( ) > ;
110218}
111219
112220/// The receiving side of a connection between wgpu client and server processes.
113221///
114- /// The user must provide an implementation of this class when
115- /// creating the corresponding `Sender`, which will invoke its methods
116- /// to report messages received, and errors encountered trying to
117- /// receive messages .
222+ /// The user must provide an implementation of this class when creating the
223+ /// corresponding `Sender`, which will invoke its methods to report messages
224+ /// received, and errors encountered trying to receive messages. Exactly how the
225+ /// `Receiver` is supplied depends on the `Sender` implementation .
118226///
119- /// The `Sender` implementation should specify on which thread these
120- /// callbacks are invoked.
227+ /// The `Sender` implementation should specify on which thread the
228+ /// [`receive_message`] method is invoked.
121229///
122- /// When the counterpart closes their `Sender`, this value is dropped.
230+ /// A `Receiver` is dropped:
231+ ///
232+ /// - when the counterpart closes their `Sender`,
233+ ///
234+ /// - when [`receive_message`] returns an error, or
235+ ///
236+ /// - after a call to [`receive_error`] returns.
237+ ///
238+ /// Even after a `Receiver` is dropped, shared memory handles received from the
239+ /// counterpart can still be used with this side's `Sender`.
240+ ///
241+ /// [`receive_message`]: Self::receive_message
123242pub trait Receiver {
124243 /// Called when a message has been received from the counterpart.
125244 ///
126- /// If this returns `Result::Err`, then
245+ /// If this returns `Result::Err`, then this method will not be called again
246+ /// on this `Receiver`, and the `Receiver` will be dropped.
127247 fn receive_message (
128248 & mut self ,
129249 handle : SharedMemoryHandle ,
130250 range : Range < usize > ,
131251 ) -> Result < ( ) , ReceiverError > ;
252+
253+ /// Report an error encountered trying to receive a message.
254+ ///
255+ /// After this call returns, this `Receiver` will be dropped.
256+ fn receive_error (
257+ & mut self ,
258+ error : std:: io:: Error ,
259+ ) {
260+ let _ = error;
261+ }
132262}
133263
134264/// A dynamically dispatched `Receiver`.
135265pub type DynReceiver = dyn Receiver + Send + ' static ;
136266
137- /// A handle to a block of shared memory managed by a [`Sender`].
267+ /// A handle to a segment of shared memory managed by a [`Sender`].
138268///
139269/// You can call [`Sender::allocate_shared_memory`] to create a
140270/// `SharedMemoryHandle`, use its serialization and deserialization
0 commit comments