Coder Social home page Coder Social logo

may's Introduction

May

May is a high-performance library for programming stackful coroutines with which you can easily develop and maintain massive concurrent programs. It can be thought as the Rust version of the popular Goroutine.


Table of contents


Features

  • The stackful coroutine implementation is based on generator;
  • Support schedule on a configurable number of threads for multi-core systems;
  • Support coroutine version of a local storage (CLS);
  • Support efficient asynchronous network I/O;
  • Support efficient timer management;
  • Support standard synchronization primitives, a semaphore, an MPMC channel, etc;
  • Support cancellation of coroutines;
  • Support graceful panic handling that will not affect other coroutines;
  • Support scoped coroutine creation;
  • Support general selection for all the coroutine API;
  • All the coroutine API are compatible with the standard library semantics;
  • All the coroutine API can be safely called in multi-threaded context;
  • Both stable, beta, and nightly channels are supported;
  • x86_64 GNU/Linux, x86_64 Windows, x86_64 macOS, AArch64 GNU/Linux, and AArch64 macOS are supported.

Usage

A naive echo server implemented with May:

#[macro_use]
extern crate may;

use may::net::TcpListener;
use std::io::{Read, Write};

fn main() {
    let listener = TcpListener::bind("127.0.0.1:8000").unwrap();
    while let Ok((mut stream, _)) = listener.accept() {
        go!(move || {
            let mut buf = vec![0; 1024 * 16]; // alloc in heap!
            while let Ok(n) = stream.read(&mut buf) {
                if n == 0 {
                    break;
                }
                stream.write_all(&buf[0..n]).unwrap();
            }
        });
    }
}

More examples

The CPU heavy load examples

The I/O heavy bound examples


Performance

You can refer to https://tfb-status.techempower.com/ to get the latest may_minihttp comparisons with other most popular frameworks.


Caveat

There is a detailed document that describes May's main restrictions. In general, there are four things you should follow when writing programs that use coroutines:

  • Don't call thread-blocking API (It will hurt the performance);
  • Carefully use Thread Local Storage (access TLS in coroutine might trigger undefined behavior).

It's considered unsafe with the following pattern:

set_tls();
// Or another coroutine API that would cause scheduling:
coroutine::yield_now(); 
use_tls();

but it's safe if your code is not sensitive about the previous state of TLS. Or there is no coroutines scheduling between set TLS and use TLS.

  • Don't run CPU bound tasks for long time, but it's ok if you don't care about fairness;
  • Don't exceed the coroutine stack. There is a guard page for each coroutine stack. When stack overflow occurs, it will trigger segment fault error.

Note:

The first three rules are common when using cooperative asynchronous libraries in Rust. Even using a futures-based system also have these limitations. So what you should really focus on is a coroutine stack size, make sure it's big enough for your applications.


How to tune a stack size

If you want to tune your coroutine stack size, please check out this document.


License

May is licensed under either of the following, at your option:

may's People

Contributors

alanhoff avatar alkis avatar chritchens avatar gallaghercommajack avatar gkbrk avatar leandros avatar skade avatar xudong-huang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

may's Issues

impl connect_timeout for TcpStream

    //// Opens a TCP connection to a remote host with a timeout.
    ///
    /// Unlike `connect`, `connect_timeout` takes a single [`SocketAddr`] since
    /// timeout must be applied to individual addresses.
    ///
    /// It is an error to pass a zero `Duration` to this function.
    ///
    /// Unlike other methods on `TcpStream`, this does not correspond to a
    /// single system call. It instead calls `connect` in nonblocking mode and
    /// then uses an OS-specific mechanism to await the completion of the
    /// connection request.
    ///
    /// [`SocketAddr`]: ../../std/net/enum.SocketAddr.html
    #[stable(feature = "tcpstream_connect_timeout", since = "1.21.0")]
    pub fn connect_timeout(addr: &SocketAddr, timeout: Duration) -> io::Result<TcpStream>

License consistency

Currently may depends on may_queue, which is LGPL, but is itself licensed under MIT/Apache. This is against the terms of the LGPL. Since you're also the author of may_queue, can you replace the license there with MIT/Apache?

Alternately, you could make all of the may libraries LGPL, or another GPL-compatible license.

Abstract coroutine implementation

Hello @Xudong-Huang. Thanks for the amazing library. I am personally do not have knowledge to write a good coroutine implementation, so I patiently sit and wait while someone write one to use it in my projects. And I've already seen several attempts to do so with different api and different implementation details.

