Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new method as_any() to GuestAddressSpace #192

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Changelog
## [Unreleased]

### Added

- [[192]](https://github.com/rust-vmm/vm-memory/pull/192): Add new method
as_any() to GuestAddressSpace

## [v0.8.0]

### Fixed
Expand Down
26 changes: 24 additions & 2 deletions src/atomic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
extern crate arc_swap;

use arc_swap::{ArcSwap, Guard};
use std::any::Any;
use std::ops::Deref;
use std::sync::{Arc, LockResult, Mutex, MutexGuard, PoisonError};

Expand Down Expand Up @@ -75,13 +76,17 @@ impl<M: GuestMemory> GuestMemoryAtomic<M> {
}
}

impl<M: GuestMemory> GuestAddressSpace for GuestMemoryAtomic<M> {
type T = GuestMemoryLoadGuard<M>;
impl<M: GuestMemory + 'static> GuestAddressSpace for GuestMemoryAtomic<M> {
type M = M;
type T = GuestMemoryLoadGuard<M>;

fn memory(&self) -> Self::T {
GuestMemoryLoadGuard { guard: self.load() }
}

fn as_any(&self) -> &dyn Any {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, super sorry for taking so long to look at this. The main question in my mind why does this method need to be part of the trait, and not composed or super-imposed somehow externally? Asking because the functionality is quite orthogonal to this trait (and does come up with some extra constraints; not sure how important, but they are there). Is this going to become a pattern that will repeat for other traits we expose in the future?

There are a bunch of ways to get similar functionality without embedding it in the trait proper when downcasting is needed locally in a project (one option/example is illustrated in this test from the event-manager crate). Another option is to define a separate trait just for the Any related part, and include it in a trait bound where required. How do these fit over the use cases you have in mind so far?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All existing code takes an <AS: GuestMemoryAddress>, so we have two ways to extend existing code:

  1. Change all instances of <AS: GuestMemoryAddress> to <AS: GuestMemoryAddress + AsAny>
  2. Enhance GuestMemoryAddress to provide as_any().
    So it's a tradeoff to reduce code changes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, sorry spent a bit more time experimenting with a few alternative solutions, but ultimately it turns out the simplest non-invasive (w.r.t. to the upstream interfaces) are still the aforementioned ones. So to describe the alternative in a bit more detail, I presume the mention of existing code having <AS: GuestMemoryAddress> refers to various local implementations. A more straightforward approach to altering each and every occurence of <AS: GuestAddressSpace> to <AS: GuestAddressSpace + AsAny> is to define a local trait also named GuestAddressSpace (to keep even simple renamings to a minimum) as GuestAddressSpace = upstream::GuestAddressSpace + AsAny. Then, by use-ing the local variant instead of the upstream one (which still requires some changes, but at a hopefully significantly reduced scale), all the existing method/impl definitions should work in a transparent manner (there are some caveats if trait objects are required, but from what we discussed so far that doesn't seem to be a requirement). Is that something that's feasible to do as opposed to the kind of alteration you mentioned?

Adding the method to the upstream trait is also something that can be done, but it really feels like it doesn't actually belong there and that it solves a completely orthogonal problem. Also, if we were to go that path, I'm worried that it might become tempting to do this more easily to other traits as well (particularly since this generates additional constraints and alters the semantics at least to some extent).

Ultimately, if the cost is really high for the alternative we can implement the changes for GuestAddressSpace, but I'm happy to help with a quick PoC to get the alternative rolling and/or showcase it's actually a working solution (if you can point me at a branch somewhere to add the changes on top of). I really thing it's better overall to avoid conflating the functionality upstream.

self
}
}

/// A guard that provides temporary access to a `GuestMemoryAtomic`. This
Expand Down Expand Up @@ -148,6 +153,23 @@ mod tests {
type GuestRegionMmap = crate::GuestRegionMmap<()>;
type GuestMemoryMmapAtomic = GuestMemoryAtomic<GuestMemoryMmap>;

#[test]
fn test_as_any() {
let region_size = 0x400;
let regions = vec![
(GuestAddress(0x0), region_size),
(GuestAddress(0x1000), region_size),
];
let gmm = GuestMemoryMmap::from_ranges(&regions).unwrap();
let gm = GuestMemoryMmapAtomic::new(gmm);

assert!(gm
.as_any()
.downcast_ref::<GuestMemoryMmapAtomic>()
.is_some());
assert!(gm.as_any().downcast_ref::<String>().is_none());
}

#[test]
fn test_atomic_memory() {
let region_size = 0x400;
Expand Down
32 changes: 29 additions & 3 deletions src/guest_memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
//! via pointers, references, or slices returned by methods of `GuestMemory`,`GuestMemoryRegion`,
//! `VolatileSlice`, `VolatileRef`, or `VolatileArrayRef`.

use std::any::Any;
use std::convert::From;
use std::fmt::{self, Display};
use std::fs::File;
Expand Down Expand Up @@ -400,33 +401,48 @@ pub trait GuestAddressSpace {
/// to access memory through this address space. The object provides
/// a consistent snapshot of the memory map.
fn memory(&self) -> Self::T;

/// Cast `self` to a dynamic trait object of `std::any::Any`.
fn as_any(&self) -> &dyn Any;
}

impl<M: GuestMemory> GuestAddressSpace for &M {
impl<M: GuestMemory + 'static> GuestAddressSpace for &M {
type M = M;
type T = Self;

fn memory(&self) -> Self {
self
}

fn as_any(&self) -> &dyn Any {
*self
}
}

impl<M: GuestMemory> GuestAddressSpace for Rc<M> {
impl<M: GuestMemory + 'static> GuestAddressSpace for Rc<M> {
type M = M;
type T = Self;

fn memory(&self) -> Self {
self.clone()
}

fn as_any(&self) -> &dyn Any {
self
}
}

impl<M: GuestMemory> GuestAddressSpace for Arc<M> {
impl<M: GuestMemory + 'static> GuestAddressSpace for Arc<M> {
type M = M;
type T = Self;

fn memory(&self) -> Self {
self.clone()
}

fn as_any(&self) -> &dyn Any {
self
}
}

/// Lifetime generic associated iterators. The actual iterator type is defined through associated
Expand Down Expand Up @@ -1256,4 +1272,14 @@ mod tests {
let r = mem.find_region(addr).unwrap();
assert_eq!(r.is_hugetlbfs(), None);
}

#[cfg(feature = "backend-mmap")]
#[test]
fn test_as_any() {
let addr = GuestAddress(0x1000);
let mem = &GuestMemoryMmap::from_ranges(&[(addr, 0x1000)]).unwrap();

assert!(mem.as_any().downcast_ref::<GuestMemoryMmap>().is_some());
assert!(mem.as_any().downcast_ref::<String>().is_none());
}
}