Index: tests/concurrent/.expect/semaphore.txt
===================================================================
--- tests/concurrent/.expect/semaphore.txt	(revision de57af9e217ccb4f8e44b5ca36bdf77c6f0fdc58)
+++ tests/concurrent/.expect/semaphore.txt	(revision de57af9e217ccb4f8e44b5ca36bdf77c6f0fdc58)
@@ -0,0 +1,3 @@
+Starting
+Done!
+Match!
Index: tests/concurrent/semaphore.cfa
===================================================================
--- tests/concurrent/semaphore.cfa	(revision de57af9e217ccb4f8e44b5ca36bdf77c6f0fdc58)
+++ tests/concurrent/semaphore.cfa	(revision de57af9e217ccb4f8e44b5ca36bdf77c6f0fdc58)
@@ -0,0 +1,77 @@
+#include <fstream.hfa>
+#include <locks.hfa>
+#include <thread.hfa>
+
+enum { num_blockers = 17, num_unblockers = 13 };
+
+void thrash() {
+	unsigned t[100];
+	for(i; 100) {
+		t[i] = 0xDEADBEEF;
+	}
+}
+
+ThreadBenaphore ben;
+
+// const unsigned int num_blocks = 25000;
+const unsigned int num_blocks = 5;
+
+thread Blocker {
+	size_t sum;
+};
+
+void main(Blocker & this) {
+	$thread * me = active_thread();
+	this.sum = 0;
+	for(num_blocks) {
+		this.sum += (unsigned)me;
+		thrash();
+		P(ben);
+		if(((thread&)this).seqable.next != 0p) sout | acquire |"Link not invalidated";
+		thrash();
+	}
+}
+
+thread Unblocker {
+	size_t sum;
+};
+
+void main(Unblocker & this) {
+	this.sum = 0;
+	LOOP: for() {
+		waitfor( ^?{} : this) {
+			break LOOP;
+		}
+		or else {}
+
+		$thread * t = V(ben, false);
+		if(t) {
+			this.sum += (unsigned)t;
+			unpark(t);
+		}
+		yield(random(10));
+	}
+}
+
+int main() {
+	size_t usum = 0;
+	size_t bsum = 0;
+
+	sout | "Starting";
+	{
+		Blocker   blockers  [num_blockers  ];
+		Unblocker unblockers[num_unblockers];
+
+		for(i;num_blockers) {
+			bsum += join(blockers[i]).sum;
+		}
+
+		sout | "Done!";
+
+		for(i;num_unblockers) {
+			usum += join(unblockers[i]).sum;
+		}
+	}
+	if(bsum == usum) sout | "Match!";
+	else sout | "No Match!" | usum | "!=" | bsum;
+}
