Skip to content

[Do Not Merge] Add native Windows back-end (stable branch) - #169

Closed
antrik wants to merge 65 commits into
servo:masterfrom
antrik:windows-stable
Closed

[Do Not Merge] Add native Windows back-end (stable branch)#169
antrik wants to merge 65 commits into
servo:masterfrom
antrik:windows-stable

Conversation

@antrik

@antrik antrik commented Sep 28, 2017

Copy link
Copy Markdown
Contributor

This is just to keep track of the windows-stable branch, provided until #166 is ready for merging.

@highfive

Copy link
Copy Markdown
Collaborator

warning Warning warning

  • These commits modify unsafe code. Please review it carefully!

vvuk and others added 24 commits October 28, 2017 15:38
Since the `fork()`-related code is not compiled on Windows platforms,
the related `use` statements need to be conditional too.
…fer()

Since we assert the number of elements in the vector to be exactly one
right before this, we can just index the element directly.
Since `fork()` isn't available on Windows, we need to do the tests using
`spawn()` instead.

The `spawn()` variants work both on Windows and on Unix platforms
(including MacOS), making the `fork()` variants pretty redundant.
However, it doesn't really hurt to keep them around, just in case. (It
might help narrowing down any problems coming up, for example.)
In preparation for introducing spawn tests using more than one channel,
we need a way to extract specific channel name args by distinctive
labels.

All existing `*_spawn()` tests are adapted to include the label in the
passed command line argument; and all `*_server()` helpers are updated
with the new argument in the `get_channel_name_arg()` calls.
In preparation for adding `_spawn()` test variants for the tests in
`src/test.rs` as well, we need to move the helper there, so it can be
used in both `src/test.rs` and `src/platform/test.rs` -- just like the
`fork()` helpers.
Just like the other cross-process tests, this one needs a variant using
`spawn()` instead of `fork()`, so it can work on platforms that don't
provide `fork()`.
Change the way parameter unwrapping / conditional execution is handled,
so the active code in the `*_server()` functions is more in line with
the corresponding `*_fork()` variants. This should faciliate keeping
the fork/spawn variants in sync.
Integrate the server functionality right into the main `_spawn()` test
functions: running either the server or the client portions as
necessary.

This further aligns the structure of these tests with the corresponding
`_fork()` variants. It also avoids the need for the
`#[ignore]`/`--ignored` hack, since all the test functions now always
run -- either as normal tests (client parts); or as pseudo-tests, when
self-spawned in server mode.
This implementation uses named pipes on Windows, with auto-generated uuid
names.  It takes advantage of DuplicateHandle to clone handles to target
processes when sending handles cross-process.  Shared memory is implemented
using anonymous file mappings.  select() and friends are implemented using
IO Completion Ports.
Move the check for finding equality of cloned/received SHM objects --
known to fail on Windows -- into a separate test case, rather than using
a conditional in the main SHM test case. This makes it more explicit
that this is a know limitation, and distinct from other SHM
functionality.

Also, more explicitly document this limitation in the relevant part of
the Windows back-end implementation itself, rather than elaborating on
it in the test case.
Turn comments describing the purpose of data types, functions etc. into
proper doc comments.

Also restructure them as needed to fit the expected form for doc
comments; and in some cases, clarify/enhance the contents too.
Like in the other back-ends, remove (`mem::replae()`) the original
handle while constructing the wrappers, to make sure we do not keep
around a copy that might interfere if reused accidentally.
Use `&self` rather than `&mut self` in some private methods that do not
actually modify the main object. (Mostly because they rely on inner
mutability.)

This also removes the need for `mut` in a few places in the callers.
This method could yield uninitialised data when invoked incorrectly.
Move some more calculations inside the unsafe block, since the other
unsafe code relies upon them to be correct in order for soundness to be
ensured.
Just use a tuple in an `Option<>` instead. The named type didn't really
add anything here -- it only made the code harder to follow.

