Omit sync requirements

I created a synchronous async runtime… in other words, all future polls are handled on the same thread by a mainloop. I dont want to go into the why (bare metal, embedded), but it works great.

The only annoying thing is that send+ sync is required for all my object shared in my async functions and futures. what would be a good solution to circumvent this requirement? A solution as least invasive as possible would be desired. I might want to leave the door open to one day switch to a threaded runtime.

one solution i can think of is to create my own mutex, rwlock and arc objects which implement sync + send requirements but actually dont do any synchronization. I dont really like that as i dont want to reimplement and maintain these std equivalent interfaces.
Better would be to declare my used structs as sync + send. But im a bit reluctant as some people my actually think my objects are threadsafe
Are there any other settings or nasty flags i can set to drop the sync+send markers? Thanks!

Are you using lots of unsafe? Otherwise, how do you build a Send+Sync object that is not threadsafe ? I thought part of the point of the borrow checker is to make it hard to write these bugs.

Mmh yes your right unsafe is required for this to work. So thats a big no-no!

Access to futures is mutually exclusive already anyways. Even data in a Mutex needs to be Send, the mutex just adds a Sync. So mutex won't help you, a multi-threaded runtime will always need a Send bound on the future type, so you cannot avoid this bound if you want to be future-compatible with moving to a multi-threaded implementation.


On second read, you're talking about Send and Sync requirements in the values used inside your futures, not the futures themself.

It is possible to get Send+Sync bounds on a type when you're only planning on accessing it from a single thread anyway, by the) checking that each access happens from the same thread, using something like send_wrapper - Rust (IIRC there's other similar crates, too).

If you want to use something like this to sort-of "emulate" a mutex API but more efficiently, I'd question if the overhead of a Mutex is large enough so that you can find a different, but safe, alternative implementation that relies on (and checks) that it's only accessed from a single thread[1]. If you do manage some performance benefit anyway, then that's presumably a viable approach; you'd switch out the implementation for using a real Mutex once you switch to a multi-threaded runtime, and user-code will rely on the promise that they shall never encounter a panic due to access from different threads when they only access your mutex-like type from your async runtime.


  1. there shouldn't be too much overhead to begin with in accessing a mutex from a single thread only, I believe; though of course I might be wrong ↩︎

1 Like

Thanks for your input!

My problem is based on the premise that locking is expensive, and therefore using real mutexes is something i should avoid. I never thought about accepting the penalty of useless locking as a viable solution. What makes you think overhead of mutexes will be minimal?

As far as I know at least the Mutex from parking-lot does nothing beyond atomically updating some byte in order to lock or unlock in case the was no contention (i. e. the lock wasn't currently held already). I don't know how much more overhead the standard library mutex has, but at least for the one from parking-lot, one atomic update operation for locking and another one for unlocking sounds fairly cheap to me.

(When a Mutex is used from a single thread, this fast-path case will always apply, since the mutex already being held would mean it's already held by the current thread, and this would result in a dead-lock, so in non-buggy code, this should never happen.)

2 Likes

Well, there isn't much implementation involved, as nothing needs to be done, right? This seems a reasonable solution to me ( as is simply not worrying the rather minor cost of using the standard structs ).

The only annoying thing is that send+ sync is required for all my object shared in my async functions and futures.

Where are these requirements actually coming from? Rust's built-in async functionality is entirely able to act under !Send conditions (and there are several async runtimes that are single-threaded and thus allow !Send futures). If you can find and eliminate the constraint, that would be better than faking Send.

4 Likes

Where are these requirements actually coming from? Rust's built-in async functionality is entirely able to act under !Send conditions

Yes your right, i was writing this out of the top of my head, sorry for that.

I thought about and i think i will go for using std mutexes for now. A second step for the future could be to measure performance and decide to step over to something more compliant.

I'd be curious to hear their names, as I'd been contemplating the possibility of such a runtime!

All of these also allow you to block_on() a future, taking over the current thread for it, and that future need not be Send (regardless of the configuration of the rest of the system) because it is always polled within that function call and thus on the same thread.

2 Likes