To properly use such coroutine library it has to have an ecosystem around with several libraries with file/networking api and other utility stuff. Is it possible to write such a client library in the abstract way, which will work with different coroutine implementations?

As I understand, if go function will be a macro (it already is a macro in your library), and yield function will be a macro, then a client library can just call it without having to know which specific library will provide the implementation. I expect that this specification can be made in an application, which uses the library.

So my primary question: Can we write a client library that uses stackful coroutines without having to know which exact implementation it uses?

yield type mismatch error detected

Hi.
I'm currently in the process to port my relm crate from tokio to may (see this branch) and I have an issue with the http example which is based on the may version of hyper.
The code panic around here saying the following error:

yield type mismatch error detected, expected type: may::coroutine_impl::EventSubscriber
thread '<unnamed>' panicked at 'Box<Any>'

It seems to come from the coroutine crate, but I'm not sure why it happens.
Thanks for your help.

Failure on Rust Nightly.

On latest nightly (rustc 1.29.0-nightly (9fd3d7899 2018-07-07)) the compilation fails with

error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
 --> /root/.cargo/git/checkouts/may-adabe427d9527748/295494d/may_queue/src/block_node.rs:4:5
  |
4 | use self::alloc::raw_vec::RawVec;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
  --> /root/.cargo/git/checkouts/may-adabe427d9527748/295494d/may_queue/src/block_node.rs:44:11
   |
44 |     data: RawVec<T>,
   |           ^^^^^^^^^
   |
   = help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
  --> /root/.cargo/git/checkouts/may-adabe427d9527748/295494d/may_queue/src/block_node.rs:55:19
   |
55 |             data: RawVec::with_capacity(BLOCK_SIZE),
   |                   ^^^^^^^^^^^^^^^^^^^^^
   |
   = help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
 --> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:8:5
  |
8 | use alloc::raw_vec::RawVec;
  |     ^^^^^^^^^^^^^^^^^^^^^^
  |
  = help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
  --> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:20:10
   |
20 |     buf: RawVec<usize>,
   |          ^^^^^^^^^^^^^
   |
   = help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
  --> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:26:18
   |
26 |             buf: RawVec::with_capacity(0),
   |                  ^^^^^^^^^^^^^^^^^^^^^
   |
   = help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
  --> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:41:18
   |
41 |             buf: RawVec::with_capacity(size),
   |                  ^^^^^^^^^^^^^^^^^^^^^
   |
   = help: add #![feature(raw_vec_internals)] to the crate attributes to enable
   Compiling num-iter v0.1.37
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
  --> /root/.cargo/git/checkouts/may-adabe427d9527748/295494d/may_queue/src/block_node.rs:63:34
   |
63 |             let data = self.data.ptr().offset((index & BLOCK_MASK) as isize);
   |                                  ^^^
   |
   = help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
  --> /root/.cargo/git/checkouts/may-adabe427d9527748/295494d/may_queue/src/block_node.rs:73:34
   |
73 |             let data = self.data.ptr().offset((index & BLOCK_MASK) as isize);
   |                                  ^^^
   |
   = help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
  --> /root/.cargo/git/checkouts/may-adabe427d9527748/295494d/may_queue/src/block_node.rs:84:36
   |
84 |         let mut p_data = self.data.ptr().offset((start & BLOCK_MASK) as isize);
   |                                    ^^^
   |
   = help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
  --> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:51:31
   |
51 |             let buf = stk.buf.ptr();
   |                               ^^^
   |
   = help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
  --> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:63:36
   |
63 |             let mut ptr = self.buf.ptr();
   |                                    ^^^
   |
   = help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
  --> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:69:28
   |
69 |         let cap = self.buf.cap();
   |                            ^^^
   |
   = help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
  --> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:79:18
   |
79 |         self.buf.cap()
   |                  ^^^
   |
   = help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
  --> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:84:27
   |
84 |         unsafe { self.buf.ptr().offset(self.buf.cap() as isize) as *mut usize }
   |                           ^^^
   |
   = help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
  --> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:84:49
   |
84 |         unsafe { self.buf.ptr().offset(self.buf.cap() as isize) as *mut usize }
   |                                                 ^^^
   |
   = help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error[E0658]: use of unstable library feature 'raw_vec_internals': implemention detail
  --> /root/.cargo/registry/src/github.com-1ecc6299db9ec823/generator-0.6.9/src/stack.rs:90:18
   |
90 |         self.buf.ptr()
   |                  ^^^
   |
   = help: add #![feature(raw_vec_internals)] to the crate attributes to enable
