Skip to content

Commit e19a805

Browse files
committed
Doc fixes. Add flushing.
1 parent 99c926e commit e19a805

2 files changed

Lines changed: 190 additions & 51 deletions

File tree

wgpu-remote/src/transport/local.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,15 @@ impl tp::Sender for LocalSender {
150150
)),
151151
}
152152
}
153+
154+
fn flush_shared_memory_range(
155+
&mut self,
156+
handle: tp::SharedMemoryHandle,
157+
range: Range<usize>,
158+
) -> std::io::Result<()> {
159+
let _ = (handle, range);
160+
Ok(())
161+
}
153162
}
154163

155164
pub struct ReceiverStarter(mpsc::Receiver<Message>);

wgpu-remote/src/transport/mod.rs

Lines changed: 181 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,93 @@
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

4299
use 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.
58114
pub 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
123242
pub 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`.
135265
pub 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

Comments
 (0)