about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/libcore/core.rc8
-rw-r--r--src/libcore/priv.rs145
-rw-r--r--src/rt/rust_builtin.cpp6
-rw-r--r--src/rt/rustrt.def.in1
4 files changed, 159 insertions, 1 deletions
diff --git a/src/libcore/core.rc b/src/libcore/core.rc
index 4a03247c642..c0d46a9fad5 100644
--- a/src/libcore/core.rc
+++ b/src/libcore/core.rc
@@ -37,6 +37,11 @@ export extfmt;
 export tuple;
 export to_str;
 
+// FIXME: This creates some APIs that I do not want to commit to. It is
+// currently exported for the uv code in std, but when that code moves into
+// core this should become unexported
+export priv;
+
 // Built-in-type support modules
 
 mod box;
@@ -61,7 +66,8 @@ mod bool;
 
 // For internal use by char, not exported
 mod unicode;
-
+// Do not export
+mod priv;
 
 // Ubiquitous-utility-type modules
 
diff --git a/src/libcore/priv.rs b/src/libcore/priv.rs
new file mode 100644
index 00000000000..ec432a69ef2
--- /dev/null
+++ b/src/libcore/priv.rs
@@ -0,0 +1,145 @@
+#[doc(hidden)];
+
+export chan_from_global_ptr;
+
+import compare_and_swap = rustrt::rust_compare_and_swap_ptr;
+
+native mod rustrt {
+    fn rust_compare_and_swap_ptr(address: *libc::uintptr_t,
+                                 oldval: libc::uintptr_t,
+                                 newval: libc::uintptr_t) -> bool;
+}
+
+type global_ptr<T: send> = *libc::uintptr_t;
+
+#[doc = "
+Atomically gets a channel from a pointer to a pointer-sized memory location
+or, if no channel exists creates and installs a new channel and sets up a new
+task to receive from it.
+"]
+unsafe fn chan_from_global_ptr<T: send>(
+    global: global_ptr<T>,
+    builder: fn() -> task::builder,
+    f: fn~(comm::port<T>)
+) -> comm::chan<T> {
+
+    enum msg {
+        proceed,
+        abort
+    }
+
+    let is_probably_zero = *global == 0u;
+    if is_probably_zero {
+        // There's no global channel. We must make it
+
+        let setup_po = comm::port();
+        let setup_ch = comm::chan(setup_po);
+        let setup_ch = task::run_listener(builder()) {|setup_po|
+            let po = comm::port::<T>();
+            let ch = comm::chan(po);
+            comm::send(setup_ch, ch);
+
+            // Wait to hear if we are the official instance of
+            // this global task
+            alt comm::recv::<msg>(setup_po) {
+              proceed { f(po); }
+              abort { }
+            }
+        };
+
+        // This is the proposed global channel
+        let ch = comm::recv(setup_po);
+        // 0 is our sentinal value. It is not a valid channel
+        assert unsafe::reinterpret_cast(ch) != 0u;
+
+        // Install the channel
+        let swapped = compare_and_swap(
+            global, 0u, unsafe::reinterpret_cast(ch));
+
+        if swapped {
+            // Success!
+            comm::send(setup_ch, proceed);
+            ch
+        } else {
+            // Somebody else got in before we did
+            comm::send(setup_ch, abort);
+            unsafe::reinterpret_cast(*global)
+        }
+    } else {
+        unsafe::reinterpret_cast(*global)
+    }
+}
+
+#[test]
+fn test_from_global_chan1() unsafe {
+
+    // This is unreadable, right?
+
+    // The global channel
+    let globchan = 0u;
+    let globchanp = ptr::addr_of(globchan);
+
+    // Create the global channel, attached to a new task
+    let ch = chan_from_global_ptr(globchanp, task::builder) {|po|
+        let ch = comm::recv(po);
+        comm::send(ch, true);
+        let ch = comm::recv(po);
+        comm::send(ch, true);
+    };
+    // Talk to it
+    let po = comm::port();
+    comm::send(ch, comm::chan(po));
+    assert comm::recv(po) == true;
+
+    // This one just reuses the previous channel
+    let ch = chan_from_global_ptr(globchanp, task::builder) {|po|
+        let ch = comm::recv(po);
+        comm::send(ch, false);
+    };
+
+    // Talk to the original global task
+    let po = comm::port();
+    comm::send(ch, comm::chan(po));
+    assert comm::recv(po) == true;
+}
+
+#[test]
+fn test_from_global_chan2() unsafe {
+
+    iter::repeat(100u) {||
+        // The global channel
+        let globchan = 0u;
+        let globchanp = ptr::addr_of(globchan);
+
+        let resultpo = comm::port();
+        let resultch = comm::chan(resultpo);
+
+        // Spawn a bunch of tasks that all want to compete to
+        // create the global channel
+        uint::range(0u, 10u) {|i|
+            task::spawn() {||
+                let ch = chan_from_global_ptr(
+                    globchanp, task::builder) {|po|
+
+                    uint::range(0u, 10u) {|_j|
+                        let ch = comm::recv(po);
+                        comm::send(ch, {i});
+                    }
+                };
+                let po = comm::port();
+                comm::send(ch, comm::chan(po));
+                // We are the winner if our version of the
+                // task was installed
+                let winner = comm::recv(po);
+                comm::send(resultch, winner == i);
+            }
+        }
+        // There should be only one winner
+        let mut winners = 0u;
+        uint::range(0u, 10u) {|_i|
+            let res = comm::recv(resultpo);
+            if res { winners += 1u };
+        }
+        assert winners == 1u;
+    }
+}
diff --git a/src/rt/rust_builtin.cpp b/src/rt/rust_builtin.cpp
index 9c2e2c3276c..910331fa7c0 100644
--- a/src/rt/rust_builtin.cpp
+++ b/src/rt/rust_builtin.cpp
@@ -776,6 +776,12 @@ rust_osmain_sched_id() {
     return task->kernel->osmain_sched_id();
 }
 
+extern "C" CDECL bool
+rust_compare_and_swap_ptr(intptr_t *address,
+                          intptr_t oldval, intptr_t newval) {
+    return sync::compare_and_swap(address, oldval, newval);
+}
+
 //
 // Local Variables:
 // mode: C++
diff --git a/src/rt/rustrt.def.in b/src/rt/rustrt.def.in
index e21edfbdd80..ec4f5bedb24 100644
--- a/src/rt/rustrt.def.in
+++ b/src/rt/rustrt.def.in
@@ -136,3 +136,4 @@ rust_dbg_lock_wait
 rust_dbg_lock_signal
 rust_dbg_call
 rust_osmain_sched_id
+rust_compare_and_swap_ptr
\ No newline at end of file