Add async tasks support
Signed-off-by: Julien CLEMENT <julien.clement@epita.fr>
This commit is contained in:
		
							parent
							
								
									204b464f59
								
							
						
					
					
						commit
						9ae31eb2f6
					
				
							
								
								
									
										93
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										93
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@ -2,6 +2,12 @@
 | 
			
		||||
# It is not intended for manual editing.
 | 
			
		||||
version = 3
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "autocfg"
 | 
			
		||||
version = "1.1.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "bit_field"
 | 
			
		||||
version = "0.10.1"
 | 
			
		||||
@ -20,10 +26,79 @@ version = "1.3.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "cfg-if"
 | 
			
		||||
version = "0.1.10"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "conquer-once"
 | 
			
		||||
version = "0.2.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "96eb12fb69466716fbae9009d389e6a30830ae8975e170eff2d2cff579f9efa3"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "conquer-util",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "conquer-util"
 | 
			
		||||
version = "0.2.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "654fb2472cc369d311c547103a1fa81d467bef370ae7a0680f65939895b1182a"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crossbeam-queue"
 | 
			
		||||
version = "0.2.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "crossbeam-utils",
 | 
			
		||||
 "maybe-uninit",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crossbeam-utils"
 | 
			
		||||
version = "0.7.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "autocfg",
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "futures-core"
 | 
			
		||||
version = "0.3.25"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "futures-task"
 | 
			
		||||
version = "0.3.25"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "futures-util"
 | 
			
		||||
version = "0.3.25"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "futures-core",
 | 
			
		||||
 "futures-task",
 | 
			
		||||
 "pin-project-lite",
 | 
			
		||||
 "pin-utils",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "julios"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "conquer-once",
 | 
			
		||||
 "crossbeam-queue",
 | 
			
		||||
 "futures-util",
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "linked_list_allocator",
 | 
			
		||||
 "multiboot2",
 | 
			
		||||