error: aborting due to 11 previous errors

A correct way to use openssl and may together

I am trying to do HTTPS requests with MAY, but I'm not sure that the way I do it is absolutely correct. Now my code looks like this:

use may::coroutine;
use may::go;
use openssl::ssl::SslConnector;
use openssl::ssl::SslMethod;
use std::io::{Read, Write};

type MayStream = may::net::TcpStream;

fn main() {
    coroutine::scope(|scope| {
        for _ in 0..100 {
            go!(scope, move || {
                let stream = MayStream::connect("91.198.174.192:443").unwrap();

                let mut stream = SslConnector::builder(SslMethod::tls()) // (1)
                    .unwrap()
                    .build()
                    .connect("www.wikipedia.org", stream)
                    .unwrap();

                let request =
                    b"GET https://www.wikipedia.org/ HTTP/1.1\r\nHost: www.wikipedia.org\r\n\r\n";
                stream.write_all(request).unwrap();
                stream.flush().unwrap();

                let mut buffer = vec![0; 4096];
                stream.read_to_end(&mut buffer).unwrap();

                println!("{}", String::from_utf8(buffer).unwrap());
            });
        }
    });
}

Is the created SslStream (1) is coroutines-oriented, meaning that it uses MAY's utilities instead of stopping the whole thread?

support blocking API

we can use this API to wrap the code that would run for a long time or would block a thread.

when the API is invoked, the worker thread would become a normal thread, and it will kick another thread to continue scheduling on the worker queue.

when the API is invoked in the IO thread, it would first re-scheduling to a worker thread.

Weird TCP performance characteristics

I've put together a short example on my fork here https://github.com/GallagherCommaJack/may/blob/master/benches/lib.rs

There's an extreme amount of jitter in the performance - from a cold start it'll be quite fast, then suddenly slow down, with speed coming back in bursts of ~100 iterations.

From looking at perf reports I'm guessing this is an issue with not closing out TCP connections properly, but I haven't looked closely enough at the TCP implementations to be sure.

I've also made a separate gist for just the tcp bench - https://gist.github.com/GallagherCommaJack/50f1880b434cacc48eacd19e94ca12af

TLS/SSL networking

Is there any plans for implementing modules for encrypted networking?

Is it possible to have coroutine callback not implementing Sync?

Hi.
In my quest to use may in relm, I've stumbled on the issue that may coroutines require both Send and Sync.
My issue has to do with the fact that gtk widgets does not implement these traits, but I think it could workaround this fact by using send-cell and executing gtk-related code in the main thread with MainContext::invoke() if only the Send trait was required.
So, my question is: why is Sync required?
Is it because a coroutine can be executed in different threads?
If it is the case, would it be possible to have a coroutine bound to a thread that only require Send or a coroutine that Send its state to a new thread when it's scheduled to run on a new thread (assuming it is sound)?
(Maybe this can help?)
Thanks.

http client/server library for may

What's the recommended library for http client/server for may? If none exists perhaps this should be top priority to increase adoption?

UB in config

I looked into the code a bit and this is what I found:

https://github.com/Xudong-Huang/may/blob/master/src/config.rs#L25

    pub fn set_io_workers(&self, workers: usize) -> &Self {
        info!("set io workers={:?}", workers);
        unsafe {
            IO_WORKERS = workers;
        }
        self
    }

IO_WORKERS is defined as static mut WORKERS: usize = 2;. There's nothing preventing the user of the library from calling this from multiple threads, which is by definition a data race (unsynchronized access from multiple threads, at least one of them is write), which is undefined behaviour:
https://doc.rust-lang.org/reference/behavior-considered-undefined.html

The function is not marked as unsafe. This means the user of the library is able to invoke UB without using any UB on his side, which is what Rust should be preventing.

Please use appropriate tools for synchronisation (either mutex or atomis).

test_condvar_canceled timeout

test sync::condvar::tests::test_condvar_canceled ... test sync::condvar::tests::test_condvar_canceled has been running for over 60 seconds

Stack probe

Hi.
To have better safety, could we use guard pages and stack probes in may?
I'm not sure it's possible, just asking.
Thanks.

when cancel a coroutine, Mutex should not be poisoned

when cancel a coroutine we trigger a cancel panic inside it. This would cause the mutex got poisoned if it not in the wait state. A better and elegant way is to let the mutex finish normally.

this is possible when you hold a mutex guard, and wait for other resources in the coroutine. when the cancel comes, it will run the mutex guard drop in a panicking context while unwind the stack.

