@@ -9,6 +9,7 @@ package deserializer
99import (
1010 "context"
1111 "errors"
12+ "sync"
1213 "testing"
1314
1415 "github.com/LFDT-Panurus/panurus/token/driver"
@@ -880,3 +881,80 @@ func TestTypedIdentityVerifierDeserializer(t *testing.T) {
880881 assert .Equal (t , 1 , mockMatcherDeserializer .GetAuditInfoMatcherCallCount ())
881882 })
882883}
884+
885+ // TestDeserializerMultiplexConcurrency exercises the three deserializer
886+ // multiplexes under concurrent registration (map writes) and deserialization
887+ // (map reads). Without the internal mutex this reliably trips the race
888+ // detector or the runtime's fatal "concurrent map read and map write" check;
889+ // with it, the test runs clean. Run under `go test -race` for full coverage.
890+ func TestDeserializerMultiplexConcurrency (t * testing.T ) {
891+ const (
892+ goroutines = 16
893+ iterations = 200
894+ )
895+
896+ signerMultiplex := NewTypedSignerDeserializerMultiplex ()
897+ verifierMultiplex := NewTypedVerifierDeserializerMultiplex ()
898+ eidrhDeserializer := NewEIDRHDeserializer ()
899+
900+ // Pre-register a deserializer for the type the readers look up so the read
901+ // paths traverse the stored slice rather than bailing out on a miss.
902+ mockSigner := & drivermock.Signer {}
903+ signerDes := & identitydrivermock.TypedSignerDeserializer {}
904+ signerDes .DeserializeSignerReturns (mockSigner , nil )
905+ signerMultiplex .AddTypedSignerDeserializer (identity .Type (99 ), signerDes )
906+
907+ mockVerifier := & drivermock.Verifier {}
908+ verifierDes := & identitydrivermock.TypedVerifierDeserializer {}
909+ verifierDes .DeserializeVerifierReturns (mockVerifier , nil )
910+ verifierDes .RecipientsReturns ([]driver.Identity {[]byte ("recipient" )}, nil )
911+ verifierDes .GetAuditInfoMatcherReturns (& drivermock.Matcher {}, nil )
912+ verifierDes .GetAuditInfoReturns ([]byte ("audit-info" ), nil )
913+ verifierMultiplex .AddTypedVerifierDeserializer (identity .Type (99 ), verifierDes )
914+
915+ mockAuditInfo := & identitydrivermock.AuditInfo {}
916+ auditDes := & identitydrivermock.AuditInfoDeserializer {}
917+ auditDes .DeserializeAuditInfoReturns (mockAuditInfo , nil )
918+ eidrhDeserializer .AddDeserializer (identity .Type (99 ), auditDes )
919+
920+ typedID := createTypedIdentity (t , identity .Type (99 ), []byte ("raw-identity" ))
921+ provider := & drivermock.AuditInfoProvider {}
922+
923+ var wg sync.WaitGroup
924+
925+ // Writers: keep registering new deserializers concurrently.
926+ for g := 0 ; g < goroutines ; g ++ {
927+ wg .Add (1 )
928+ go func () {
929+ defer wg .Done ()
930+ for i := 0 ; i < iterations ; i ++ {
931+ // Register under a distinct type so writers keep hammering the
932+ // same map concurrently without clobbering the entry the
933+ // readers rely on below.
934+ signerMultiplex .AddTypedSignerDeserializer (identity .Type (100 ), & identitydrivermock.TypedSignerDeserializer {})
935+ verifierMultiplex .AddTypedVerifierDeserializer (identity .Type (100 ), & identitydrivermock.TypedVerifierDeserializer {})
936+ eidrhDeserializer .AddDeserializer (identity .Type (100 ), & identitydrivermock.AuditInfoDeserializer {})
937+ }
938+ }()
939+ }
940+
941+ // Readers: keep deserializing concurrently.
942+ for g := 0 ; g < goroutines ; g ++ {
943+ wg .Add (1 )
944+ go func () {
945+ defer wg .Done ()
946+ ctx := context .Background ()
947+ for i := 0 ; i < iterations ; i ++ {
948+ _ , _ = signerMultiplex .DeserializeSigner (ctx , typedID )
949+ _ , _ = verifierMultiplex .DeserializeVerifier (ctx , typedID )
950+ _ , _ = verifierMultiplex .Recipients (typedID )
951+ _ , _ = verifierMultiplex .GetAuditInfoMatcher (ctx , typedID , []byte ("audit-info" ))
952+ _ = verifierMultiplex .MatchIdentity (ctx , typedID , []byte ("audit-info" ))
953+ _ , _ = verifierMultiplex .GetAuditInfo (ctx , typedID , provider )
954+ _ , _ , _ = eidrhDeserializer .GetEIDAndRH (ctx , typedID , []byte ("audit-info" ))
955+ }
956+ }()
957+ }
958+
959+ wg .Wait ()
960+ }
0 commit comments