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

Implement async queues #246

Merged
merged 9 commits into from
Oct 7, 2020
Merged

Implement async queues #246

merged 9 commits into from
Oct 7, 2020

Conversation

mzohreva
Copy link
Contributor

@mzohreva mzohreva commented Jun 3, 2020

  • Implement async queues
  • Enable async usercalls in enclave-runner
  • Limit memory usage for pending events

This is an alternative to @parthsane's PRs #194 and #217, with the following improvements:

  • Has tests for the FIFO implementation
  • Fixes the issue with calling async_queues usercall more than once
  • Improves memory usage for pending events by removing a VecDeque that could grow unbounded

@mzohreva mzohreva requested review from jethrogb, parthsane and Goirad June 3, 2020 23:06
@mzohreva
Copy link
Contributor Author

mzohreva commented Jun 3, 2020

Also this does not change the ABI.

Here is an example enclave code using async queues:

#![feature(sgx_platform)]

extern crate async_queue;
extern crate fortanix_sgx_abi;

use async_queue::{
    QueueEvent, Receiver, Sender,
    Synchronizer, SynchronizationError,
};
use fortanix_sgx_abi::{FifoDescriptor, Return, Usercall};
use std::os::fortanix_sgx::usercalls::alloc::User;
use std::os::fortanix_sgx::usercalls::raw::{
    self, UsercallNrs,
    EV_RETURNQ_NOT_EMPTY, EV_USERCALLQ_NOT_FULL,
    WAIT_INDEFINITE,
};
use std::{io, mem, thread};

#[derive(Clone, Debug)]
enum QueueSynchronizer {
    Usercall,
    Return,
}

impl Synchronizer for QueueSynchronizer {
    fn wait(&self, event: QueueEvent) -> Result<(), SynchronizationError> {
        let ev = match (self, event) {
            (Self::Usercall, QueueEvent::NotEmpty) => panic!("enclave should not recv on usercall queue"),
            (Self::Return, QueueEvent::NotFull)    => panic!("enclave should not send on return queue"),
            (Self::Usercall, QueueEvent::NotFull)  => EV_USERCALLQ_NOT_FULL,
            (Self::Return, QueueEvent::NotEmpty)   => EV_RETURNQ_NOT_EMPTY,
        };
        unsafe {
            raw::wait(ev, WAIT_INDEFINITE);
        }
        Ok(())
    }

    fn notify(&self, _event: QueueEvent) {
        // any synchronous usercall would do
        unsafe {
            raw::insecure_time();
        }
    }
}

struct AsyncQueues {
    usercall_queue: FifoDescriptor<Usercall>,
    return_queue: FifoDescriptor<Return>,
}

impl AsyncQueues {
    fn new() -> io::Result<Self> {
        let usercall_queue = User::<raw::FifoDescriptor<raw::Usercall>>::uninitialized().into_raw();
        let return_queue = User::<raw::FifoDescriptor<raw::Return>>::uninitialized().into_raw();

        let r = unsafe { raw::async_queues(usercall_queue, return_queue) };
        if r != 0 {
            return Err(io::Error::from_raw_os_error(r));
        }

        let usercall_queue = unsafe {
            User::<raw::FifoDescriptor<raw::Usercall>>::from_raw(usercall_queue)
        }.to_enclave();

        let return_queue = unsafe {
            User::<raw::FifoDescriptor<raw::Return>>::from_raw(return_queue)
        }.to_enclave();

        // For some reason rust considers std::os::fortanix_sgx::usercalls::raw::FifoDescriptor
        // and fortanix_sgx_abi::FifoDescriptor different types, hence the transmutes here.
        let usercall_queue: FifoDescriptor<Usercall> = unsafe { mem::transmute(usercall_queue) };
        let return_queue: FifoDescriptor<Return> = unsafe { mem::transmute(return_queue) };

        Ok(Self { usercall_queue, return_queue })
    }

    fn sender(&self) -> Sender<Usercall, QueueSynchronizer> {
        Sender::new(self.usercall_queue.clone(), QueueSynchronizer::Usercall)
    }

    // Panics if there are existing receivers
    fn receiver(&self) -> Receiver<Return, QueueSynchronizer> {
        Receiver::new(self.return_queue.clone(), QueueSynchronizer::Return)
    }
}

