Affix

 view release on metacpan or  search on metacpan

t/027_thread_safety.t  view on Meta::CPAN

use v5.40;
use lib '../lib', 'lib';
use blib;
use Test2::Tools::Affix qw[:all];
use Affix               qw[:all];
use Config;
#
$|++;
#
subtest 'os threads' => sub {
    typedef Callback_t => Callback [ [Int] => Void ];
    typedef ThreadTask => Struct [ cb   => Callback_t(), val  => Int ];
    typedef TaskNode   => Struct [ task => ThreadTask(), next => Pointer [ ThreadTask() ] ];
    typedef TaskQueue =>
        Struct [ head => Pointer [ TaskNode() ], tail => Pointer [ TaskNode() ], lock => Pointer [Void], cond => Pointer [Void], stop => Int ];
    typedef ThreadPool => Struct [
        threads         => Pointer [Void],
        thread_count    => Int,
        task_queue      => TaskQueue(),
        pool_lock       => Pointer [Void],
        task_available  => Pointer [Void],
        active_threads  => Int,
        tasks_remaining => Int
    ];
    my $c_source = <<~'C';
        #include "std.h"
        //ext: .c
        #if _WIN32
        #include <windows.h>
        #else
        #include <pthread.h>
        #include <unistd.h>
        #endif
        #include <stdlib.h>

        typedef void (*callback_t)(int);

        typedef struct {
            callback_t cb;
            int val;
        } ThreadArgs;

        #if _WIN32
        unsigned __stdcall
        #else
        void *
        #endif
        thread_func(void* arg) {
            ThreadArgs* args = (ThreadArgs*)arg;
            // Brief sleep to ensure we aren't just getting lucky on the main thread stack
            usleep(2000000);
            // Execute Perl callback from this foreign thread
            // This will SEGFAULT if Affix doesn't inject Perl context!
            args->cb(args->val);
            return 0;
        }

        void run_in_foreign_thread(callback_t cb, int val) {
        #if _WIN32
            ThreadArgs* args = (ThreadArgs*)malloc(sizeof(ThreadArgs));
            args->cb = cb;
            args->val = val;
            unsigned threadID;
            HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, &thread_func, args, 0, &threadID);
            // Block main thread to simulate WebUI/MainLoop behavior
            WaitForSingleObject(hThread, INFINITE);
            CloseHandle(hThread);
            free(args);
        #else
            pthread_t thread_id;
            ThreadArgs* args = (ThreadArgs*)malloc(sizeof(ThreadArgs));
            args->cb = cb;
            args->val = val;
            pthread_create(&thread_id, NULL, thread_func, args);
            // Block main thread to simulate WebUI/MainLoop behavior
            pthread_join(thread_id, NULL);
            free(args);
        #endif
        }
    C

    # Pass reference (\$c_source) so Affix::Build treats it as code, not a filename
    my $lib = compile_ok($c_source);
    ok $lib && -e $lib, 'Compiled a threaded test library';
    #
    ok affix( $lib, 'run_in_foreign_thread', [ Callback_t(), Int ] => Void ), 'affix run_in_foreign_thread';
    #
    my $ok_flag = 0;
    my $str;

    # This calls C, which spawns a thread, which calls this sub
    run_in_foreign_thread(
        sub ($val) {

            # Allocating memory here (creating SVs) tests the memory allocator context
            $str     = 'Received: ' . $val;
            $ok_flag = $val;
        },
        123
    );
    is $ok_flag, 123, 'Callback executed successfully from foreign thread without crashing';
    diag $str;
};
#
subtest ithreads => sub {
    skip_all 'No ithreads', 1 unless $Config{useithreads};
    require threads;

    # Test core affix cloning and usage across threads
    ok affix libc(), [ abs => 'absolute' ], [Int] => Int;
    is absolute(-42), 42, 'Main thread: absolute(-42) == 42';
    my @threads = map {
        threads->create(
            sub {
                my $tid = threads->tid();
                my $res = absolute( -100 * $tid );
                return $res == ( 100 * $tid );
            }
        )
    } 1 .. 5;
    for my $thr (@threads) {
        my $tid = $thr->tid();
        ok $thr->join(), "Thread $tid: absolute() worked correctly after cloning";
    }
};
#
done_testing();



( run in 2.219 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )