Skip to content
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

Implement reentrant mutexes and make stdio use them #24029

Merged
merged 1 commit into from
Apr 8, 2015

Conversation

nagisa
Copy link
Member

@nagisa nagisa commented Apr 3, 2015

write_fmt calls write for each formatted field. The default implementation of write_fmt is used,
which will call write on not-yet-locked stdout (and write locking after), therefore making print!
in multithreaded environment still interleave contents of two separate prints.

I’m not sure whether we want to do this change, though, because it has the same deadlock hazard which we tried to avoid by not locking inside write_fmt itself (see this comment).

Spotted on reddit.

cc @alexcrichton

@rust-highfive
Copy link
Collaborator

r? @aturon

(rust_highfive has picked a reviewer for you, use r? to override)

@aturon
Copy link
Member

aturon commented Apr 3, 2015

I wonder if we should consider re-entrant locking.

@alexcrichton
Copy link
Member

Yes I think to do this we need to add reentrant locking, I consider accidental deadlocks to be worse than garbled output. I'd also prefer to override the write_fmt method on Stdio itself if we take this route as well.

@tanadeau
Copy link
Contributor

tanadeau commented Apr 3, 2015

Thanks for taking a look at this. I created the reddit discussion that @nagisa saw. I've been banging on the new std:io implementation thus seeing this issue and #23818.

@brson
Copy link
Contributor

brson commented Apr 4, 2015

It's reasonable to expect people to manage their own locks when they are racing for access to a stream, though it's got a lot less just-workitude. Garbled console output is what I expect from the multithreading gods. If Rust gets reentrant locks it would be backwards compatible to add one around the entire thing.

@nagisa nagisa changed the title Lock stdout for the whole duration of print Implement reentrant mutexes and make stdio use them Apr 4, 2015
@nagisa
Copy link
Member Author

nagisa commented Apr 4, 2015

Reentrant mutexes are here. A try build would be nice.

@emberian
Copy link
Member

emberian commented Apr 4, 2015

@bors: try

@bors
Copy link
Contributor

bors commented Apr 4, 2015

⌛ Trying commit e542f32 with merge 702b000...

@emberian
Copy link
Member

emberian commented Apr 4, 2015

@bors: try

bors added a commit that referenced this pull request Apr 4, 2015
write_fmt calls write for each formatted field. The default implementation of write_fmt is used,
which will call write on not-yet-locked stdout (and write locking after), therefore making print!
in multithreaded environment still interleave contents of two separate prints.