fn main() {
    let queues = AsyncQueues::new().expect("failed to setup async queues");
    let usercall_queue_tx = queues.sender();
    let return_queue_rx = queues.receiver();

    const N: u64 = 100;
    let h = thread::spawn(move || {
        for i in 0..N {
            usercall_queue_tx.send(&Usercall {
                id: 1 + i,
                args: (UsercallNrs::insecure_time as _, 0, 0, 0, 0),
            }).unwrap();
        }
    });

    for _ in 0..N {
        let t = return_queue_rx.recv().unwrap();
        println!("[{:3}] {:?}", t.id, t.value);
    }
    h.join().unwrap();
}

@Goirad
Copy link
Member

Goirad commented Jun 4, 2020

The example enclave code seems awfully verbose. Is this something that most (all?) enclaves will have to do?

@mzohreva
Copy link
Contributor Author

mzohreva commented Jun 4, 2020

The example enclave code seems awfully verbose. Is this something that most (all?) enclaves will have to do?

No, this will be abstracted away once we port MIO to SGX and users will use tokio (which relies on MIO for low level operations).

async-queue/Cargo.toml Outdated Show resolved Hide resolved
Copy link
Member

@jethrogb jethrogb left a comment

Choose a reason for hiding this comment

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

Note to self: continue reviewing at interface_sync::Receiver

async-queue/Cargo.toml Outdated Show resolved Hide resolved
async-queue/src/lib.rs Outdated Show resolved Hide resolved
async-queue/src/lib.rs Outdated Show resolved Hide resolved
async-queue/src/fifo.rs Outdated Show resolved Hide resolved
async-queue/src/fifo.rs Outdated Show resolved Hide resolved
async-queue/src/fifo.rs Outdated Show resolved Hide resolved
async-queue/src/fifo.rs Outdated Show resolved Hide resolved
async-queue/src/fifo.rs Outdated Show resolved Hide resolved
async-queue/src/fifo.rs Outdated Show resolved Hide resolved
async-queue/src/fifo.rs Outdated Show resolved Hide resolved
async-queue/src/fifo.rs Outdated Show resolved Hide resolved
async-queue/src/lib.rs Outdated Show resolved Hide resolved
async-queue/src/fifo.rs Outdated Show resolved Hide resolved
async-queue/src/fifo.rs Outdated Show resolved Hide resolved
async-queue/src/fifo.rs Outdated Show resolved Hide resolved
async-queue/src/fifo.rs Outdated Show resolved Hide resolved
async-queue/src/fifo.rs Outdated Show resolved Hide resolved
async-queue/src/fifo.rs Outdated Show resolved Hide resolved
async-queue/src/lib.rs Outdated Show resolved Hide resolved
async-queue/src/lib.rs Outdated Show resolved Hide resolved
enclave-runner/src/lib.rs Outdated Show resolved Hide resolved
enclave-runner/src/usercalls/interface.rs Outdated Show resolved Hide resolved
@mzohreva mzohreva force-pushed the mz/impl_fifo branch 3 times, most recently from d14bf91 to e1e8150 Compare September 18, 2020 19:50
@mzohreva mzohreva mentioned this pull request Sep 18, 2020
- Optimize offsets calculations
- Replace WithAtomicId with Identified/WithId
- Better lifetime management
- Other minor nits
Copy link
Member

@jethrogb jethrogb left a comment

Choose a reason for hiding this comment

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

github now requires me to put some text here

ipc-queue/Cargo.toml Show resolved Hide resolved
ipc-queue/src/fifo.rs Outdated Show resolved Hide resolved
ipc-queue/src/fifo.rs Outdated Show resolved Hide resolved
ipc-queue/src/sealed.rs Outdated Show resolved Hide resolved
ipc-queue/src/fifo.rs Outdated Show resolved Hide resolved
ipc-queue/src/interface_sync.rs Show resolved Hide resolved
- DescriptorGuard
- Fix description of recv operation in ABI
- Closed variant in TrySendError/TryRecvError for future
- Check pointers in FifoDescriptor in SGX using User<[T]>
- UnsafeCell<[T]> -> [UnsafeCell<T>]
- Change Usercall/Return types in ABI to not include the id field
- Change FIFO interface to send/recv Identified<T> values
This was referenced Sep 25, 2020
Copy link
Member

@jethrogb jethrogb left a comment

Choose a reason for hiding this comment

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

Note to self: continue review at struct QueueSynchronizer

fortanix-sgx-abi/src/lib.rs Outdated Show resolved Hide resolved
ipc-queue/.gitignore Outdated Show resolved Hide resolved
enclave-runner/src/usercalls/mod.rs Show resolved Hide resolved
while let Ok(usercall) = usercall_queue_rx.recv().await {
let _ = io_queue_send.send(UsercallSendData::Async(usercall));
}
});
Copy link
Member

