-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Thread Safe IO #8631
Thread Safe IO #8631
Conversation
libuv does not always catch SIGPIPE.
Each IO handle has a home event loop, which created it. When a task wants to use an IO handle, it must first make sure it is on that home event loop. It uses the scheduler handle in the IO handle to send itself there before starting the IO action. Once the IO action completes, the task restores its previous home state. If it is an AnySched task, then it will be executed on the new scheduler. If it has a normal home, then it will return there before executing any more code after the IO action.
@brson r? |
@anasazi Can you convert relevant I/O tests to use |
There are some non-deterministic deadlocks when tests are converted so I've removed the |
The deadlocks are specifically caused by the way the networking test cases are structured - they depend on scheduling behavior to ensure that the server sets up before the client. |
libuv handles are tied to the event loop that created them. In order to perform IO, the handle must be on the thread with its home event loop. Thus, when as task wants to do IO it must first go to the IO handle's home event loop and pin itself to the corresponding scheduler while the IO action is in flight. Once the IO action completes, the task is unpinned and either returns to its home scheduler if it is a pinned task, or otherwise stays on the current scheduler. Making new blocking IO implementations (i.e. files) thread safe is rather simple. Add a home field to the IO handle's struct in uvio and implement the HomingIO trait. Wrap every IO call in the HomingIO.home_for_io method, which will take care of the scheduling. I'm not sure if this remains thread safe in the presence of asynchronous IO at the libuv level. If we decide to do that, then this set up should be revisited.
Remove overlap between `manual_split_once` and `needless_splitn` changelog: Remove overlap between [`manual_split_once`] and [`needless_splitn`]. Fixes some incorrect `rsplitn` suggestions for [`manual_split_once`] Things that can trigger `needless_splitn` no longer trigger `manual_split_once`, e.g. ```rust s.[r]splitn(2, '=').next(); s.[r]splitn(2, '=').nth(0); s.[r]splitn(3, '=').next_tuple(); ``` Fixes some suggestions: ```rust let s = "should not match"; s.rsplitn(2, '.').nth(1); // old -> Some("should not match") Some(s.rsplit_once('.').map_or(s, |x| x.0)); // new -> None s.rsplit_once('.').map(|x| x.0); s.rsplitn(2, '.').nth(1)?; // old -> "should not match" s.rsplit_once('.').map_or(s, |x| x.0); // new -> early returns s.rsplit_once('.')?.0; ```
libuv handles are tied to the event loop that created them. In order to perform IO, the handle must be on the thread with its home event loop. Thus, when as task wants to do IO it must first go to the IO handle's home event loop and pin itself to the corresponding scheduler while the IO action is in flight. Once the IO action completes, the task is unpinned and either returns to its home scheduler if it is a pinned task, or otherwise stays on the current scheduler.
Making new blocking IO implementations (i.e. files) thread safe is rather simple. Add a home field to the IO handle's struct in uvio and implement the HomingIO trait. Wrap every IO call in the HomingIO.home_for_io method, which will take care of the scheduling.
I'm not sure if this remains thread safe in the presence of asynchronous IO at the libuv level. If we decide to do that, then this set up should be revisited.