and this is only valid in coroutine context, we can't cancel a thread.

add unsafe property for coroutine creation

add unsafe declaration for coroutine creation since it will trigger undefined behavior if user's code

  • direct or indirect access TLS
  • stack exceeding during execution

ref #6

this would break current test and usage.

support running coroutines on only one thread

when user config MAY as following

may::config()::set_workers(1).set_io_workers(0);

MAY should running all the coroutines on a single thread. this would

  • help to avoid some tricky sync issue
  • allow library to access TLS since coroutines won't scheduled on other thread

This will NOT help the TLS issue. Let coroutines bound to a single thread is still not safe, e.g. coroutine A in the thread set a TLS into value A', and then another coroutine B is scheduled and it set the TLS into value B', after coroutine A rescheduled on this same thread, it will access a dirty value!

support nonblocking read/write in coroutine context

currently all the io operations are "blocking" in coroutine context, but some times we still need nonblocking read/write that not evolve rescheduling, e.g. detect if a socket has some data available.

the API interface should be the same as std API. We need to store an extra blocking flag for the io object internally.

support set stack size and task name for go! macro

Example

go!(|| {
    println!("task with default stack size");
});

go!(|| {
    println!("task with specific stack size");
},
4096);

go!(|| {
    println!("task with specific stack size and name");
},
4096, String::from("task3"));

stack overflow when using rand and lazy_static

I'm trying to understand why this small program is failing to run:

#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate may;
extern crate rand;

use std::collections::HashMap;

lazy_static! {
    static ref DATA: Vec<u8> = vec![rand::random::<u8>()];
}

fn main() {
    let mut entries: HashMap<usize, Vec<u8>> = HashMap::new();

    let task = go!(move || {
        entries.insert(0, DATA.clone());
    });

    task.join().unwrap();
}

This program in particular gives me the error stack overflow detected, size=4096, but if I try to do something more elaborated using shared access to entries it also raises some address boundary errors.

The interesting part is that this single combination is failing but if I use the crates separately they work fine:

// This will work
static ref DATA: Vec<u8> = vec![123];

// Or this
let data = vec![rand::random::<u8>()];
let task = go!(...);

I'm using may 0.2.0 and rand 0.4.2.

bounded mpmc?

Hi,

And congrats for May. This is definitely a game changer, that makes it a real joy to do async I/O in Rust.

Is there currently a way to have a bounded mpmc queue? I can use a shared atomic counter, but I was wondering if there was already something available, or if you were planning to implement it.

Cheers!

What should I use instead of copy?

The caveats mention that one should not use copy since it can easily blow up the stack size. What's the recommended method for passing bytes from one stream into another?

[rfc] new scheduler

The current scheduler has the following problems:

  1. it needs configuration (workers, io_workers, run_on_io): it would be nicer if user does not need to configure anything and yet get maximum performance all the time
  2. it has a global ready list: scaling to anything other than a few cores and this will become a bottleneck
  3. timer thread is also global: another point of contention
  4. event loop is on a separate thread
    Both 3) and 4) cause unnecessary OS context switches whenever there is a timer expiry or I/O poll

I propose the following design for a new scheduler which I plan to implement. This is a request for comments. My understanding of may is not that deep so it is possible that some things won't work :-)

  • may has N schedulers (S) where N is the number of CPUs of the machine
  • each S runs on its own kernel thread
  • each S has a single threaded eventloop (coros waiting for io) and a timerlist (coros waiting on timer)
  • each S has its own readylist which contains coroutines ready to run (crossbeam-deque: work-stealing)
  • each S has its own yieldlist (coros that yielded and can resume immediately)
  • may has a single list of parked threads (parked)
  • may has a counter of stealing threads (num_stealing)

The scheduling loop will look like this:

loop {
  // when yieldlist is not empty, this means we have coroutines the yielded but are ready to run.
  // as such we do need to check the eventloop and return as fast as possible
  let timeout = if !sched.yieldlist.empty() { 0 } else { sched.next_deadline() - now() };
  while let Some(co) = yieldlist.pop_back() {
    sched.readylist.push(co);
  }
  // select moves ready coroutines to the local readylist. each ready coroutine is pushed to the front
  sched.eventloop.select(timeout);
  // if we have more than 1 coroutine ready to run, and there are no threads stealing,
  // and we have parked threads, unpark one to increase parallelism
  if sched.readylist.len() > 1 && num_stealing.load(Ordering::Acquire) == 0 && !parked.empty() {
    if let Some(t) = parked.pop() {
      t.unpark();
    }
  }
  // run all coros until readylist is empty
  while let Some(co) = sched.readylist.pop() {
    run_coroutine(co);
  }
  // we have cpu bound coroutines that yielded, restart the loop to make more coroutines ready/expire timers, etc.
  if !select.yieldlist.empty() {
    continue;
  }
  // we have no ready coroutines to run. time to steal!
  assert!(sched.readlist.empty());
  num_stealing.fetch_add(1, Ordering::Release);
  // see implementation below
  if sched.steal() {
    continue;
  }
  // we didn't manage to steal anything, which means there is nothing to do.
  num_stealing.fetch_sub(1, Ordering::Release);
  parked.push(thread::current());
  thread::park()
}

To make stealing fast and avoid spurious park()/unpark() we spin for a while trying to steal and then give up.

fn steal(&mut self) {
  let deadline = now() + 100ms;  // needs tuning
  loop {
    let id = rand() % N;  // random victim
    if id == self.id {  // stealing from ourselves is silly :-p
      continue;
    } else {
      let stolen = loop {
        match schedules[id].readylist.steal() {
          Steal::Empty => break None,
          Steal::Data(co) => break Some(co),
          Steal::Retry => {},
      };
      if let Some(co)  = stolen {
        self.readylist.push(co);
        return true;
      }
    }
    if now() > deadline {
      return false;
    }
  }
}

Update may-minihttp implementation of TechEmpower benchmark to use serde_derive

This line uses the relatively slow serde_json::Value and allocates two BTreeMap nodes and two Strings.

I used the following microbenchmark to compare json! vs serde_derive as used by the hyper and tokio-minihttp implementations. On my machine:

test bench_derive     ... bench:          59 ns/iter (+/- 5)
test bench_json_value ... bench:         254 ns/iter (+/- 2)

This means you are wasting (254 ns/response - 59 ns/response) * 1,148,424 responses/second = 0.224 CPU-seconds per second, which seems like an enormous amount to give up.

#![feature(test)]

#[macro_use]
extern crate serde_derive;

#[macro_use]
extern crate serde_json;

extern crate serde;
extern crate test;

use test::Bencher;

#[bench]
fn bench_derive(b: &mut Bencher) {
    #[derive(Serialize)]
    struct Message<'a> {
        message: &'a str,
    }

    b.iter(|| {
        serde_json::to_vec(&Message { message: "Hello, World!" }).unwrap();
    });
}

#[bench]
fn bench_json_value(b: &mut Bencher) {
    b.iter(|| {
        serde_json::to_vec(&json!({"message": "Hello, World!"})).unwrap();
    });
}

IoData should not be cloneable

need to refine IoData,make sure every io object own only one IoData
impl Drop for IoData to del the handle from eventloop

Development status

First of all, thanks for this amazing library! I'm trying to decide which async library to use in my project (options: May and Tokio), and it seems like May latest commit was almost 2 months ago. Thus, my question is very simple: is any further development of the library planned? Thank you!

How to cancel a coroutine?

It seems that this crate provides the ability to cancel coroutines, but I can't resolve how to do it. What is the general syntax for coroutine cancellation?

way to control the scheduler

This can allow some usecases:

  • for a given coroutine/scope limit parallelism to 1
  • pin a coroutine/scope to a specific thread (UI thread for example)

Perhaps this is already possible?

linux AIO support

Just read https://www.usenix.org/system/files/fast19-kourtis.pdf and it makes a really compelling case for the combination of stackful coroutines + proper linux AIO (and eventually SPDK support). This is a combination I could actually imagine myself using in sled, where I'm now trying to scale toward a many-core architecture, but don't want to pay the various ergonomic costs associated with the popular async stuff in the rust ecosystem right now.

https://github.com/hmwill/tokio-linux-aio may be a nice reference for building linux AIO support for May.

Would you be interested in having AIO support in May directly, or do you see this as something better implemented in a separate library? Very curious about this :)

Do channels block coroutines?

I have this small example but I'm not able to understand why I'm getting the whole sentence after 18secs in my terminal instead of one char each second:

#[macro_use]
extern crate may;

use std::{thread, time};
use may::sync::mpsc::{channel, Sender};
use std::io::{self, Write};

struct Message(u8, Sender<u8>);