Choose a reason for hiding this comment

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

Instead of the forwarding, can you do something like recv_queue.select(usercall_queue_rx.recv()) in the loop below?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's possible, but it would complicate the already too complicated loop below. What's your main concern with having it as a separate task?

Copy link
Member

Choose a reason for hiding this comment

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

Primarily because the current flow is obfuscating what the input is to the while loop below and a lesser concern might be performance.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The input to that while loop comes from fn do_work and this task, I don't see that being obfuscated, specially because UsercallSendData has two variants. I don't think performance will be impacted much by having an extra task considering that doing select will also introduce an extra future.

We could also implement some form of back pressure on the usercall queue more easily in this form, but that might not be needed in practice.

fortanix-sgx-abi/src/lib.rs Show resolved Hide resolved
enclave-runner/src/usercalls/mod.rs Outdated Show resolved Hide resolved
enclave-runner/src/usercalls/mod.rs Outdated Show resolved Hide resolved
enclave-runner/src/usercalls/mod.rs Outdated Show resolved Hide resolved
Comment on lines 511 to 513
struct PendingEvents {
counts: [u32; Self::MAX_EVENT],
}
Copy link
Member

Choose a reason for hiding this comment

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

This reorders the delivery of events compared to the current situation (events with less bits set will trigger first). I don't think that matters too much, but is there any specific reason for changing this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The impetus for this type is that some threads may never be interested in a particular event, and therefore we could be using unbounded amount of memory storing those events. For example most enclave threads are not interested in EV_RETURNQ_NOT_EMPTY since the return queue is probably owned by just one thread, but the enclave runner needs to send that event to all threads since it does not know which thread owns that queue.

Copy link
Member

Choose a reason for hiding this comment

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

The impetus for this type is that some threads may never be interested in a particular event, and therefore we could be using unbounded amount of memory storing those events.

Makes sense.

the enclave runner needs to send [EV_RETURNQ_NOT_EMPTY] to all threads since it does not know which thread owns that queue

Well it only needs to be sent to all threads if no thread is currently waiting on that specific event:

Userspace will wake any or all threads waiting on the appropriate event when it is triggered.

(emphasis mine)

enclave-runner/src/usercalls/mod.rs Outdated Show resolved Hide resolved
- Compile-time checks for `PendingEvents`
- Add `ReturnSource`
- Remove .gitignore in ipc-queue
- Use 'static refs in `Fifo` instead of `NonNull`
- Remove accessor functions in `Fifo`
- Rename some types in ipc-queue
- Other nits
enclave-runner/src/usercalls/mod.rs Outdated Show resolved Hide resolved
enclave-runner/src/usercalls/mod.rs Outdated Show resolved Hide resolved
enclave-runner/src/usercalls/mod.rs Outdated Show resolved Hide resolved
enclave-runner/src/usercalls/mod.rs Outdated Show resolved Hide resolved
// synchronous usercalls made by the enclave for the purpose of
// synchronizing access to usercall and return queues.
// The size of this channel should not matter since recv() can
// return RecvError::Lagged.
Copy link
Member

Choose a reason for hiding this comment

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

Should it just be size 0 then?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It can't be 0 (will panic) but no reason to just set it to 1 I guess.

enclave-runner/src/usercalls/mod.rs Outdated Show resolved Hide resolved
enclave-runner/src/usercalls/mod.rs Outdated Show resolved Hide resolved
Copy link
Member

@jethrogb jethrogb left a comment

Choose a reason for hiding this comment

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

Approving this but not yet merging as I want to make sure we publish our existing updates first.

@jethrogb
Copy link
Member

jethrogb commented Oct 7, 2020

bors r+

@bors
Copy link
Contributor

bors bot commented Oct 7, 2020

Build succeeded:

@bors bors bot merged commit 3d53ec3 into master Oct 7, 2020
@mzohreva mzohreva deleted the mz/impl_fifo branch October 7, 2020 15:55
bors bot added a commit that referenced this pull request Oct 26, 2020
295: Update fortanix-sgx-abi version r=jethrogb a=mzohreva

This was missed in #246 

Co-authored-by: Mohsen Zohrevandi <mohsen.zohrevandi@fortanix.com>
@jethrogb jethrogb mentioned this pull request Nov 5, 2020
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants