Add tests for pin()ing this thread or a different thread.
I attempted to capture some of the edge cases, such as pin() on a running,
thread, a sleeping thread, or a thread which is often in migrate-disable
state, pinning to the cpu a thread is already on, and re-pinning an already
pinned thread.

Signed-off-by: Nadav Har'El <[email protected]>
---
 tests/tst-pin.cc       | 198 +++++++++++++++++++++++++++++++++++++++++++++++++
 modules/tests/Makefile |   2 +-
 2 files changed, 199 insertions(+), 1 deletion(-)
 create mode 100644 tests/tst-pin.cc

diff --git a/tests/tst-pin.cc b/tests/tst-pin.cc
new file mode 100644
index 0000000..222b3cc
--- /dev/null
+++ b/tests/tst-pin.cc
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2016 Cloudius Systems, Ltd.
+ *
+ * This work is open source software, licensed under the terms of the
+ * BSD license as described in the LICENSE file in the top-level directory.
+ */
+
+#include <unistd.h>
+#include <errno.h>
+
+#include <iostream>
+
+#include <osv/sched.hh>
+#include <osv/condvar.h>
+
+static int tests = 0, fails = 0;
+
+#define expect(actual, expected) do_expect(actual, expected, #actual, 
#expected, __FILE__, __LINE__)
+template<typename T>
+bool do_expect(T actual, T expected, const char *actuals, const char 
*expecteds, const char *file, int line)
+{
+    ++tests;
+    if (actual != expected) {
+        fails++;
+        std::cout << "FAIL: " << file << ":" << line << ": For " << actuals <<
+                ", expected " << expecteds << ", saw " << actual << ".\n";
+        return false;
+    }
+    return true;
+}
+
+#define expect_errno_l(call, experrno) ( \
+        do_expect(call, -1L, #call, "-1", __FILE__, __LINE__) && \
+        do_expect(errno, experrno, #call " errno",  #experrno, __FILE__, 
__LINE__) )
+
+int main(int argc, char **argv)
+{
+    if (sched::cpus.size() < 2) {
+        // This test cannot be run on only one CPU, because we want to see
+        // the effect of pinning threads to different CPUs.
+        std::cout << "Cannot run this test with only one CPU.\n";
+        return 0;
+    }
+    // pin this thread to a specific CPU and check the current CPU is
+    // as expected (note that pin() only returns after the migration
+    // completed).
+    sched::thread::pin(sched::cpus[0]);
+    expect(sched::cpu::current(), sched::cpus[0]);
+    // re-pinning of this thread is allowed:
+    sched::thread::pin(sched::cpus[1]);
+    expect(sched::cpu::current(), sched::cpus[1]);
+    sched::thread::pin(sched::cpus[0]);
+    expect(sched::cpu::current(), sched::cpus[0]);
+
+
+    // Check that we can pin a different thread.
+    // In this test the thread will most likely be sleeping.
+    mutex m;
+    condvar c;
+    bool t_pinned = false;
+    sched::thread t([&] {
+        WITH_LOCK (m) {
+            while(!t_pinned) {
+                c.wait(m);
+            }
+        }
+        expect(sched::cpu::current(), sched::cpus[1]);
+    });
+    t.start();
+    sched::thread::pin(&t, sched::cpus[1]);
+    WITH_LOCK (m) {
+        t_pinned = true;
+        c.wake_all();
+    }
+    t.join();
+
+
+    // Similar test for pinning a different thread, but in this
+    // this test the thread will most likely be runnable, so we
+    // we will verify a different code path.
+    mutex m2;
+    condvar c2;
+    bool t2_pinned = false;
+    sched::thread t2([&] {
+        // Run in a tight loop to try to catch the case of trying to pin
+        // a runnable thread
+        auto now = osv::clock::uptime::now();
+        while (osv::clock::uptime::now() < now + 
std::chrono::milliseconds(100)) {
+            for (register int i = 0; i < 100000; i++) {
+                // To force gcc to not optimize this loop away
+                asm volatile("" : : : "memory");
+            }
+        }
+        WITH_LOCK (m2) {
+            while(!t2_pinned) {
+                c.wait(m2);
+            }
+        }
+        expect(sched::cpu::current(), sched::cpus[1]);
+    });
+    t2.start();
+    sched::thread::sleep(std::chrono::milliseconds(1));
+    sched::thread::pin(&t2, sched::cpus[1]);
+    WITH_LOCK (m2) {
+        t2_pinned = true;
+        c2.wake_all();
+    }
+    t2.join();
+
+
+    // Another similar test for pinning a different thread. In this
+    // this test the thread is in a tight uptime() loop. uptime() very
+    // frequently sets migrate_disable() temporarily - while getting a
+    // per-cpu variable - so we will often (but not always!) exercise
+    // here the code path of trying to migrate a non-migratable thread.
+    mutex m3;
+    condvar c3;
+    bool t3_pinned = false;
+    sched::thread t3([&] {
+        auto now = osv::clock::uptime::now();
+        while (osv::clock::uptime::now() < now + 
std::chrono::milliseconds(1000)) {
+        }
+        WITH_LOCK (m3) {
+            while(!t3_pinned) {
+                c.wait(m3);
+            }
+        }
+        expect(sched::cpu::current(), sched::cpus[1]);
+    });
+    t3.start();
+    sched::thread::sleep(std::chrono::milliseconds(1));
+    sched::thread::pin(&t3, sched::cpus[1]);
+    WITH_LOCK (m3) {
+        t3_pinned = true;
+        c3.wake_all();
+    }
+    t3.join();
+
+    // Test a bug we had of pinning a thread which was already on the
+    // given CPU. In that case, it stays there, but needs to become
+    // pinned - we can't just ignore the call.
+    mutex m4;
+    condvar c4;
+    bool t4_pinned = false;
+    sched::thread t4([&] {
+        WITH_LOCK (m4) {
+            while(!t4_pinned) {
+                c4.wait(m4);
+            }
+        }
+    });
+    t4.start();
+    sched::thread::sleep(std::chrono::milliseconds(1));
+    expect(t4.migratable(), true);
+    auto ccpu = t4.tcpu();
+    sched::thread::pin(&t4, ccpu);
+    expect(t4.migratable(), false);
+    expect(t4.tcpu(), ccpu);
+    WITH_LOCK (m4) {
+        t4_pinned = true;
+        c4.wake_all();
+    }
+    t4.join();
+
+    // Test pinning a thread several times in succession. It should work and
+    // not hang (the second call shouldn't wait until the first pinning is
+    // cancelled somehow).
+    mutex m5;
+    condvar c5;
+    bool t5_pinned = false;
+    sched::thread t5([&] {
+        WITH_LOCK (m5) {
+            while(!t5_pinned) {
+                c5.wait(m5);
+            }
+            expect(sched::cpu::current(), sched::cpus[1]);
+        }
+    });
+    t5.start();
+    sched::thread::sleep(std::chrono::milliseconds(1));
+    sched::thread::pin(&t5, sched::cpus[0]);
+    sched::thread::pin(&t5, sched::cpus[1]);
+    sched::thread::pin(&t5, sched::cpus[1]);
+    sched::thread::pin(&t5, sched::cpus[0]);
+    sched::thread::pin(&t5, sched::cpus[1]);
+    expect(t5.migratable(), false);
+    expect(t5.tcpu(), sched::cpus[1]);
+    WITH_LOCK (m5) {
+        t5_pinned = true;
+        c5.wake_all();
+    }
+    t5.join();
+
+
+
+    std::cout << "SUMMARY: " << tests << " tests, " << fails << " failures\n";
+    return fails == 0 ? 0 : 1;
+}
diff --git a/modules/tests/Makefile b/modules/tests/Makefile
index e40c0d1..771aaad 100644
--- a/modules/tests/Makefile
+++ b/modules/tests/Makefile
@@ -82,7 +82,7 @@ tests := tst-pthread.so misc-ramdisk.so tst-vblk.so 
tst-bsd-evh.so \
        tst-fstatat.so misc-reboot.so tst-fcntl.so payload-namespace.so \
        tst-namespace.so tst-without-namespace.so payload-env.so \
        payload-merge-env.so misc-execve.so misc-execve-payload.so 
misc-mutex2.so \
-       tst-pthread-setcancelstate.so tst-syscall.so
+       tst-pthread-setcancelstate.so tst-syscall.so tst-pin.so
 
 #      libstatic-thread-variable.so tst-static-thread-variable.so \
 
-- 
2.5.5

-- 
You received this message because you are subscribed to the Google Groups "OSv 
Development" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to