• getting back into Relacy...

    From Chris M. Thomasson@3:633/10 to All on Tue Oct 13 00:37:22 2026
    So, I have the most recent version and coded up my old multex algo. It
    100% dead lock free and allows a thread to locks multiple objects in any
    order and lock them. All of the lock ordering is taken care of in the
    finalize function. Basically a thread seeds its per thread lock indices
    with addresses to objects it wants to lock. Calls finalize, then locks
    them. Does its thing and then unlocks them. Fwiw, here is my Relacy test output:

    struct ct_multex_relacy_test_v000
    65% (65536/100000)
    iterations: 100000
    total time: 9390
    throughput: 10649


    D:\ct_dev\ct_msvc\projects\ct_relacy_test\x64\Debug\ct_relacy_test.exe (process 13356) exited with code 0 (0x0).
    Press any key to close this window . . .


    Any my code:

    #include <relacy/relacy.hpp>
    #include <cstdio>
    #include <cstddef>
    #include <cstdlib>
    #include <string>
    #include <vector>

    #if ! defined (NDEBUG)
    # define CT_DBG_PRINTF(e) std::printf e
    #else
    # define CT_DBG_PRINTF(e) ((void)0)
    #endif


    #define CT_THREADS (5)
    #define CT_MUTEX_TABLE_N (64)


    struct ct_mutex_table
    {
    rl::mutex m_mutexs[CT_MUTEX_TABLE_N];

    std::size_t
    hash_ptr(
    void* p
    ) const {
    return rl::hash_ptr(p, CT_MUTEX_TABLE_N);
    }
    };


    struct ct_thread_mutex_hash
    {
    std::vector<std::size_t> m_hashed_idxs;

    // must be before any locks!
    void finalize()
    {
    std::sort(
    m_hashed_idxs.begin(),
    m_hashed_idxs.end());

    m_hashed_idxs.erase(
    std::unique(
    m_hashed_idxs.begin(),
    m_hashed_idxs.end()),
    m_hashed_idxs.end());
    }


    // BEFORE WE CALL, WE NEED TO sort and dedup before this.
    void
    lock(
    ct_mutex_table& mutex_table
    ) {
    for (auto& mtx_idx : m_hashed_idxs)
    {
    rl::mutex& mtx = mutex_table.m_mutexs[mtx_idx];
    mtx.lock($);
    }
    }

    // does not really even need to be reversed...
    void
    unlock(
    ct_mutex_table& mutex_table
    ) {
    for (auto& mtx_idx : m_hashed_idxs)
    {
    rl::mutex& mtx = mutex_table.m_mutexs[mtx_idx];
    mtx.unlock($);
    }
    }
    };


    struct ct_state
    {
    ct_mutex_table m_mutex_table;
    VAR_T(int) m_var_0 = 0;
    VAR_T(int) m_var_1 = 1;
    VAR_T(int) m_var_2 = 2;


    void worker_thread_a()
    {
    ct_thread_mutex_hash self_hash;

    self_hash.m_hashed_idxs.push_back(
    m_mutex_table.hash_ptr(&m_var_0));

    self_hash.m_hashed_idxs.push_back(
    m_mutex_table.hash_ptr(&m_var_1));

    self_hash.finalize();

    self_hash.lock(m_mutex_table);

    VAR(m_var_0) = 42;
    VAR(m_var_1) = 128;

    self_hash.unlock(m_mutex_table);
    }

    void worker_thread_b()
    {
    ct_thread_mutex_hash self_hash;

    // Seed!
    {
    self_hash.m_hashed_idxs.push_back(
    m_mutex_table.hash_ptr(&m_var_1));

    self_hash.m_hashed_idxs.push_back(
    m_mutex_table.hash_ptr(&m_var_0));
    }


    // Okay... Finalize it...
    {
    self_hash.finalize();
    }


    // Finally, we can lock the bastard!
    {
    self_hash.lock(m_mutex_table);

    {
    VAR(m_var_0) = 99;
    VAR(m_var_1) = 77;
    }

    self_hash.unlock(m_mutex_table);
    }
    }
    };


    struct ct_multex_relacy_test_v000
    : rl::test_suite<ct_multex_relacy_test_v000, CT_THREADS>
    {
    ct_state m_state = { };

    void before()
    {
    // not sure to init m_state here. Is it already init?
    }

    void
    thread(
    unsigned tidx
    ) {
    if (tidx == 0)
    m_state.worker_thread_a();
    else
    m_state.worker_thread_b();
    }
    };


    int main(int argc, char** argv)
    {
    {
    rl::test_params p;

    p.iteration_count = argc > 1 ? (unsigned)atoi(argv[1]) : 100000;
    p.execution_depth_limit = 100000;

    if (argc > 2) {
    std::string m = argv[2];
    if (m == "full") p.search_type = rl::sched_full;
    else if (m == "bound") { p.search_type = rl::sched_bound; p.context_bound = argc > 3 ? atoi(argv[3]) : 2; }
    }

    rl::simulate<ct_multex_relacy_test_v000>(p);
    }

    return 0;
    }



    /*
    #include <relacy/relacy.hpp>
    #include <iostream>


    int
    main()
    {
    std::cout << "ct_relacy_test\n";
    std::cout << "____________________________________________" <<
    std::endl;

    {

    }

    std::cout << "____________________________________________" <<
    std::endl;
    std::cout << "ct_relacy_test fin! :^)\n";

    return 0;
    }

    */



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Tue Oct 13 00:38:39 2026
    On 10/13/2026 12:37 AM, Chris M. Thomasson wrote:
    So, I have the most recent version and coded up my old multex algo. It
    100% dead lock free and allows a thread to locks multiple objects in any order and lock them. All of the lock ordering is taken care of in the finalize function. Basically a thread seeds its per thread lock indices
    with addresses to objects it wants to lock. Calls finalize, then locks
    them. Does its thing and then unlocks them. Fwiw, here is my Relacy test output:

    struct ct_multex_relacy_test_v000
    65% (65536/100000)
    iterations: 100000
    total time: 9390
    throughput: 10649


    D:\ct_dev\ct_msvc\projects\ct_relacy_test\x64\Debug\ct_relacy_test.exe (process 13356) exited with code 0 (0x0).
    Press any key to close this window . . .


    Any my code:
    [...]

    Fwiw, header only Relacy can be found here:

    https://github.com/dvyukov/relacy




    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Tue Oct 13 13:05:41 2026
    Breaking the tests apart. one ct_main.cpp for the launcher, each test in
    its own personal environment:

    ct_main.cpp
    ____________________________________
    #include <relacy/relacy.hpp>


    // tests
    #include "ct_multex_test.hpp"


    #include <cstdio>
    #include <cstddef>
    #include <cstdlib>
    #include <string>
    #include <vector>


    int main(int argc, char** argv)
    {
    {
    rl::test_params p;

    p.iteration_count = argc > 1 ? (unsigned)atoi(argv[1]) : 100000;
    p.execution_depth_limit = 100000;

    if (argc > 2) {
    std::string m = argv[2];
    if (m == "full") p.search_type = rl::sched_full;
    else if (m == "bound") { p.search_type = rl::sched_bound; p.context_bound = argc > 3 ? atoi(argv[3]) : 2; }
    }

    {
    ct::multex_test::test::manifest(p);
    }
    }

    return 0;
    }



    /*
    #include <relacy/relacy.hpp>
    #include <iostream>


    int
    main()
    {
    std::cout << "ct_relacy_test\n";
    std::cout << "____________________________________________" <<
    std::endl;

    {

    }

    std::cout << "____________________________________________" <<
    std::endl;
    std::cout << "ct_relacy_test fin! :^)\n";

    return 0;
    }

    */


    ____________________________________




    ct_multex_test.hpp
    ____________________________________
    #pragma once

    #include <relacy/relacy.hpp>
    #include <cstdio>
    #include <cstddef>
    #include <cstdlib>
    #include <string>
    #include <vector>


    namespace ct
    {
    namespace multex_test
    {
    constexpr unsigned long CT_THREADS = 5;
    constexpr unsigned long CT_MUTEX_TABLE_N = 64;

    struct mutex_table
    {
    rl::mutex m_mutexs[CT_MUTEX_TABLE_N];

    std::size_t
    hash_ptr(
    void* p
    ) const {
    return rl::hash_ptr(p, CT_MUTEX_TABLE_N);
    }
    };


    struct thread_mutex_hash
    {
    std::vector<std::size_t> m_hashed_idxs;

    // must be before any locks!
    void finalize()
    {
    std::sort(
    m_hashed_idxs.begin(),
    m_hashed_idxs.end());

    m_hashed_idxs.erase(
    std::unique(
    m_hashed_idxs.begin(),
    m_hashed_idxs.end()),
    m_hashed_idxs.end());
    }


    // BEFORE WE CALL, WE NEED TO sort and dedup before this.
    void
    lock(
    mutex_table& mtx_table
    ) {
    for (auto& mtx_idx : m_hashed_idxs)
    {
    rl::mutex& mtx = mtx_table.m_mutexs[mtx_idx];
    mtx.lock($);
    }
    }

    // does not really even need to be reversed...
    void
    unlock(
    mutex_table& mtx_table
    ) {
    for (auto& mtx_idx : m_hashed_idxs)
    {
    rl::mutex& mtx = mtx_table.m_mutexs[mtx_idx];
    mtx.unlock($);
    }
    }
    };


    struct state
    {
    mutex_table m_mutex_table;

    VAR_T(int) m_var_0 = 0;
    VAR_T(int) m_var_1 = 1;
    VAR_T(int) m_var_2 = 2;


    void
    self_assert() const
    {
    int var_0 = VAR(m_var_0);
    int var_1 = VAR(m_var_1);
    int var_2 = VAR(m_var_2);

    RL_ASSERT((var_0 == 42) || (var_0 == 99) || (var_0 ==
    666));
    RL_ASSERT((var_1 == 128) || (var_1 == 77));
    RL_ASSERT((var_2 == 2) || (var_2 == -666));
    }


    void
    worker_thread_a()
    {
    thread_mutex_hash self_hash;

    self_hash.m_hashed_idxs.push_back(
    m_mutex_table.hash_ptr(&m_var_0));

    self_hash.m_hashed_idxs.push_back(
    m_mutex_table.hash_ptr(&m_var_1));

    self_hash.finalize();

    self_hash.lock(m_mutex_table);

    VAR(m_var_0) = 42;
    VAR(m_var_1) = 128;

    self_hash.unlock(m_mutex_table);
    }

    void
    worker_thread_b()
    {
    thread_mutex_hash self_hash;

    // Seed!
    {
    self_hash.m_hashed_idxs.push_back(
    m_mutex_table.hash_ptr(&m_var_1));

    self_hash.m_hashed_idxs.push_back(
    m_mutex_table.hash_ptr(&m_var_0));
    }


    // Okay... Finalize it...
    {
    self_hash.finalize();
    }


    // Finally, we can lock the bastard!
    {
    self_hash.lock(m_mutex_table);

    {
    VAR(m_var_0) = 99;
    VAR(m_var_1) = 77;
    }

    self_hash.unlock(m_mutex_table);
    }
    }


    void
    worker_thread_c()
    {
    thread_mutex_hash self_hash;

    // Seed!
    {
    self_hash.m_hashed_idxs.push_back(
    m_mutex_table.hash_ptr(&m_var_1));

    self_hash.m_hashed_idxs.push_back(
    m_mutex_table.hash_ptr(&m_var_0));
    }


    // Okay... Finalize it...
    {
    self_hash.finalize();
    }


    // Finally, we can lock the bastard!
    {
    self_hash.lock(m_mutex_table);

    {
    VAR(m_var_0) = 99;
    VAR(m_var_1) = 77;
    }

    self_hash.unlock(m_mutex_table);
    }

    self_hash.m_hashed_idxs.push_back(
    m_mutex_table.hash_ptr(&m_var_2));

    {
    self_hash.finalize();
    }

    {
    self_hash.lock(m_mutex_table);

    {
    VAR(m_var_0) = 666;
    VAR(m_var_2) = -666;
    }

    self_hash.unlock(m_mutex_table);
    }
    }
    };

    // the actual test unit...
    struct multex_relacy_test_v000
    : rl::test_suite<multex_relacy_test_v000, CT_THREADS>
    {
    state m_state = { };

    void before()
    {
    // not sure to init m_state here. Is it already init?
    }

    void after()
    {
    m_state.self_assert();
    }

    void
    thread(
    unsigned tidx
    ) {
    if (tidx == 0)
    m_state.worker_thread_a();
    else
    m_state.worker_thread_b();
    }
    };
    }
    }



    namespace ct
    {
    namespace multex_test
    {
    namespace test
    {
    inline void
    manifest(
    rl::test_params& p
    ) {
    rl::simulate<ct::multex_test::multex_relacy_test_v000>(p);
    }
    }
    }
    }
    ________________________________

    Works fine.


    struct ct::multex_test::multex_relacy_test_v000
    65% (65536/100000)
    iterations: 100000
    total time: 375
    throughput: 266666


    D:\ct_dev\ct_msvc\projects\ct_relacy_test\x64\Release\ct_relacy_test.exe (process 708) exited with code 0 (0x0).
    Press any key to close this window . . .


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)