@@ -549,14 +549,15 @@ const VerifyMultiJob = struct {
549549/// Persistent thread pool with preallocated buffers for parallel pairing verification.
550550/// Workers park on ResetEvents between jobs. All dispatch buffers are preallocated.
551551/// Scratch arrays for NAPI callers eliminate per-call allocs for n <= SCRATCH_MAX.
552- const PairingPool = struct {
552+ pub const PairingPool = struct {
553553 const max_workers = 256 ;
554554
555555 n_workers : u32 ,
556556 workers : []std.Thread ,
557557 work_items : []? WorkItem ,
558558 work_ready : []std.Thread.ResetEvent ,
559559 work_done : []std.Thread.ResetEvent ,
560+ shutdown : std .atomic .Value (bool ),
560561
561562 // Preallocated per-dispatch buffers (safe to reuse: dispatch is synchronous)
562563 pairing_bufs : [][Pairing .sizeOf ()]u8 ,
@@ -568,7 +569,7 @@ const PairingPool = struct {
568569 scratch_sig_ptrs : []* const Signature ,
569570 scratch_rands : [][32 ]u8 ,
570571
571- var instance : ? * PairingPool = null ;
572+ pub var instance : ? * PairingPool = null ;
572573
573574 fn get () * PairingPool {
574575 if (@as (* const ? * PairingPool , & instance ).* ) | p | return p ;
@@ -605,6 +606,7 @@ const PairingPool = struct {
605606 .work_items = work_items ,
606607 .work_ready = work_ready ,
607608 .work_done = work_done ,
609+ .shutdown = std .atomic .Value (bool ).init (false ),
608610 .pairing_bufs = allocator .alloc ([Pairing .sizeOf ()]u8 , n_workers ) catch @panic ("PairingPool: OOM" ),
609611 .has_work = allocator .alloc (bool , n_workers ) catch @panic ("PairingPool: OOM" ),
610612 .scratch_msg_ptrs = allocator .alloc (* const [32 ]u8 , SCRATCH_MAX ) catch @panic ("PairingPool: OOM" ),
@@ -616,7 +618,6 @@ const PairingPool = struct {
616618 // Workers get IDs 1..n; ID 0 is reserved for the calling (main) thread.
617619 for (0.. background_worker_count ) | i | {
618620 threads [i ] = std .Thread .spawn (.{}, workerLoop , .{ pool , i + 1 }) catch @panic ("PairingPool: spawn failed" );
619- threads [i ].detach ();
620621 }
621622
622623 instance = pool ;
@@ -628,6 +629,8 @@ const PairingPool = struct {
628629 pool .work_ready [worker_index ].wait ();
629630 pool .work_ready [worker_index ].reset ();
630631
632+ if (pool .shutdown .load (.acquire )) return ;
633+
631634 const item = pool .work_items [worker_index ] orelse {
632635 pool .work_done [worker_index ].set ();
633636 continue ;
@@ -850,6 +853,29 @@ const PairingPool = struct {
850853
851854 return acc .finalVerify (null );
852855 }
856+
857+ pub fn deinit (pool : * PairingPool ) void {
858+ pool .shutdown .store (true , .release );
859+
860+ // Wake all background workers so they observe the shutdown flag and exit.
861+ for (pool .work_ready [1.. pool .n_workers ]) | * e | e .set ();
862+
863+ // Join all background threads.
864+ for (pool .workers ) | t | t .join ();
865+
866+ allocator .free (pool .workers );
867+ allocator .free (pool .work_items );
868+ allocator .free (pool .work_ready );
869+ allocator .free (pool .work_done );
870+ allocator .free (pool .pairing_bufs );
871+ allocator .free (pool .has_work );
872+ allocator .free (pool .scratch_msg_ptrs );
873+ allocator .free (pool .scratch_pk_ptrs );
874+ allocator .free (pool .scratch_sig_ptrs );
875+ allocator .free (pool .scratch_rands );
876+ allocator .destroy (pool );
877+ instance = null ;
878+ }
853879};
854880
855881/// Verify an aggregated signature against multiple messages and multiple public keys.
0 commit comments