@@ -18,11 +18,15 @@ const std = @import("std");
18
18
const attributes = @import ("../../attributes.zig" );
19
19
const AttributeValue = attributes .AttributeValue ;
20
20
21
- /// Thread-safe compile- time key ID generator.
21
+ /// Compile- time key ID generator for creating unique IDs during compilation .
22
22
///
23
23
/// This structure encapsulates the compile-time counter state to prevent
24
24
/// type resolution cascades that can occur with bare global variables.
25
25
/// Each call to `next()` returns a unique ID starting from 0.
26
+ ///
27
+ /// Note: This is NOT thread-safe at runtime. It only works at compile-time
28
+ /// where there is no concurrency. This generator is only called from the
29
+ /// `Key()` function which executes at compile-time.
26
30
const ComptimeKeyGenerator = struct {
27
31
var next_id : usize = 0 ;
28
32
@@ -407,7 +411,7 @@ test "detach error handling" {
407
411
try std .testing .expect (try detachContext (token1 ));
408
412
}
409
413
410
- test "compile time key creation" {
414
+ test "key creation" {
411
415
const MyKey = Key ("my_service.request_id" );
412
416
const OtherKey = Key ("my_service.request_id" );
413
417
try std .testing .expect (MyKey .id != OtherKey .id );
@@ -428,3 +432,127 @@ test "context chaining" {
428
432
try std .testing .expectEqualStrings ("value1" , ctx1 .getValue (key1 ).? .string );
429
433
try std .testing .expect (ctx1 .getValue (key2 ) == null );
430
434
}
435
+
436
+ test "context thread isolation" {
437
+ // Verify that each thread has its own independent context stack
438
+ var thread_count = std .atomic .Value (u32 ).init (0 );
439
+
440
+ const threadWorker = struct {
441
+ fn run (counter : * std .atomic .Value (u32 )) void {
442
+ defer cleanup ();
443
+
444
+ // Each thread should start with an uninitialized context stack
445
+ if (context_stack == null ) {
446
+ _ = counter .fetchAdd (1 , .seq_cst );
447
+ }
448
+ }
449
+ }.run ;
450
+
451
+ // Spawn multiple threads to verify isolation
452
+ const t1 = try std .Thread .spawn (.{}, threadWorker , .{& thread_count });
453
+ const t2 = try std .Thread .spawn (.{}, threadWorker , .{& thread_count });
454
+ const t3 = try std .Thread .spawn (.{}, threadWorker , .{& thread_count });
455
+
456
+ t1 .join ();
457
+ t2 .join ();
458
+ t3 .join ();
459
+
460
+ // All threads should have seen null context_stack initially
461
+ try std .testing .expectEqual (@as (u32 , 3 ), thread_count .load (.seq_cst ));
462
+ }
463
+
464
+ test "context thread-local storage verification" {
465
+ // Verify that thread-local storage works correctly for context stacks
466
+ var success = std .atomic .Value (bool ).init (false );
467
+
468
+ const verifyThreadLocal = struct {
469
+ fn run (result : * std .atomic .Value (bool )) void {
470
+ // Verify this thread has its own context_stack variable
471
+ if (context_stack == null ) {
472
+ result .store (true , .seq_cst );
473
+ }
474
+ }
475
+ }.run ;
476
+
477
+ const thread = try std .Thread .spawn (.{}, verifyThreadLocal , .{& success });
478
+ thread .join ();
479
+
480
+ try std .testing .expect (success .load (.seq_cst ));
481
+ }
482
+
483
+ test "runtime key creation thread safety" {
484
+ // This test verifies that createKey() is thread-safe by having multiple
485
+ // threads create keys simultaneously and checking for uniqueness
486
+
487
+ const num_threads = 4 ;
488
+ const keys_per_thread = 100 ;
489
+
490
+ // Shared state for collecting results
491
+ const SharedData = struct {
492
+ keys : std .ArrayList (ContextKey ),
493
+ mutex : std.Thread.Mutex = .{},
494
+ barrier : std.Thread.ResetEvent = .{},
495
+ };
496
+
497
+ var shared = SharedData {
498
+ .keys = std .ArrayList (ContextKey ).init (std .testing .allocator ),
499
+ };
500
+ defer shared .keys .deinit ();
501
+
502
+ const keyGenWorker = struct {
503
+ fn run (data : * SharedData , thread_id : u32 ) void {
504
+ // Wait for all threads to start
505
+ data .barrier .wait ();
506
+
507
+ // Generate keys rapidly to stress test atomicity
508
+ var local_keys : [keys_per_thread ]ContextKey = undefined ;
509
+ for (0.. keys_per_thread ) | i | {
510
+ var name_buf : [64 ]u8 = undefined ;
511
+ const name = std .fmt .bufPrint (
512
+ & name_buf ,
513
+ "thread_{}_key_{}" ,
514
+ .{ thread_id , i },
515
+ ) catch unreachable ;
516
+ local_keys [i ] = createKey (name );
517
+ }
518
+
519
+ // Add to shared collection
520
+ data .mutex .lock ();
521
+ defer data .mutex .unlock ();
522
+ data .keys .appendSlice (& local_keys ) catch unreachable ;
523
+ }
524
+ }.run ;
525
+
526
+ // Spawn threads
527
+ var threads : [num_threads ]std.Thread = undefined ;
528
+ for (0.. num_threads ) | i | {
529
+ threads [i ] = try std .Thread .spawn (
530
+ .{},
531
+ keyGenWorker ,
532
+ .{ & shared , @as (u32 , @intCast (i )) },
533
+ );
534
+ }
535
+
536
+ // Start all threads simultaneously
537
+ shared .barrier .set ();
538
+
539
+ // Wait for completion
540
+ for (threads ) | thread | {
541
+ thread .join ();
542
+ }
543
+
544
+ // Verify we have the expected number of keys
545
+ try std .testing .expectEqual (
546
+ @as (usize , num_threads * keys_per_thread ),
547
+ shared .keys .items .len ,
548
+ );
549
+
550
+ // Verify all key IDs are unique
551
+ var seen = std .AutoHashMap (usize , void ).init (std .testing .allocator );
552
+ defer seen .deinit ();
553
+
554
+ for (shared .keys .items ) | key | {
555
+ const result = try seen .getOrPut (key .id );
556
+ try std .testing .expect (! result .found_existing );
557
+ }
558
+ }
0 commit comments