mirror of
https://github.com/rust-lang/rust.git
synced 2025-05-14 02:49:40 +00:00
Implement select() for new runtime pipes.
This commit is contained in:
parent
7326bc879e
commit
f34fadd126
@ -12,13 +12,13 @@
|
|||||||
|
|
||||||
use option::*;
|
use option::*;
|
||||||
use cast;
|
use cast;
|
||||||
use util;
|
|
||||||
use ops::Drop;
|
use ops::Drop;
|
||||||
use rt::kill::BlockedTask;
|
use rt::kill::BlockedTask;
|
||||||
use kinds::Send;
|
use kinds::Send;
|
||||||
use rt::sched::Scheduler;
|
use rt::sched::Scheduler;
|
||||||
use rt::local::Local;
|
use rt::local::Local;
|
||||||
use unstable::atomics::{AtomicUint, AtomicOption, Acquire, SeqCst};
|
use rt::select::{Select, SelectPort};
|
||||||
|
use unstable::atomics::{AtomicUint, AtomicOption, Acquire, Release, SeqCst};
|
||||||
use unstable::sync::UnsafeAtomicRcBox;
|
use unstable::sync::UnsafeAtomicRcBox;
|
||||||
use util::Void;
|
use util::Void;
|
||||||
use comm::{GenericChan, GenericSmartChan, GenericPort, Peekable};
|
use comm::{GenericChan, GenericSmartChan, GenericPort, Peekable};
|
||||||
@ -76,6 +76,7 @@ pub fn oneshot<T: Send>() -> (PortOne<T>, ChanOne<T>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ChanOne<T> {
|
impl<T> ChanOne<T> {
|
||||||
|
#[inline]
|
||||||
fn packet(&self) -> *mut Packet<T> {
|
fn packet(&self) -> *mut Packet<T> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let p: *mut ~Packet<T> = cast::transmute(&self.void_packet);
|
let p: *mut ~Packet<T> = cast::transmute(&self.void_packet);
|
||||||
@ -141,7 +142,6 @@ impl<T> ChanOne<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl<T> PortOne<T> {
|
impl<T> PortOne<T> {
|
||||||
fn packet(&self) -> *mut Packet<T> {
|
fn packet(&self) -> *mut Packet<T> {
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -162,46 +162,115 @@ impl<T> PortOne<T> {
|
|||||||
|
|
||||||
pub fn try_recv(self) -> Option<T> {
|
pub fn try_recv(self) -> Option<T> {
|
||||||
let mut this = self;
|
let mut this = self;
|
||||||
let packet = this.packet();
|
|
||||||
|
|
||||||
// Optimistic check. If data was sent already, we don't even need to block.
|
// Optimistic check. If data was sent already, we don't even need to block.
|
||||||
// No release barrier needed here; we're not handing off our task pointer yet.
|
// No release barrier needed here; we're not handing off our task pointer yet.
|
||||||
if unsafe { (*packet).state.load(Acquire) } != STATE_ONE {
|
if !this.optimistic_check() {
|
||||||
// No data available yet.
|
// No data available yet.
|
||||||
// Switch to the scheduler to put the ~Task into the Packet state.
|
// Switch to the scheduler to put the ~Task into the Packet state.
|
||||||
let sched = Local::take::<Scheduler>();
|
let sched = Local::take::<Scheduler>();
|
||||||
do sched.deschedule_running_task_and_then |sched, task| {
|
do sched.deschedule_running_task_and_then |sched, task| {
|
||||||
unsafe {
|
this.block_on(sched, task);
|
||||||
// Atomically swap the task pointer into the Packet state, issuing
|
|
||||||
// an acquire barrier to prevent reordering of the subsequent read
|
|
||||||
// of the payload. Also issues a release barrier to prevent
|
|
||||||
// reordering of any previous writes to the task structure.
|
|
||||||
let task_as_state = task.cast_to_uint();
|
|
||||||
let oldstate = (*packet).state.swap(task_as_state, SeqCst);
|
|
||||||
match oldstate {
|
|
||||||
STATE_BOTH => {
|
|
||||||
// Data has not been sent. Now we're blocked.
|
|
||||||
rtdebug!("non-rendezvous recv");
|
|
||||||
sched.metrics.non_rendezvous_recvs += 1;
|
|
||||||
}
|
|
||||||
STATE_ONE => {
|
|
||||||
rtdebug!("rendezvous recv");
|
|
||||||
sched.metrics.rendezvous_recvs += 1;
|
|
||||||
|
|
||||||
// Channel is closed. Switch back and check the data.
|
|
||||||
// NB: We have to drop back into the scheduler event loop here
|
|
||||||
// instead of switching immediately back or we could end up
|
|
||||||
// triggering infinite recursion on the scheduler's stack.
|
|
||||||
let recvr = BlockedTask::cast_from_uint(task_as_state);
|
|
||||||
sched.enqueue_blocked_task(recvr);
|
|
||||||
}
|
|
||||||
_ => util::unreachable()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Task resumes.
|
// Task resumes.
|
||||||
|
this.recv_ready()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Select for PortOne<T> {
|
||||||
|
#[inline]
|
||||||
|
fn optimistic_check(&mut self) -> bool {
|
||||||
|
unsafe { (*self.packet()).state.load(Acquire) == STATE_ONE }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_on(&mut self, sched: &mut Scheduler, task: BlockedTask) -> bool {
|
||||||
|
unsafe {
|
||||||
|
// Atomically swap the task pointer into the Packet state, issuing
|
||||||
|
// an acquire barrier to prevent reordering of the subsequent read
|
||||||
|
// of the payload. Also issues a release barrier to prevent
|
||||||
|
// reordering of any previous writes to the task structure.
|
||||||
|
let task_as_state = task.cast_to_uint();
|
||||||
|
let oldstate = (*self.packet()).state.swap(task_as_state, SeqCst);
|
||||||
|
match oldstate {
|
||||||
|
STATE_BOTH => {
|
||||||
|
// Data has not been sent. Now we're blocked.
|
||||||
|
rtdebug!("non-rendezvous recv");
|
||||||
|
sched.metrics.non_rendezvous_recvs += 1;
|
||||||
|
false
|
||||||
|
}
|
||||||
|
STATE_ONE => {
|
||||||
|
// Re-record that we are the only owner of the packet.
|
||||||
|
// Release barrier needed in case the task gets reawoken
|
||||||
|
// on a different core (this is analogous to writing a
|
||||||
|
// payload; a barrier in enqueueing the task protects it).
|
||||||
|
// NB(#8132). This *must* occur before the enqueue below.
|
||||||
|
// FIXME(#6842, #8130) This is usually only needed for the
|
||||||
|
// assertion in recv_ready, except in the case of select().
|
||||||
|
// This won't actually ever have cacheline contention, but
|
||||||
|
// maybe should be optimized out with a cfg(test) anyway?
|
||||||
|
(*self.packet()).state.store(STATE_ONE, Release);
|
||||||
|
|
||||||
|
rtdebug!("rendezvous recv");
|
||||||
|
sched.metrics.rendezvous_recvs += 1;
|
||||||
|
|
||||||
|
// Channel is closed. Switch back and check the data.
|
||||||
|
// NB: We have to drop back into the scheduler event loop here
|
||||||
|
// instead of switching immediately back or we could end up
|
||||||
|
// triggering infinite recursion on the scheduler's stack.
|
||||||
|
let recvr = BlockedTask::cast_from_uint(task_as_state);
|
||||||
|
sched.enqueue_blocked_task(recvr);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => rtabort!("can't block_on; a task is already blocked")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the only select trait function that's not also used in recv.
|
||||||
|
fn unblock_from(&mut self) -> bool {
|
||||||
|
let packet = self.packet();
|
||||||
|
unsafe {
|
||||||
|
// In case the data is available, the acquire barrier here matches
|
||||||
|
// the release barrier the sender used to release the payload.
|
||||||
|
match (*packet).state.load(Acquire) {
|
||||||
|
// Impossible. We removed STATE_BOTH when blocking on it, and
|
||||||
|
// no self-respecting sender would put it back.
|
||||||
|
STATE_BOTH => rtabort!("refcount already 2 in unblock_from"),
|
||||||
|
// Here, a sender already tried to wake us up. Perhaps they
|
||||||
|
// even succeeded! Data is available.
|
||||||
|
STATE_ONE => true,
|
||||||
|
// Still registered as blocked. Need to "unblock" the pointer.
|
||||||
|
task_as_state => {
|
||||||
|
// In the window between the load and the CAS, a sender
|
||||||
|
// might take the pointer and set the refcount to ONE. If
|
||||||
|
// that happens, we shouldn't clobber that with BOTH!
|
||||||
|
// Acquire barrier again for the same reason as above.
|
||||||
|
match (*packet).state.compare_and_swap(task_as_state, STATE_BOTH,
|
||||||
|
Acquire) {
|
||||||
|
STATE_BOTH => rtabort!("refcount became 2 in unblock_from"),
|
||||||
|
STATE_ONE => true, // Lost the race. Data available.
|
||||||
|
same_ptr => {
|
||||||
|
// We successfully unblocked our task pointer.
|
||||||
|
assert!(task_as_state == same_ptr);
|
||||||
|
let handle = BlockedTask::cast_from_uint(task_as_state);
|
||||||
|
// Because we are already awake, the handle we
|
||||||
|
// gave to this port shall already be empty.
|
||||||
|
handle.assert_already_awake();
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> SelectPort<T> for PortOne<T> {
|
||||||
|
fn recv_ready(self) -> Option<T> {
|
||||||
|
let mut this = self;
|
||||||
|
let packet = this.packet();
|
||||||
|
|
||||||
// No further memory barrier is needed here to access the
|
// No further memory barrier is needed here to access the
|
||||||
// payload. Some scenarios:
|
// payload. Some scenarios:
|
||||||
@ -213,8 +282,11 @@ impl<T> PortOne<T> {
|
|||||||
// 3) We encountered STATE_BOTH above and blocked, but the receiving task (this task)
|
// 3) We encountered STATE_BOTH above and blocked, but the receiving task (this task)
|
||||||
// is pinned to some other scheduler, so the sending task had to give us to
|
// is pinned to some other scheduler, so the sending task had to give us to
|
||||||
// a different scheduler for resuming. That send synchronized memory.
|
// a different scheduler for resuming. That send synchronized memory.
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
// See corresponding store() above in block_on for rationale.
|
||||||
|
// FIXME(#8130) This can happen only in test builds.
|
||||||
|
assert!((*packet).state.load(Acquire) == STATE_ONE);
|
||||||
|
|
||||||
let payload = (*packet).payload.take();
|
let payload = (*packet).payload.take();
|
||||||
|
|
||||||
// The sender has closed up shop. Drop the packet.
|
// The sender has closed up shop. Drop the packet.
|
||||||
@ -234,7 +306,7 @@ impl<T> Peekable<T> for PortOne<T> {
|
|||||||
match oldstate {
|
match oldstate {
|
||||||
STATE_BOTH => false,
|
STATE_BOTH => false,
|
||||||
STATE_ONE => (*packet).payload.is_some(),
|
STATE_ONE => (*packet).payload.is_some(),
|
||||||
_ => util::unreachable()
|
_ => rtabort!("peeked on a blocked task")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -368,6 +440,36 @@ impl<T> Peekable<T> for Port<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Select for Port<T> {
|
||||||
|
#[inline]
|
||||||
|
fn optimistic_check(&mut self) -> bool {
|
||||||
|
do self.next.with_mut_ref |pone| { pone.optimistic_check() }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn block_on(&mut self, sched: &mut Scheduler, task: BlockedTask) -> bool {
|
||||||
|
let task = Cell::new(task);
|
||||||
|
do self.next.with_mut_ref |pone| { pone.block_on(sched, task.take()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn unblock_from(&mut self) -> bool {
|
||||||
|
do self.next.with_mut_ref |pone| { pone.unblock_from() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> SelectPort<(T, Port<T>)> for Port<T> {
|
||||||
|
fn recv_ready(self) -> Option<(T, Port<T>)> {
|
||||||
|
match self.next.take().recv_ready() {
|
||||||
|
Some(StreamPayload { val, next }) => {
|
||||||
|
self.next.put_back(next);
|
||||||
|
Some((val, self))
|
||||||
|
}
|
||||||
|
None => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct SharedChan<T> {
|
pub struct SharedChan<T> {
|
||||||
// Just like Chan, but a shared AtomicOption instead of Cell
|
// Just like Chan, but a shared AtomicOption instead of Cell
|
||||||
priv next: UnsafeAtomicRcBox<AtomicOption<StreamChanOne<T>>>
|
priv next: UnsafeAtomicRcBox<AtomicOption<StreamChanOne<T>>>
|
||||||
|
@ -106,8 +106,14 @@ impl Drop for KillFlag {
|
|||||||
// blocked task handle. So unblocking a task must restore that spare.
|
// blocked task handle. So unblocking a task must restore that spare.
|
||||||
unsafe fn revive_task_ptr(task_ptr: uint, spare_flag: Option<KillFlagHandle>) -> ~Task {
|
unsafe fn revive_task_ptr(task_ptr: uint, spare_flag: Option<KillFlagHandle>) -> ~Task {
|
||||||
let mut task: ~Task = cast::transmute(task_ptr);
|
let mut task: ~Task = cast::transmute(task_ptr);
|
||||||
rtassert!(task.death.spare_kill_flag.is_none());
|
if task.death.spare_kill_flag.is_none() {
|
||||||
task.death.spare_kill_flag = spare_flag;
|
task.death.spare_kill_flag = spare_flag;
|
||||||
|
} else {
|
||||||
|
// A task's spare kill flag is not used for blocking in one case:
|
||||||
|
// when an unkillable task blocks on select. In this case, a separate
|
||||||
|
// one was created, which we now discard.
|
||||||
|
rtassert!(task.death.unkillable > 0);
|
||||||
|
}
|
||||||
task
|
task
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +125,7 @@ impl BlockedTask {
|
|||||||
Killable(flag_arc) => {
|
Killable(flag_arc) => {
|
||||||
let flag = unsafe { &mut **flag_arc.get() };
|
let flag = unsafe { &mut **flag_arc.get() };
|
||||||
match flag.swap(KILL_RUNNING, SeqCst) {
|
match flag.swap(KILL_RUNNING, SeqCst) {
|
||||||
KILL_RUNNING => rtabort!("tried to wake an already-running task"),
|
KILL_RUNNING => None, // woken from select(), perhaps
|
||||||
KILL_KILLED => None, // a killer stole it already
|
KILL_KILLED => None, // a killer stole it already
|
||||||
task_ptr =>
|
task_ptr =>
|
||||||
Some(unsafe { revive_task_ptr(task_ptr, Some(flag_arc)) })
|
Some(unsafe { revive_task_ptr(task_ptr, Some(flag_arc)) })
|
||||||
@ -162,6 +168,27 @@ impl BlockedTask {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts one blocked task handle to a list of many handles to the same.
|
||||||
|
pub fn make_selectable(self, num_handles: uint) -> ~[BlockedTask] {
|
||||||
|
let handles = match self {
|
||||||
|
Unkillable(task) => {
|
||||||
|
let flag = unsafe { KillFlag(AtomicUint::new(cast::transmute(task))) };
|
||||||
|
UnsafeAtomicRcBox::newN(flag, num_handles)
|
||||||
|
}
|
||||||
|
Killable(flag_arc) => flag_arc.cloneN(num_handles),
|
||||||
|
};
|
||||||
|
// Even if the task was unkillable before, we use 'Killable' because
|
||||||
|
// multiple pipes will have handles. It does not really mean killable.
|
||||||
|
handles.consume_iter().transform(|x| Killable(x)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// This assertion has two flavours because the wake involves an atomic op.
|
||||||
|
// In the faster version, destructors will fail dramatically instead.
|
||||||
|
#[inline] #[cfg(not(test))]
|
||||||
|
pub fn assert_already_awake(self) { }
|
||||||
|
#[inline] #[cfg(test)]
|
||||||
|
pub fn assert_already_awake(self) { assert!(self.wake().is_none()); }
|
||||||
|
|
||||||
/// Convert to an unsafe uint value. Useful for storing in a pipe's state flag.
|
/// Convert to an unsafe uint value. Useful for storing in a pipe's state flag.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub unsafe fn cast_to_uint(self) -> uint {
|
pub unsafe fn cast_to_uint(self) -> uint {
|
||||||
|
@ -142,6 +142,9 @@ pub mod tube;
|
|||||||
/// Simple reimplementation of core::comm
|
/// Simple reimplementation of core::comm
|
||||||
pub mod comm;
|
pub mod comm;
|
||||||
|
|
||||||
|
/// Routines for select()ing on pipes.
|
||||||
|
pub mod select;
|
||||||
|
|
||||||
// FIXME #5248 shouldn't be pub
|
// FIXME #5248 shouldn't be pub
|
||||||
/// The runtime needs to be able to put a pointer into thread-local storage.
|
/// The runtime needs to be able to put a pointer into thread-local storage.
|
||||||
pub mod local_ptr;
|
pub mod local_ptr;
|
||||||
|
102
src/libstd/rt/select.rs
Normal file
102
src/libstd/rt/select.rs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file at the top-level directory of this distribution and at
|
||||||
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
use option::*;
|
||||||
|
// use either::{Either, Left, Right};
|
||||||
|
use rt::kill::BlockedTask;
|
||||||
|
use rt::sched::Scheduler;
|
||||||
|
use rt::local::Local;
|
||||||
|
|
||||||
|
/// Trait for message-passing primitives that can be select()ed on.
|
||||||
|
pub trait Select {
|
||||||
|
// Returns true if data was available.
|
||||||
|
fn optimistic_check(&mut self) -> bool;
|
||||||
|
// Returns true if data was available. If so, shall also wake() the task.
|
||||||
|
fn block_on(&mut self, &mut Scheduler, BlockedTask) -> bool;
|
||||||
|
// Returns true if data was available.
|
||||||
|
fn unblock_from(&mut self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for message-passing primitives that can use the select2() convenience wrapper.
|
||||||
|
// (This is separate from the above trait to enable heterogeneous lists of ports
|
||||||
|
// that implement Select on different types to use select().)
|
||||||
|
pub trait SelectPort<T> : Select {
|
||||||
|
fn recv_ready(self) -> Option<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receive a message from any one of many ports at once.
|
||||||
|
pub fn select<A: Select>(ports: &mut [A]) -> uint {
|
||||||
|
if ports.is_empty() {
|
||||||
|
fail!("can't select on an empty list");
|
||||||
|
}
|
||||||
|
|
||||||
|
for ports.mut_iter().enumerate().advance |(index, port)| {
|
||||||
|
if port.optimistic_check() {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If one of the ports already contains data when we go to block on it, we
|
||||||
|
// don't bother enqueueing on the rest of them, so we shouldn't bother
|
||||||
|
// unblocking from it either. This is just for efficiency, not correctness.
|
||||||
|
// (If not, we need to unblock from all of them. Length is a placeholder.)
|
||||||
|
let mut ready_index = ports.len();
|
||||||
|
|
||||||
|
let sched = Local::take::<Scheduler>();
|
||||||
|
do sched.deschedule_running_task_and_then |sched, task| {
|
||||||
|
let task_handles = task.make_selectable(ports.len());
|
||||||
|
|
||||||
|
for ports.mut_iter().zip(task_handles.consume_iter()).enumerate().advance
|
||||||
|
|(index, (port, task_handle))| {
|
||||||
|
// If one of the ports has data by now, it will wake the handle.
|
||||||
|
if port.block_on(sched, task_handle) {
|
||||||
|
ready_index = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Task resumes. Now unblock ourselves from all the ports we blocked on.
|
||||||
|
// If the success index wasn't reset, 'take' will just take all of them.
|
||||||
|
// Iterate in reverse so the 'earliest' index that's ready gets returned.
|
||||||
|
for ports.mut_slice(0, ready_index).mut_rev_iter().enumerate().advance |(index, port)| {
|
||||||
|
if port.unblock_from() {
|
||||||
|
ready_index = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(ready_index < ports.len());
|
||||||
|
return ready_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FIXME(#5121, #7914) This all should be legal, but rust is not clever enough yet.
|
||||||
|
|
||||||
|
impl <'self> Select for &'self mut Select {
|
||||||
|
fn optimistic_check(&mut self) -> bool { self.optimistic_check() }
|
||||||
|
fn block_on(&mut self, sched: &mut Scheduler, task: BlockedTask) -> bool {
|
||||||
|
self.block_on(sched, task)
|
||||||
|
}
|
||||||
|
fn unblock_from(&mut self) -> bool { self.unblock_from() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select2<TA, A: SelectPort<TA>, TB, B: SelectPort<TB>>(mut a: A, mut b: B)
|
||||||
|
-> Either<(Option<TA>, B), (A, Option<TB>)> {
|
||||||
|
let result = {
|
||||||
|
let mut ports = [&mut a as &mut Select, &mut b as &mut Select];
|
||||||
|
select(ports)
|
||||||
|
};
|
||||||
|
match result {
|
||||||
|
0 => Left ((a.recv_ready(), b)),
|
||||||
|
1 => Right((a, b.recv_ready())),
|
||||||
|
x => fail!("impossible case in select2: %?", x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
@ -22,6 +22,7 @@ use unstable::finally::Finally;
|
|||||||
use ops::Drop;
|
use ops::Drop;
|
||||||
use clone::Clone;
|
use clone::Clone;
|
||||||
use kinds::Send;
|
use kinds::Send;
|
||||||
|
use vec;
|
||||||
|
|
||||||
/// An atomically reference counted pointer.
|
/// An atomically reference counted pointer.
|
||||||
///
|
///
|
||||||
@ -41,26 +42,53 @@ struct AtomicRcBoxData<T> {
|
|||||||
data: Option<T>,
|
data: Option<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe fn new_inner<T: Send>(data: T, refcount: uint) -> *mut libc::c_void {
|
||||||
|
let data = ~AtomicRcBoxData { count: AtomicUint::new(refcount),
|
||||||
|
unwrapper: AtomicOption::empty(),
|
||||||
|
data: Some(data) };
|
||||||
|
cast::transmute(data)
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Send> UnsafeAtomicRcBox<T> {
|
impl<T: Send> UnsafeAtomicRcBox<T> {
|
||||||
pub fn new(data: T) -> UnsafeAtomicRcBox<T> {
|
pub fn new(data: T) -> UnsafeAtomicRcBox<T> {
|
||||||
unsafe {
|
unsafe { UnsafeAtomicRcBox { data: new_inner(data, 1) } }
|
||||||
let data = ~AtomicRcBoxData { count: AtomicUint::new(1),
|
|
||||||
unwrapper: AtomicOption::empty(),
|
|
||||||
data: Some(data) };
|
|
||||||
let ptr = cast::transmute(data);
|
|
||||||
return UnsafeAtomicRcBox { data: ptr };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// As new(), but returns an extra pre-cloned handle.
|
/// As new(), but returns an extra pre-cloned handle.
|
||||||
pub fn new2(data: T) -> (UnsafeAtomicRcBox<T>, UnsafeAtomicRcBox<T>) {
|
pub fn new2(data: T) -> (UnsafeAtomicRcBox<T>, UnsafeAtomicRcBox<T>) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let data = ~AtomicRcBoxData { count: AtomicUint::new(2),
|
let ptr = new_inner(data, 2);
|
||||||
unwrapper: AtomicOption::empty(),
|
(UnsafeAtomicRcBox { data: ptr }, UnsafeAtomicRcBox { data: ptr })
|
||||||
data: Some(data) };
|
}
|
||||||
let ptr = cast::transmute(data);
|
}
|
||||||
return (UnsafeAtomicRcBox { data: ptr },
|
|
||||||
UnsafeAtomicRcBox { data: ptr });
|
/// As new(), but returns a vector of as many pre-cloned handles as requested.
|
||||||
|
pub fn newN(data: T, num_handles: uint) -> ~[UnsafeAtomicRcBox<T>] {
|
||||||
|
unsafe {
|
||||||
|
if num_handles == 0 {
|
||||||
|
~[] // need to free data here
|
||||||
|
} else {
|
||||||
|
let ptr = new_inner(data, num_handles);
|
||||||
|
vec::from_fn(num_handles, |_| UnsafeAtomicRcBox { data: ptr })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// As newN(), but from an already-existing handle. Uses one xadd.
|
||||||
|
pub fn cloneN(self, num_handles: uint) -> ~[UnsafeAtomicRcBox<T>] {
|
||||||
|
if num_handles == 0 {
|
||||||
|
~[] // The "num_handles - 1" trick (below) fails in the 0 case.
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
let mut data: ~AtomicRcBoxData<T> = cast::transmute(self.data);
|
||||||
|
// Minus one because we are recycling the given handle's refcount.
|
||||||
|
let old_count = data.count.fetch_add(num_handles - 1, Acquire);
|
||||||
|
// let old_count = data.count.fetch_add(num_handles, Acquire);
|
||||||
|
assert!(old_count >= 1);
|
||||||
|
let ptr = cast::transmute(data);
|
||||||
|
cast::forget(self); // Don't run the destructor on this handle.
|
||||||
|
vec::from_fn(num_handles, |_| UnsafeAtomicRcBox { data: ptr })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user