@ -61,6 +136,12 @@ dependencies = [
 | 
			
		||||
 "scopeguard",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "maybe-uninit"
 | 
			
		||||
version = "2.0.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "multiboot2"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
@ -85,6 +166,18 @@ dependencies = [
 | 
			
		||||
 "x86_64",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "pin-project-lite"
 | 
			
		||||
version = "0.2.9"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "pin-utils"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "rustversion"
 | 
			
		||||
version = "1.0.6"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										14
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										14
									
								
								Cargo.toml
									
									
									
									
									
								
							@ -20,3 +20,17 @@ linked_list_allocator = "0.9.0"
 | 
			
		||||
[dependencies.lazy_static]
 | 
			
		||||
version = "1.0"
 | 
			
		||||
features = ["spin_no_std"]
 | 
			
		||||
 | 
			
		||||
[dependencies.crossbeam-queue]
 | 
			
		||||
version = "0.2.1"
 | 
			
		||||
default-features = false
 | 
			
		||||
features = ["alloc"]
 | 
			
		||||
 | 
			
		||||
[dependencies.conquer-once]
 | 
			
		||||
version = "0.2.0"
 | 
			
		||||
default-features = false
 | 
			
		||||
 | 
			
		||||
[dependencies.futures-util]
 | 
			
		||||
version = "0.3.4"
 | 
			
		||||
default-features = false
 | 
			
		||||
features = ["alloc"]
 | 
			
		||||
 | 
			
		||||
@ -1,32 +1,16 @@
 | 
			
		||||
use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1};
 | 
			
		||||
use lazy_static::lazy_static;
 | 
			
		||||
use super::{PICS, InterruptIndex};
 | 
			
		||||
 | 
			
		||||
use x86_64::instructions::port::Port;
 | 
			
		||||
use spin::{self, Mutex};
 | 
			
		||||
use crate::{print};
 | 
			
		||||
use x86_64::structures::idt::{InterruptStackFrame};
 | 
			
		||||
 | 
			
		||||
pub const PS2_CONTROLLER_PORT: u16 = 0x60;
 | 
			
		||||
 | 
			
		||||
pub extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStackFrame) {
 | 
			
		||||
    lazy_static! {
 | 
			
		||||
        static ref KEYBOARD: Mutex<Keyboard<layouts::Us104Key, ScancodeSet1>> = Mutex::new(
 | 
			
		||||
            Keyboard::new(layouts::Us104Key, ScancodeSet1, HandleControl::Ignore)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let mut keyboard = KEYBOARD.lock();
 | 
			
		||||
    let mut port = Port::new(PS2_CONTROLLER_PORT);
 | 
			
		||||
    let scancode: u8 = unsafe { port.read() };
 | 
			
		||||
 | 
			
		||||
    if let Ok(Some(key_event)) = keyboard.add_byte(scancode) {
 | 
			
		||||
        if let Some(key) = keyboard.process_keyevent(key_event) {
 | 
			
		||||
            match key {
 | 
			
		||||
                DecodedKey::Unicode(character) => print!("{}", character),
 | 
			
		||||
                DecodedKey::RawKey(key) => print!("{:?}", key),
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    crate::task::keyboard::add_scancode(scancode);
 | 
			
		||||
 | 
			
		||||
    unsafe {
 | 
			
		||||
        PICS.lock()
 | 
			
		||||
            .notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8());
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ mod interrupts;
 | 
			
		||||
mod memory;
 | 
			
		||||
mod serial;
 | 
			
		||||
mod vga;
 | 
			
		||||
mod task;
 | 
			
		||||
 | 
			
		||||
//#[macro_use]
 | 
			
		||||
extern crate alloc;
 | 
			
		||||
@ -16,6 +17,7 @@ extern crate multiboot2;
 | 
			
		||||
use core::panic::PanicInfo;
 | 
			
		||||
use multiboot2::BootInformation;
 | 
			
		||||
use vga::{Color, ColorCode};
 | 
			
		||||
use task::{executor::Executor, Task, keyboard};
 | 
			
		||||
 | 
			
		||||
#[alloc_error_handler]
 | 
			
		||||
fn alloc_error_handler(layout: alloc::alloc::Layout) -> ! {
 | 
			
		||||
@ -53,8 +55,7 @@ pub extern "C" fn julios_main(multiboot_info_addr: usize) -> ! {
 | 
			
		||||
    println!("***JuliOS V0.1.0***");
 | 
			
		||||
    serial_println!("Hello serial");
 | 
			
		||||
 | 
			
		||||
    use alloc::boxed::Box;
 | 
			
		||||
    let x = Box::new(41);
 | 
			
		||||
 | 
			
		||||
    panic!("Kernel end of flow");
 | 
			
		||||
    let mut executor = Executor::new();
 | 
			
		||||
    executor.spawn(Task::new(keyboard::print_keypresses()));
 | 
			
		||||
    executor.run();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										101
									
								
								src/task/executor.rs
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										101
									
								
								src/task/executor.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,101 @@
 | 
			
		||||
use super::{Task, TaskId};
 | 
			
		||||
use alloc::{collections::BTreeMap, sync::Arc, task::Wake};
 | 
			
		||||
use core::task::{Context, Poll, Waker};
 | 
			
		||||
use crossbeam_queue::ArrayQueue;
 | 
			
		||||
 | 
			
		||||
pub struct Executor {
 | 
			
		||||
    tasks: BTreeMap<TaskId, Task>,
 | 
			
		||||
    task_queue: Arc<ArrayQueue<TaskId>>,
 | 
			
		||||
    waker_cache: BTreeMap<TaskId, Waker>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Executor {
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Executor {
 | 
			
		||||
            tasks: BTreeMap::new(),
 | 
			
		||||
            task_queue: Arc::new(ArrayQueue::new(100)),
 | 
			
		||||
            waker_cache: BTreeMap::new()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn run(&mut self) -> ! {
 | 
			
		||||
        loop {
 | 
			
		||||
            self.run_ready_tasks();
 | 
			
		||||
            self.sleep_if_idle();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn sleep_if_idle(&self) {
 | 
			
		||||
        use x86_64::instructions::interrupts::{self, enable_and_hlt};
 | 
			
		||||
        interrupts::disable();
 | 
			
		||||
 | 
			
		||||
        if self.task_queue.is_empty() {
 | 
			
		||||
            enable_and_hlt();
 | 
			
		||||
        } else {
 | 
			
		||||
            interrupts::enable();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn spawn(&mut self, task: Task) {
 | 
			
		||||
        let task_id = task.id;
 | 
			
		||||
        if self.tasks.insert(task.id, task).is_some() {
 | 
			
		||||
            panic!("Duplicate task ID");
 | 
			
		||||
        }
 | 
			
		||||
        self.task_queue.push(task_id).expect("Task queue full");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn run_ready_tasks(&mut self) {
 | 
			
		||||
        let Self {
 | 
			
		||||
            tasks,
 | 
			
		||||
            task_queue,
 | 
			
		||||
            waker_cache
 | 
			
		||||
        } = self; // Executor destructuring
 | 
			
		||||
 | 
			
		||||
        while let Ok(task_id) = task_queue.pop() {
 | 
			
		||||
            let task = match tasks.get_mut(&task_id) {
 | 
			
		||||
                Some(task) => task,
 | 
			
		||||
                None => continue, // Task does not exist anymore
 | 
			
		||||
            };
 | 
			
		||||
            let waker = waker_cache
 | 
			
		||||
                .entry(task_id)
 | 
			
		||||
                .or_insert_with(|| TaskWaker::new(task_id, task_queue.clone()));
 | 
			
		||||
            let mut context = Context::from_waker(waker);
 | 
			
		||||
            match task.poll(&mut context) {
 | 
			
		||||
                Poll::Ready(()) => {
 | 
			
		||||
                    // task is done
 | 
			
		||||
                    tasks.remove(&task_id);
 | 
			
		||||
                    waker_cache.remove(&task_id);
 | 
			
		||||
                }
 | 
			
		||||
                Poll::Pending => {}
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct TaskWaker {
 | 
			
		||||
    task_id: TaskId,
 | 
			
		||||
    task_queue: Arc<ArrayQueue<TaskId>>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl TaskWaker {
 | 
			
		||||
    fn new(task_id: TaskId, task_queue: Arc<ArrayQueue<TaskId>>) -> Waker {
 | 
			
		||||
        Waker::from(Arc::new(TaskWaker {
 | 
			
		||||
            task_id,
 | 
			
		||||
            task_queue
 | 
			
		||||
        }))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn wake_task(&self) {
 | 
			
		||||
        self.task_queue.push(self.task_id).expect("Task queue full");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Wake for TaskWaker {
 | 
			
		||||
    fn wake(self: Arc<Self>) {
 | 
			
		||||
        self.wake_task();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn wake_by_ref(self: &Arc<Self>) {
 | 
			
		||||
        self.wake_task();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										80
									
								
								src/task/keyboard.rs
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										80
									
								
								src/task/keyboard.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,80 @@
 | 
			
		||||
use crate::{println, print};
 | 
			
		||||
 | 
			
		||||
use conquer_once::spin::OnceCell;
 | 
			
		||||
use core::{pin::Pin, task::{Context, Poll}};
 | 
			
		||||
use crossbeam_queue::ArrayQueue;
 | 
			
		||||
use futures_util::task::AtomicWaker;
 | 
			
		||||
use futures_util::stream::{Stream, StreamExt};
 | 
			
		||||
use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1};
 | 
			
		||||
 | 
			
		||||
static SCANCODE_QUEUE: OnceCell<ArrayQueue<u8>> = OnceCell::uninit();
 | 
			
		||||
 | 
			
		||||
static WAKER: AtomicWaker = AtomicWaker::new();
 | 
			
		||||
 | 
			
		||||
pub struct ScancodeStream {
 | 
			
		||||
    _private: (), // Makes ScancodeStream constructable only
 | 
			
		||||
                  // inside the module with ScancodeStream::new()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ScancodeStream {
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        SCANCODE_QUEUE.try_init_once(|| ArrayQueue::new(100))
 | 
			
		||||
            .expect("ScancodeStream::new should only be called once");
 | 
			
		||||
        ScancodeStream { _private: () }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Stream for ScancodeStream {
 | 
			
		||||
    type Item = u8;
 | 
			
		||||
 | 
			
		||||
    fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<u8>> {
 | 
			
		||||
        let queue = SCANCODE_QUEUE
 | 
			
		||||
            .try_get()
 | 
			
		||||
            .expect("Keyboard scancode queue not initialized");
 | 
			
		||||
 | 
			
		||||
        if let Ok(scancode) = queue.pop() {
 | 
			
		||||
            return Poll::Ready(Some(scancode));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        WAKER.register(&cx.waker());
 | 
			
		||||
 | 
			
		||||
        match queue.pop() {
 | 
			
		||||
            Ok(scancode) => {
 | 
			
		||||
                WAKER.take();
 | 
			
		||||
                Poll::Ready(Some(scancode))
 | 
			
		||||
            },
 | 
			
		||||
            Err(crossbeam_queue::PopError) => Poll::Pending
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(crate) fn add_scancode(scancode: u8) {
 | 
			
		||||
    if let Ok(queue) = SCANCODE_QUEUE.try_get() {
 | 
			
		||||
        if let Err(_) = queue.push(scancode) {
 | 
			
		||||
            println!("Keyboard scancode queue full, dropping input {}", scancode);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            WAKER.wake();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        println!("Keyboard scancode queue uninitialized");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn print_keypresses() {
 | 
			
		||||
    let mut scancodes = ScancodeStream::new();
 | 
			
		||||
    let mut keyboard: Keyboard<layouts::Us104Key, ScancodeSet1> = 
 | 
			
		||||
        Keyboard::new(layouts::Us104Key, ScancodeSet1, HandleControl::Ignore);
 | 
			
		||||
 | 
			
		||||
    while let Some(scancode) = scancodes.next().await {
 | 
			
		||||
        if let Ok(Some(key_event)) = keyboard.add_byte(scancode) {
 | 
			
		||||
            if let Some(key) = keyboard.process_keyevent(key_event) {
 | 
			
		||||
                match key {
 | 
			
		||||
                    DecodedKey::Unicode(character) => print!("{}", character),
 | 
			
		||||
                    DecodedKey::RawKey(key) => print!("{:?}", key),
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								src/task/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										36
									
								
								src/task/mod.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
			
		||||
use core::future::Future;
 | 
			
		||||
use core::pin::Pin;
 | 
			
		||||
use core::sync::atomic::{AtomicU64, Ordering};
 | 
			
		||||
use core::task::{Context, Poll};
 | 
			
		||||
use alloc::boxed::Box;
 | 
			
		||||
 | 
			
		||||
pub mod executor;
 | 
			
		||||
pub mod keyboard;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
 | 
			
		||||
struct TaskId(u64);
 | 
			
		||||
 | 
			
		||||
impl TaskId {
 | 
			
		||||
    fn new() -> Self {
 | 
			
		||||
        static NEXT_ID: AtomicU64 = AtomicU64::new(0);
 | 
			
		||||
        TaskId(NEXT_ID.fetch_add(1, Ordering::Relaxed))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct Task {
 | 
			
		||||
    id: TaskId,
 | 
			
		||||
    future: Pin<Box<dyn Future<Output = ()>>>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Task {
 | 
			
		||||
    pub fn new(future: impl Future<Output = ()> + 'static) -> Task {
 | 
			
		||||
        Task {
 | 
			
		||||
            id: TaskId::new(),
 | 
			
		||||
            future: Box::pin(future)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn poll(&mut self, context: &mut Context) -> Poll<()> {
 | 
			
		||||
        self.future.as_mut().poll(context)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user