(The original introduction of this custom type resulted from a
communication failure...)
Extend the buffer's exposed length to cover its entire allocated
capacity before using it in receive calls, so we can use safe slice
operations rather than manual pointer arithmetic for determining
addresses and lengths to be passed to the system calls.

To keep the effects localised, we reset the length to the actually
filled part again after the system calls, rather than keeping it at the
full allocated size permanently. I'm not entirely sure yet whether to
consider that more or less defensive than the other option -- but at
least I'm confident that it's more robust than the original approach.
Make sure any outstanding async I/O operation is cancelled before
freeing the memory of the `ov` structure and receive buffer.

This is an important safety fix: leaving the operation pending after the
memory is freed might result in the kernel later writing to memory
locations that are no longer valid.
antrik added 25 commits October 28, 2017 15:55
While it's an unlikely case, I don't see any problem with the buffer
being used up to the last byte...
Again, while it's an unlikely case, I see no problem with fully using
the available value range.
The only really unsafe part here is turning the `MessageHeader` struct
into a series of bytes. All the actual buffer manipulations can be done
just as efficiently using safe operations.

This includes a major revamp of the way we deal with the header
construction: making it more straightforward, and reusing existing
functionality.
Abstracting this trivial functionality only makes it more obscure.
Tuple structs are obscure, and are used pretty much only for the
"newtype" pattern -- a struct with named fields is generally more
readable.
Adaptation of same mechanism in unix back-end.
That code resulted from some kind of misunderstanding...
This way we can do simple comparision, rather than needing one-arm match
statements.
…unction

The convention is to keep all `impl`s of a type together in one place.
Should be easier to read than mixing explicit and implicit return for no
obvious reason...
Since the `Ok` and `ERROR_IO_PENDING` cases are identical (as explained
in the comment), just fall through to a common default handler.
Put the success case before the error cases, rather than in between...
Further streamline error handling by turning the FFI result into a
`Result<>` value, so we can handle it in one uniform `match` expression.

While it could be argue that this adds unnecessary extra code, I believe
it makes the error handling easier to follow -- especially the common
handling of the `Ok` and `ERROR_IO_PENDING` cases.

It also allows moving the comment closer to the actual case it
explains...
The second paragraph explains the same thing as the first one, only
clearer... So we can just get rid of the first one entirely.
Apparently this ended up in the wrong place during some edit...

While at it, also improve wording.
Don't know whether there was a point to it in some earlier iteration --
but in the current code, this method never returns anything else than
`Ok(())`...
This seems to be a leftover from some earlier version of the code...
Move the raw FFI call from `OsIpcReceiver.get_message()` to a new helper
method in `MessageReader`.

This fixes a major layering violation, and makes the functionality more
reusable.

Note: this doesn't address the related raw FFI call in
`OsIpcReceiverSet.select()` -- that one is a whole other can of worms,
which will require a larger refactoring to fix...
The reader is not supposed to be used for anything else; and the caller
drops it immediately afterwards anyway. Taking it by value (and dropping
it ourself) makes this more explicit.
As far as I can tell, this method only differs from the regular receive
pattern in that it allocates the entire buffer for the message of known
size in advance, and then reads into it repeatedly until the expected
size is filled; at which point it returns the entire buffer -- rather
than trying to extract individual messages from the buffer with
`get_message()` between reads.

Since the actual async read initiation and completion is pretty much the
same -- apart from some shortcuts, which should be insignificant I
believe -- we can just implement this functionality in terms of the
regular methods, rather than keeping separate code paths that do pretty
much the same.
Just as with `write_buf()`, I see no reason to use the raw handle -- so
let's stick with the safer abstraction here as well...
The local variable isn't moving anywhere -- so no need to put it on the
heap for a stable address...
@bors-servo

Copy link
Copy Markdown
Contributor

☔ The latest upstream changes (presumably #177) made this pull request unmergeable. Please resolve the merge conflicts.

@jdm

jdm commented Mar 12, 2021

Copy link
Copy Markdown
Member

Fixed in #272

@jdm jdm closed this Mar 12, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants