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. | # It is not intended for manual editing. | ||||||
| version = 3 | version = 3 | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "autocfg" | ||||||
|  | version = "1.1.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "bit_field" | name = "bit_field" | ||||||
| version = "0.10.1" | version = "0.10.1" | ||||||
| @ -20,10 +26,79 @@ version = "1.3.2" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" | 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]] | [[package]] | ||||||
| name = "julios" | name = "julios" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  |  "conquer-once", | ||||||
|  |  "crossbeam-queue", | ||||||
|  |  "futures-util", | ||||||
|  "lazy_static", |  "lazy_static", | ||||||
|  "linked_list_allocator", |  "linked_list_allocator", | ||||||
|  "multiboot2", |  "multiboot2", | ||||||
| @ -61,6 +136,12 @@ dependencies = [ | |||||||
|  "scopeguard", |  "scopeguard", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "maybe-uninit" | ||||||
|  | version = "2.0.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "multiboot2" | name = "multiboot2" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| @ -85,6 +166,18 @@ dependencies = [ | |||||||
|  "x86_64", |  "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]] | [[package]] | ||||||
| name = "rustversion" | name = "rustversion" | ||||||
| version = "1.0.6" | version = "1.0.6" | ||||||
|  | |||||||
							
								
								
									
										14
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										14
									
								
								Cargo.toml
									
									
									
									
									
								
							| @ -20,3 +20,17 @@ linked_list_allocator = "0.9.0" | |||||||
| [dependencies.lazy_static] | [dependencies.lazy_static] | ||||||
| version = "1.0" | version = "1.0" | ||||||
| features = ["spin_no_std"] | 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 super::{PICS, InterruptIndex}; | ||||||
|  | 
 | ||||||
| use x86_64::instructions::port::Port; | use x86_64::instructions::port::Port; | ||||||
| use spin::{self, Mutex}; |  | ||||||
| use crate::{print}; |  | ||||||
| use x86_64::structures::idt::{InterruptStackFrame}; | use x86_64::structures::idt::{InterruptStackFrame}; | ||||||
| 
 | 
 | ||||||
| pub const PS2_CONTROLLER_PORT: u16 = 0x60; | pub const PS2_CONTROLLER_PORT: u16 = 0x60; | ||||||
| 
 | 
 | ||||||
| pub extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStackFrame) { | 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 mut port = Port::new(PS2_CONTROLLER_PORT); | ||||||
|     let scancode: u8 = unsafe { port.read() }; |     let scancode: u8 = unsafe { port.read() }; | ||||||
| 
 | 
 | ||||||
|     if let Ok(Some(key_event)) = keyboard.add_byte(scancode) { |     crate::task::keyboard::add_scancode(scancode); | ||||||
|         if let Some(key) = keyboard.process_keyevent(key_event) { | 
 | ||||||
|             match key { |  | ||||||
|                 DecodedKey::Unicode(character) => print!("{}", character), |  | ||||||
|                 DecodedKey::RawKey(key) => print!("{:?}", key), |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     unsafe { |     unsafe { | ||||||
|         PICS.lock() |         PICS.lock() | ||||||
|             .notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8()); |             .notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8()); | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ mod interrupts; | |||||||
| mod memory; | mod memory; | ||||||
| mod serial; | mod serial; | ||||||
| mod vga; | mod vga; | ||||||
|  | mod task; | ||||||
| 
 | 
 | ||||||
| //#[macro_use]
 | //#[macro_use]
 | ||||||
| extern crate alloc; | extern crate alloc; | ||||||
| @ -16,6 +17,7 @@ extern crate multiboot2; | |||||||
| use core::panic::PanicInfo; | use core::panic::PanicInfo; | ||||||
| use multiboot2::BootInformation; | use multiboot2::BootInformation; | ||||||
| use vga::{Color, ColorCode}; | use vga::{Color, ColorCode}; | ||||||
|  | use task::{executor::Executor, Task, keyboard}; | ||||||
| 
 | 
 | ||||||
| #[alloc_error_handler] | #[alloc_error_handler] | ||||||
| fn alloc_error_handler(layout: alloc::alloc::Layout) -> ! { | 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***"); |     println!("***JuliOS V0.1.0***"); | ||||||
|     serial_println!("Hello serial"); |     serial_println!("Hello serial"); | ||||||
| 
 | 
 | ||||||
|     use alloc::boxed::Box; |     let mut executor = Executor::new(); | ||||||
|     let x = Box::new(41); |     executor.spawn(Task::new(keyboard::print_keypresses())); | ||||||
| 
 |     executor.run(); | ||||||
|     panic!("Kernel end of flow"); |  | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										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