fn main() {
  let (tx, rx) = channel();

  thread::spawn(move || {
    while let Ok(Message(byte, inner_tx)) = rx.recv() {
      thread::sleep(time::Duration::from_millis(1000));
      inner_tx.send(byte).unwrap(); // I expect this to wake the coroutine that's waiting on .recv
    }
  });

  let sentence = "This is a sentence";

  may::coroutine::scope(|s| {
    for byte in sentence.as_bytes() {
      let tx = tx.clone();

      go!(s, move || {
        let (inner_tx, inner_rx) = channel();

        tx.send(Message(byte.clone(), inner_tx)).unwrap();

        if let Ok(byte) = inner_rx.recv() {
          io::stdout().write(&[byte]).unwrap();
        }
      });
    }
  });
}

Higher Latency compared to Tokio

Thank you for creating this high-performance library and making async easier. Reading your blog, it seems average and peak latency seems to be higher compared to tokio based implementation.

Is this due to higher throughput or scheduling coroutine?

e.g.

Tokio threaded:

Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    52.54us   56.39us  12.14ms   99.36%

vs

May coroutine:

Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.26ms    6.48ms 105.13ms   98.35%

Error: Cannot access stdout during shutdown

Hello! When I'm writing my code like this:

#[macro_use]
extern crate may;

fn main () {
    for i in 1..2000 {
        go!(move || {
            println!("{}", i);
        });
    }
}

I got a panic in the output:

// 1~989 omitted 
990
991
992
993
994
thread '<unnamed>' panicked at 'cannot access stdout during shutdown', libcore/option.rs:960:5
note: Run with `RUST_BACKTRACE=1` for a backtrace.

And I cannot get the correct output, which should include all numbers in [1, 2000).
What happened? I'm using may 0.3.0 from crates.io.

Resolve cast_ref_to_mut Clippy warning

I have noticed you have disabled cast_ref_to_mut Clippy error. I checked the code, and I found nonsensical code like the following:

// prevent release version wrong optimization!! use a volatile read
let push_index = unsafe { std::ptr::read_volatile(&self.push_index) };

This isn't wrong optimization, this is undefined behaviour caused by cast_ref_to_mut violation.

Volatile is 100% wrong when dealing with thread safety. Please consider migrating to atomic types specifically (atomic types like AtomicPtr are inner mutability types by the way). If you are concerned about performance, Ordering::Relaxed is pretty much free (other than disabling optimizations that would break this code anyways), so if you don't need atomics at a given point, you can simply use relaxed ordering.

Undefined behavior invoked by moving stacks between threads

A coroutine's stack can be moved from one thread to another. However, a stack may always contain things that are not Send, breaking things.

This is one example:

extern crate may;

use std::cell::RefCell;
use std::rc::Rc;
use std::thread::ThreadId;

use may::coroutine;
use coroutine::yield_now;

thread_local!(static ID: RefCell<Option<Rc<ThreadId>>> = RefCell::new(None));

fn main() {
    may::config().set_io_workers(60);
    may::config().set_workers(60);
    let h = coroutine::spawn(move || {
        let v = (0..10000)
            .map(|i| {
                coroutine::spawn(|| {
                    let handle = Rc::new(std::thread::current().id());
                    ID.with(|id| {
                        *id.borrow_mut() = Some(Rc::clone(&handle));
                    });
                    for _ in 0..10000 {
                        if *handle != std::thread::current().id() {
                            println!("Access to Rc content without a mutex from a different thread, {:?} vs {:?}",
                                     *handle, std::thread::current().id());
                        }
                        yield_now();
                    }
                })
            })
            .collect::<Vec<_>>();
        for i in v {
            i.join().unwrap();
        }
    });
    h.join().unwrap();
}

If something else was accessing the Rc (like making copis of it) from the original thread (which it could, because it is accessible in the thread local storage), it would be undefined behaviour โ€’ both the coroutine, that moved, and the thing in that thread (possibly other coroutine) could be accessing the counters in the Rc at the same time, or the data inside, which could be for example a RefCell.

Now, suggesting not to use thread local storage doesn't solve anything, because:

  • I might not use thread local storage myself, but I can't certainly be expected to audit all the libraries I use not to use thread local storage. Even the standard library uses thread local storage internally.
  • There are things that are not Send for other reasons. One example might be Zero-MQ sockets, which explode the whole application if ever touched from a different thread then they were created in.

I believe this problem is fundamental to any attempt to move stacks between threads in Rust. Such thing just breaks the Rust contract.

So my only suggestion is to create the coroutine in one thread (it is possible to check nothing Send crosses a closure boundary, so the closure can be safely sent to another thread) and then pin it there to that thread.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.