diff --git a/executor/async-await.html b/executor/async-await.html index 5433600..d4f36f2 100644 --- a/executor/async-await.html +++ b/executor/async-await.html @@ -162,7 +162,7 @@

Async/Await

async fn notify_user(user_id: u32) { let user = async_fetch_user(user_id).await; if user.group == 1 { - async_send_email(&user).await; + async_send_email(&user).await; } } } @@ -204,7 +204,7 @@

Async/Await

fn main() { impl Future for NotifyUser { type Output = (); - + fn poll(&mut self, cx: &mut Context) -> Poll<()> { loop { match self.state { @@ -219,43 +219,43 @@

Async/Await

}

The poll method starts a loop because in the case that one of the states isn’t blocked, the state machine can perform multiple state transitions in a single poll call. This reduces the number of poll calls the executor needs to make.

Now, let’s look at how each state performs the state transition.

-

When we initialize NotifyUser, its state is State::Unpolled, which represents the starting state. When we poll NotifyUser for the first time, it calls async_fetch_user to instantiate and store the fetch_user_fut state machine.

+

When we initialize NotifyUser, its state is State::Unpolled, which represents the starting state. When we poll NotifyUser for the first time, it calls async_fetch_user to instantiate and store the fetch_user_fut state machine.

It then transitions its state to State::FetchingUser. Note that this code block doesn’t return Poll::Pending. This is because none of the executed code is blocking, so we can go ahead and execute the handle for the next state transition.

#![allow(unused)]
 fn main() {
 State::Unpolled => {
-		self.fetch_user_fut = Some(async_fetch_user(self.user_id));
-		self.state = State::FetchingUser;
+	self.fetch_user_fut = Some(async_fetch_user(self.user_id));
+	self.state = State::FetchingUser;
 }
 }

When we get to the FetchinUser state, it polls the fetch_user_fut to see if it’s ready. If it’s Pending, we return Poll::Pending. Otherwise, NotifyUser can perform its next state transition. If self.user.group == 1, it needs to create and store the fetch_user_fut state machine and transition the state to State::SendingEmail. Otherwise, it can transition its state to State::Ready.

#![allow(unused)]
 fn main() {
 State::FetchingUser => {
-		match self.fetch_user_fut.unwrap().poll(cx) {
-				Poll::Pending => return Poll::Pending,
-				Poll::Ready(user) => {
-						self.user = Some(user);
-						if self.user.group == 1 {
-								self.fetch_user_fut = Some(async_send_email(&self.user));
-								self.state = State::SendingEmail;
-						} else {
-								self.state = State::Ready;
-						}
-				}
+	match self.fetch_user_fut.unwrap().poll(cx) {
+		Poll::Pending => return Poll::Pending,
+		Poll::Ready(user) => {
+			self.user = Some(user);
+			if self.user.group == 1 {
+				self.fetch_user_fut = Some(async_send_email(&self.user));
+				self.state = State::SendingEmail;
+			} else {
+				self.state = State::Ready;
+			}
 		}
+	}
 }
 }

If the state is SendingEmail, it polls send_email_fut to check if it’s ready. If it is, it transitions the state to State::Ready. Otherwise, it returns Poll::Pending.

#![allow(unused)]
 fn main() {
 State::SendingEmail => {
-		match self.send_email_fut.unwrap().poll(cx) {
-				Poll::Pending => return Poll::Pending,
-				Poll::Ready(()) => {
-						self.state = State::Ready;
-				}
+	match self.send_email_fut.unwrap().poll(cx) {
+		Poll::Pending => return Poll::Pending,
+		Poll::Ready(()) => {
+			self.state = State::Ready;
 		}
+	}
 }
 }

Finally, if the state is Ready, NotifyUser returns Poll::Ready(()) to indicate that the state machine is complete.

@@ -283,7 +283,7 @@

Async/Await

impl Future for NotifyUser { type Output = (); - + fn poll(&mut self, cx: &mut Context) -> Poll<()> { loop { match self.state { @@ -293,25 +293,25 @@

Async/Await

}, State::FetchingUser => { match self.fetch_user_fut.unwrap().poll(cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(user) => { - self.user = Some(user); - if self.user.group == 1 { - self.fetch_user_fut = Some(async_send_email(&self.user)); - self.state = State::SendingEmail; - } else { - self.state = State::Ready; - } + Poll::Pending => return Poll::Pending, + Poll::Ready(user) => { + self.user = Some(user); + if self.user.group == 1 { + self.fetch_user_fut = Some(async_send_email(&self.user)); + self.state = State::SendingEmail; + } else { + self.state = State::Ready; } + } } }, State::SendingEmail => { - match self.send_email_fut.unwrap().poll(cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(()) => { - self.state = State::Ready; - } + match self.send_email_fut.unwrap().poll(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(()) => { + self.state = State::Ready; } + } }, State::Ready => return Poll::Ready(()); } diff --git a/executor/local_executor.html b/executor/local_executor.html index 16d8187..3b360a9 100644 --- a/executor/local_executor.html +++ b/executor/local_executor.html @@ -185,7 +185,7 @@

Deep Di "There is already an LocalExecutor running on this thread" ); LOCAL_EX.set(self, || { - let join_handle = self.spawn(async move { future.await }); + let join_handle = self.spawn(async move { future.await }); let waker = dummy_waker(); let cx = &mut Context::from_waker(&waker); pin!(join_handle); @@ -207,7 +207,7 @@

Deep Di fn main() { scoped_tls::scoped_thread_local!(static LOCAL_EX: LocalExecutor); } -

Next, it calls spawn to create and schedule the task onto the TaskQueue.

+

Next, it calls spawn to create and schedule the task onto the TaskQueue.

It then loops until the future is completed. It’s super important to understand that the poll method here doesn’t actually poll the user-provided future. It simply polls the JoinHandle, which checks if the COMPLETED flag on the task’s state is set.

Since the executor is single-threaded, looping alone won’t actually progress the underlying future. Therefore, in each loop, the executor calls the run_task_queues method.

run_task_queues simply loops and calls run_one_task_queue until there are no more tasks left in the TaskQueue.

diff --git a/print.html b/print.html index 738c2ab..1706d88 100644 --- a/print.html +++ b/print.html @@ -283,7 +283,7 @@

spawn_local async fn notify_user(user_id: u32) { let user = async_fetch_user(user_id).await; if user.group == 1 { - async_send_email(&user).await; + async_send_email(&user).await; } } } @@ -325,7 +325,7 @@

spawn_local fn main() { impl Future for NotifyUser { type Output = (); - + fn poll(&mut self, cx: &mut Context) -> Poll<()> { loop { match self.state { @@ -340,43 +340,43 @@

spawn_local }

The poll method starts a loop because in the case that one of the states isn’t blocked, the state machine can perform multiple state transitions in a single poll call. This reduces the number of poll calls the executor needs to make.

Now, let’s look at how each state performs the state transition.

-

When we initialize NotifyUser, its state is State::Unpolled, which represents the starting state. When we poll NotifyUser for the first time, it calls async_fetch_user to instantiate and store the fetch_user_fut state machine.

+

When we initialize NotifyUser, its state is State::Unpolled, which represents the starting state. When we poll NotifyUser for the first time, it calls async_fetch_user to instantiate and store the fetch_user_fut state machine.

It then transitions its state to State::FetchingUser. Note that this code block doesn’t return Poll::Pending. This is because none of the executed code is blocking, so we can go ahead and execute the handle for the next state transition.

#![allow(unused)]
 fn main() {
 State::Unpolled => {
-		self.fetch_user_fut = Some(async_fetch_user(self.user_id));
-		self.state = State::FetchingUser;
+	self.fetch_user_fut = Some(async_fetch_user(self.user_id));
+	self.state = State::FetchingUser;
 }
 }

When we get to the FetchinUser state, it polls the fetch_user_fut to see if it’s ready. If it’s Pending, we return Poll::Pending. Otherwise, NotifyUser can perform its next state transition. If self.user.group == 1, it needs to create and store the fetch_user_fut state machine and transition the state to State::SendingEmail. Otherwise, it can transition its state to State::Ready.

#![allow(unused)]
 fn main() {
 State::FetchingUser => {
-		match self.fetch_user_fut.unwrap().poll(cx) {
-				Poll::Pending => return Poll::Pending,
-				Poll::Ready(user) => {
-						self.user = Some(user);
-						if self.user.group == 1 {
-								self.fetch_user_fut = Some(async_send_email(&self.user));
-								self.state = State::SendingEmail;
-						} else {
-								self.state = State::Ready;
-						}
-				}
+	match self.fetch_user_fut.unwrap().poll(cx) {
+		Poll::Pending => return Poll::Pending,
+		Poll::Ready(user) => {
+			self.user = Some(user);
+			if self.user.group == 1 {
+				self.fetch_user_fut = Some(async_send_email(&self.user));
+				self.state = State::SendingEmail;
+			} else {
+				self.state = State::Ready;
+			}
 		}
+	}
 }
 }

If the state is SendingEmail, it polls send_email_fut to check if it’s ready. If it is, it transitions the state to State::Ready. Otherwise, it returns Poll::Pending.

#![allow(unused)]
 fn main() {
 State::SendingEmail => {
-		match self.send_email_fut.unwrap().poll(cx) {
-				Poll::Pending => return Poll::Pending,
-				Poll::Ready(()) => {
-						self.state = State::Ready;
-				}
+	match self.send_email_fut.unwrap().poll(cx) {
+		Poll::Pending => return Poll::Pending,
+		Poll::Ready(()) => {
+			self.state = State::Ready;
 		}
+	}
 }
 }

Finally, if the state is Ready, NotifyUser returns Poll::Ready(()) to indicate that the state machine is complete.

@@ -404,7 +404,7 @@

spawn_local impl Future for NotifyUser { type Output = (); - + fn poll(&mut self, cx: &mut Context) -> Poll<()> { loop { match self.state { @@ -414,25 +414,25 @@

spawn_local }, State::FetchingUser => { match self.fetch_user_fut.unwrap().poll(cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(user) => { - self.user = Some(user); - if self.user.group == 1 { - self.fetch_user_fut = Some(async_send_email(&self.user)); - self.state = State::SendingEmail; - } else { - self.state = State::Ready; - } + Poll::Pending => return Poll::Pending, + Poll::Ready(user) => { + self.user = Some(user); + if self.user.group == 1 { + self.fetch_user_fut = Some(async_send_email(&self.user)); + self.state = State::SendingEmail; + } else { + self.state = State::Ready; } + } } }, State::SendingEmail => { - match self.send_email_fut.unwrap().poll(cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(()) => { - self.state = State::Ready; - } + match self.send_email_fut.unwrap().poll(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(()) => { + self.state = State::Ready; } + } }, State::Ready => return Poll::Ready(()); } @@ -1114,7 +1114,7 @@

Deep Di "There is already an LocalExecutor running on this thread" ); LOCAL_EX.set(self, || { - let join_handle = self.spawn(async move { future.await }); + let join_handle = self.spawn(async move { future.await }); let waker = dummy_waker(); let cx = &mut Context::from_waker(&waker); pin!(join_handle); @@ -1136,7 +1136,7 @@

Deep Di fn main() { scoped_tls::scoped_thread_local!(static LOCAL_EX: LocalExecutor); } -

Next, it calls spawn to create and schedule the task onto the TaskQueue.

+

Next, it calls spawn to create and schedule the task onto the TaskQueue.

It then loops until the future is completed. It’s super important to understand that the poll method here doesn’t actually poll the user-provided future. It simply polls the JoinHandle, which checks if the COMPLETED flag on the task’s state is set.

Since the executor is single-threaded, looping alone won’t actually progress the underlying future. Therefore, in each loop, the executor calls the run_task_queues method.

run_task_queues simply loops and calls run_one_task_queue until there are no more tasks left in the TaskQueue.