Hacker News new | ask | show | jobs
by jude- 1685 days ago
I'm not saying that we should go back to hand-rolling our own epoll loops. I'm saying that we can do better than async/await by making both the state machines and event loop explicit. For example, here's an API I'd prefer to use over async/await:

   /// A state machine that adds three numbers and uploads them to a web server
   struct AddAndUpload {
      /// I/O handle to the event loop
      io: IOClient,
      /// Buffer to store numbers I load
      nums: [u64; 3],
      /// URL to upload the data to
      url: String
   }

   impl AddAndUpload {
      /// Constructor
      pub fn new(io: IOClient, url: String) -> AddAndUpload {
         AddAndUpload {
            io,
            nums: [0u64; 3],
            url
         }
      }

      /// Entry point to this state machine
      pub fn inner_main(&mut self) -> Result<(), IOClient::Error> {
         /// go and get the data
         let n1_fut : IOClient::Future<u64> = self.io.sql_async("SELECT n1 FROM table1", &[])?;
         let n2_fut : IOClient::Future<u64> = self.io.sql_async("SELECT n2 FROM table2", &[])?;
         let n3_fut : IOClient::Future<u64> = self.io.sql_async("SELECT n3 FROM table3", &[])?;

         // wait for all I/O operations to finish
         IOClient::wait_all(&[&n1_fut, &n2_fut, &n3_fut])?;

         // extract results
         let n1 = n1_fut.into_inner();
         let n2 = n2_fut.into_inner();
         let n3 = n3_fut.into_inner();

         // upload them
         let sum = n1 + n2 + n3;
         let upload_fut : IOClient::Future<IOClient::HTTPStatus> = self.io.http_post_async(&self.url, &["content-type: application/octet-stream"], &sum.to_be_bytes())?;

         let upload_http_status = upload_fut.wait()?.into_inner();

         match upload_http_status.as_u16() {
            200 => {
               Ok(())
            }
            400..499 => {
               Err(IOClient::Error::Custom("client error"))
            }
            500..599 => {
               Err(IOClient::Error::Custom("server error"))
            }
            x => {
               Err(IOClient::Error::Custom("Nonsensical HTTP code"))
            }
         }
      }
   }

   impl IOClient::StateMachine for AddAndUpload {
      type Return = ();
      fn main(&mut self) -> Result<(), IOClient::Error> {
         self.inner_main()
      }
   }

   /\* somewhere else \*/

   fn main() {
       let io_server = IOServer::spawn().unwrap();
       let io_client = io_server.client().unwrap();
       let add_and_upload = AddAndUpload::new(io_client, "http://example.com".to_string());
       loop {
         io_server.run().unwrap();
         match add_and_upload.get_machine_status() {
            Ok(IOClient::StateMachine::Finished(result)) => {
               eprintln!("add_and_uploaded exited with {:?}", &result);
               break;
            }
            Ok(_) => {},
            Err(e) => {
               panic!("add_and_upload aborted: {:?}", &e);
            }
         }
      }
      io_server.terminate();
   }
1 comments

okay, but that doesnt solve basically the main thing that async paradigms seek to solve: sharing of resources between waiting disjoint processes.

your statemachine blocks the thread. if you had a more complicated state machine, maybe nested machines, theyd block each other because they dont know how to cooperate.

This code is just an example. The state machine can easily run in its own thread, separate from the main thread. As long as the state machine had an IOClient instance that lets it send I/O requests to the IOServer and receive I/O results, you're good. Also, you could imagine an IOClient having an API that takes a StateMachine instance as input, and returns an IOClient::Future that resolved to the machine's main() return value.