diff options
Diffstat (limited to 'eval/src/tests/eval/compile_cache/compile_cache_test.cpp')
-rw-r--r-- | eval/src/tests/eval/compile_cache/compile_cache_test.cpp | 80 |
1 files changed, 60 insertions, 20 deletions
diff --git a/eval/src/tests/eval/compile_cache/compile_cache_test.cpp b/eval/src/tests/eval/compile_cache/compile_cache_test.cpp index 1de56e605c9..a0dad889d9a 100644 --- a/eval/src/tests/eval/compile_cache/compile_cache_test.cpp +++ b/eval/src/tests/eval/compile_cache/compile_cache_test.cpp @@ -5,12 +5,16 @@ #include <vespa/eval/eval/test/eval_spec.h> #include <vespa/vespalib/util/time.h> #include <vespa/vespalib/util/threadstackexecutor.h> +#include <vespa/vespalib/util/blockingthreadstackexecutor.h> +#include <vespa/vespalib/util/stringfmt.h> #include <thread> #include <set> using namespace vespalib; using namespace vespalib::eval; +using vespalib::make_string_short::fmt; + struct MyExecutor : public Executor { std::vector<Executor::Task::UP> tasks; Executor::Task::UP execute(Executor::Task::UP task) override { @@ -157,7 +161,7 @@ TEST("require that cache usage works") { } TEST("require that async cache usage works") { - ThreadStackExecutor executor(8, 256*1024); + auto executor = std::make_shared<ThreadStackExecutor>(8, 256*1024); auto binding = CompileCache::bind(executor); CompileCache::Token::UP token_a = CompileCache::compile(*Function::parse("x+y"), PassParams::SEPARATE); EXPECT_EQUAL(5.0, token_a->get().get_function<2>()(2.0, 3.0)); @@ -166,7 +170,6 @@ TEST("require that async cache usage works") { CompileCache::Token::UP token_c = CompileCache::compile(*Function::parse("x+y"), PassParams::SEPARATE); EXPECT_EQUAL(5.0, token_c->get().get_function<2>()(2.0, 3.0)); EXPECT_EQUAL(CompileCache::num_cached(), 2u); - executor.sync(); // wait for compile threads to drop all compile cache tokens token_a.reset(); TEST_DO(verify_cache(2, 2)); token_b.reset(); @@ -176,24 +179,24 @@ TEST("require that async cache usage works") { } TEST("require that compile tasks are run in the most recently bound executor") { - MyExecutor exe1; - MyExecutor exe2; + auto exe1 = std::make_shared<MyExecutor>(); + auto exe2 = std::make_shared<MyExecutor>(); auto token0 = CompileCache::compile(*Function::parse("a+b"), PassParams::SEPARATE); EXPECT_EQUAL(CompileCache::num_bound(), 0u); - EXPECT_EQUAL(exe1.tasks.size(), 0u); - EXPECT_EQUAL(exe2.tasks.size(), 0u); + EXPECT_EQUAL(exe1->tasks.size(), 0u); + EXPECT_EQUAL(exe2->tasks.size(), 0u); { auto bind1 = CompileCache::bind(exe1); auto token1 = CompileCache::compile(*Function::parse("a-b"), PassParams::SEPARATE); EXPECT_EQUAL(CompileCache::num_bound(), 1u); - EXPECT_EQUAL(exe1.tasks.size(), 1u); - EXPECT_EQUAL(exe2.tasks.size(), 0u); + EXPECT_EQUAL(exe1->tasks.size(), 1u); + EXPECT_EQUAL(exe2->tasks.size(), 0u); { auto bind2 = CompileCache::bind(exe2); auto token2 = CompileCache::compile(*Function::parse("a*b"), PassParams::SEPARATE); EXPECT_EQUAL(CompileCache::num_bound(), 2u); - EXPECT_EQUAL(exe1.tasks.size(), 1u); - EXPECT_EQUAL(exe2.tasks.size(), 1u); + EXPECT_EQUAL(exe1->tasks.size(), 1u); + EXPECT_EQUAL(exe2->tasks.size(), 1u); } EXPECT_EQUAL(CompileCache::num_bound(), 1u); } @@ -201,9 +204,9 @@ TEST("require that compile tasks are run in the most recently bound executor") { } TEST("require that executors may be unbound in any order") { - MyExecutor exe1; - MyExecutor exe2; - MyExecutor exe3; + auto exe1 = std::make_shared<MyExecutor>(); + auto exe2 = std::make_shared<MyExecutor>(); + auto exe3 = std::make_shared<MyExecutor>(); auto bind1 = CompileCache::bind(exe1); auto bind2 = CompileCache::bind(exe2); auto bind3 = CompileCache::bind(exe3); @@ -213,13 +216,13 @@ TEST("require that executors may be unbound in any order") { bind3.reset(); EXPECT_EQUAL(CompileCache::num_bound(), 1u); auto token = CompileCache::compile(*Function::parse("a+b"), PassParams::SEPARATE); - EXPECT_EQUAL(exe1.tasks.size(), 1u); - EXPECT_EQUAL(exe2.tasks.size(), 0u); - EXPECT_EQUAL(exe3.tasks.size(), 0u); + EXPECT_EQUAL(exe1->tasks.size(), 1u); + EXPECT_EQUAL(exe2->tasks.size(), 0u); + EXPECT_EQUAL(exe3->tasks.size(), 0u); } TEST("require that the same executor can be bound multiple times") { - MyExecutor exe1; + auto exe1 = std::make_shared<MyExecutor>(); auto bind1 = CompileCache::bind(exe1); auto bind2 = CompileCache::bind(exe1); auto bind3 = CompileCache::bind(exe1); @@ -230,7 +233,7 @@ TEST("require that the same executor can be bound multiple times") { EXPECT_EQUAL(CompileCache::num_bound(), 1u); auto token = CompileCache::compile(*Function::parse("a+b"), PassParams::SEPARATE); EXPECT_EQUAL(CompileCache::num_bound(), 1u); - EXPECT_EQUAL(exe1.tasks.size(), 1u); + EXPECT_EQUAL(exe1->tasks.size(), 1u); } struct CompileCheck : test::EvalSpec::EvalTest { @@ -286,9 +289,9 @@ TEST_F("compile sequentially, then run all conformance tests", test::EvalSpec()) TEST_F("compile concurrently (8 threads), then run all conformance tests", test::EvalSpec()) { f1.add_all_cases(); - ThreadStackExecutor executor(8, 256*1024); + auto executor = std::make_shared<ThreadStackExecutor>(8, 256*1024); auto binding = CompileCache::bind(executor); - while (executor.num_idle_workers() < 8) { + while (executor->num_idle_workers() < 8) { std::this_thread::sleep_for(1ms); } for (size_t i = 0; i < 2; ++i) { @@ -305,6 +308,43 @@ TEST_F("compile concurrently (8 threads), then run all conformance tests", test: } } +struct MyCompileTask : public Executor::Task { + size_t seed; + size_t loop; + MyCompileTask(size_t seed_in, size_t loop_in) : seed(seed_in), loop(loop_in) {} + void run() override { + for (size_t i = 0; i < loop; ++i) { + // use custom constant to make a unique function that needs compilation + auto token = CompileCache::compile(*Function::parse(fmt("%zu", seed + i)), PassParams::SEPARATE); + } + } +}; + +TEST_MT_FF("require that deadlock is avoided with blocking executor", 8, std::shared_ptr<Executor>(nullptr), TimeBomb(300)) { + size_t loop = 16; + if (thread_id == 0) { + auto t0 = steady_clock::now(); + f1 = std::make_shared<BlockingThreadStackExecutor>(2, 256*1024, 3); + auto binding = CompileCache::bind(f1); + TEST_BARRIER(); // #1 + for (size_t i = 0; i < num_threads; ++i) { + f1->execute(std::make_unique<MyCompileTask>(i * loop, loop)); + } + TEST_BARRIER(); // #2 + auto t1 = steady_clock::now(); + fprintf(stderr, "deadlock test took %" PRIu64 " ms\n", count_ms(t1 - t0)); + + } else { + TEST_BARRIER(); // #1 + size_t seed = (10000 + (thread_id * loop)); + for (size_t i = 0; i < loop; ++i) { + // use custom constant to make a unique function that needs compilation + auto token = CompileCache::compile(*Function::parse(fmt("%zu", seed + i)), PassParams::SEPARATE); + } + TEST_BARRIER(); // #2 + } +} + //----------------------------------------------------------------------------- TEST_MAIN() { TEST_RUN_ALL(); } |