I’m not sure whether we want to do this change, though, because it has the same deadlock hazard which we tried to avoid by not locking inside write_fmt itself (see [this comment](https://github.com/rust-lang/rust/blob/80def6c2447d23a624e611417f24cf0ab2a5a676/src/libstd/io/stdio.rs#L267)).

Spotted on [reddit].

cc @alexcrichton 

[reddit]: http://www.reddit.com/r/rust/comments/31comh/println_with_multiple_threads/
@bors
Copy link
Contributor

bors commented Apr 4, 2015

⌛ Trying commit 17c7614 with merge 1721156...

@bors
Copy link
Contributor

bors commented Apr 4, 2015

💔 Test failed - try-mac

@emberian
Copy link
Member

emberian commented Apr 4, 2015

@bors: try

@bors
Copy link
Contributor

bors commented Apr 4, 2015

⌛ Trying commit 916ed81 with merge 2989a3d...

bors added a commit that referenced this pull request Apr 4, 2015
write_fmt calls write for each formatted field. The default implementation of write_fmt is used,
which will call write on not-yet-locked stdout (and write locking after), therefore making print!
in multithreaded environment still interleave contents of two separate prints.

I’m not sure whether we want to do this change, though, because it has the same deadlock hazard which we tried to avoid by not locking inside write_fmt itself (see [this comment](https://github.com/rust-lang/rust/blob/80def6c2447d23a624e611417f24cf0ab2a5a676/src/libstd/io/stdio.rs#L267)).

Spotted on [reddit].

cc @alexcrichton 

[reddit]: http://www.reddit.com/r/rust/comments/31comh/println_with_multiple_threads/
@bors
Copy link
Contributor

bors commented Apr 4, 2015

💔 Test failed - try-win-64

}

#[unstable(feature = "reentrant_mutex", reason = "new API")]
impl<'mutex, T> DerefMut for ReentrantMutexGuard<'mutex, T> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately I believe this is not sound to implement DerefMut for these guards. For example I believe that this is then allowed in safe code:

let a = ReentrantMutex::new(4);
let mut b = a.lock().unwrap();
let mut c = a.lock().unwrap();
let ptr1 = &mut *b;
let ptr2 = &mut *c;
// ptr1/ptr2 are mutable aliases

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh, this hurts a lot, since mutable references are exactly what most stdio methods want and alternatives I’ve considered so far:

  • with_mut_ref(&self, fucn: fn(&mut T) -> R) -> R;
  • as_mut_ptr(&self) -> *mut T;

are both insufficient. First one prevents aliasing reliably by disallowing to enclose over the environment, at the same time making the whole scheme as useless as it is safe; and the second one allows keeping pointer to the data after the lock expires.

I fear it might be not viable to have ReentrantMutex which protects data inside itself…

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that it basically means that to use a reentrant mutex safely you also need to likely have a RefCell in play somewhere. For example even with a function that has no environment you could still do something like creating a cycle of Arc pointers, so T holds a reference to the reentrant mutex allowing you to access it through the &mut T pointer the function pointer is given.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Err, indeed. I, for some reason, was sure panicking shouldn’t be done here, and dismissed RefCell without considering it enough. It sounds like a most plausible thing to do here.

@nagisa nagisa force-pushed the print-locking branch 2 times, most recently from f0d46f3 to a28e18f Compare April 6, 2015 17:18
pub use self::poison::{PoisonError, TryLockError, TryLockResult, LockResult};
pub use self::remutex::{ReentrantMutex, ReentrantMutexGuard};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After some chatting with @aturon, we think it may actually be best for now to not expose these publicly for now. Could you move the remutex.rs file to sys_common and use it from there instead?

@alexcrichton
Copy link
Member

Looking great to me! r=me after moving to sys_common and lifting the RefCell out of ReentrantMutex entirely

@nagisa nagisa force-pushed the print-locking branch 3 times, most recently from 55f19d0 to 48877bf Compare April 6, 2015 19:44
@nagisa
Copy link
Member Author

nagisa commented Apr 6, 2015

r+? @alexcrichton

@@ -67,3 +68,45 @@ impl Mutex {
debug_assert!(r == 0 || r == libc::EINVAL);
}
}

pub struct ReentrantMutex { inner: Box<UnsafeCell<ffi::pthread_mutex_t>> }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you leave a FIXME here to remove the Box at some point? Right now it unfortunately boxes twice (once at the common layer and once here as well). Fine for now, just want to make sure we don't forget to remove it at some point (these are supposed to be the lowest level abstractions).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While you're at it, could you also leave a comment about why it's boxed?

@alexcrichton
Copy link
Member

Looks great to me, thanks! Could you add a test as well? Something along the lines of:

  1. Spawn 2 threasd
  2. Print two messages with internal formatting
  3. Ensure the output is the two lines, possible interchanged.

@nagisa
Copy link
Member Author

nagisa commented Apr 7, 2015

Done.

EDiT: no it is not, didn’t notice the comment about FIXME

EDIT: Yes, done.

@alexcrichton
Copy link
Member

@bors: r+ 8e397b5

Thanks!

@bors
Copy link
Contributor

bors commented Apr 8, 2015

⌛ Testing commit 8e397b5 with merge 1bdcdf3...

@bors
Copy link
Contributor

bors commented Apr 8, 2015

💔 Test failed - auto-mac-32-opt

@alexcrichton
Copy link
Member

@bors: retry

um, what?

@bors
Copy link
Contributor

bors commented Apr 8, 2015

⌛ Testing commit 8e397b5 with merge a5e1d04...

@bors
Copy link
Contributor

bors commented Apr 8, 2015

💔 Test failed - auto-mac-32-opt

write_fmt calls write for each formatted field. The default implementation of write_fmt is used,
which will call write on not-yet-locked stdout (and write locking after), therefore making print!
in multithreaded environment still interleave contents of two separate prints.

This patch implements reentrant mutexes, changes stdio handles to use these mutexes and overrides
write_fmt to lock the stdio handle for the whole duration of the call.
@alexcrichton
Copy link
Member

@bors: r+ 45aa6c8

@bors
Copy link
Contributor

bors commented Apr 8, 2015

⌛ Testing commit 45aa6c8 with merge ff80477...

bors added a commit that referenced this pull request Apr 8, 2015
write_fmt calls write for each formatted field. The default implementation of write_fmt is used,
which will call write on not-yet-locked stdout (and write locking after), therefore making print!
in multithreaded environment still interleave contents of two separate prints.

I’m not sure whether we want to do this change, though, because it has the same deadlock hazard which we tried to avoid by not locking inside write_fmt itself (see [this comment](https://github.com/rust-lang/rust/blob/80def6c2447d23a624e611417f24cf0ab2a5a676/src/libstd/io/stdio.rs#L267)).

Spotted on [reddit].

cc @alexcrichton 

[reddit]: http://www.reddit.com/r/rust/comments/31comh/println_with_multiple_threads/
@bors bors merged commit 45aa6c8 into rust-lang:master Apr 8, 2015
@aldanor
Copy link

aldanor commented Jun 4, 2015

Great stuff! Are there any future plans re: stabilizing ReentrantMutex? Having this in std would be a big win.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.