@@ -217,6 +217,7 @@ type ValidationAuthorityImpl struct {
217217 allowRestrictedAddrs bool
218218 experimentalVA * ValidationAuthorityImpl
219219 experimentalVASampleRate float64
220+ experimentalVATimeout time.Duration
220221
221222 metrics * vaMetrics
222223}
@@ -241,6 +242,7 @@ func NewValidationAuthorityImpl(
241242 allowRestrictedAddrs bool ,
242243 experimentalVA * ValidationAuthorityImpl ,
243244 experimentalVASampleRate float64 ,
245+ experimentalVATimeout time.Duration ,
244246) (* ValidationAuthorityImpl , error ) {
245247
246248 if len (accountURIPrefixes ) == 0 {
@@ -290,43 +292,46 @@ func NewValidationAuthorityImpl(
290292 allowRestrictedAddrs : allowRestrictedAddrs ,
291293 experimentalVA : experimentalVA ,
292294 experimentalVASampleRate : experimentalVASampleRate ,
295+ experimentalVATimeout : experimentalVATimeout ,
293296 }
294297
295298 return va , nil
296299}
297300
298- func (va * ValidationAuthorityImpl ) shouldDispatchExperiment () bool {
301+ func (va * ValidationAuthorityImpl ) shouldRunExperiment () bool {
299302 return va .experimentalVA != nil && rand .Float64 () < va .experimentalVASampleRate
300303}
301304
302- func (va * ValidationAuthorityImpl ) dispatchExperiment (operation string , primary remoteResult , experimentFunc func (context.Context ) (remoteResult , error )) {
303- go func () {
304- ctx , cancel := context .WithTimeout (context .Background (), va .slowRemoteTimeout )
305- defer cancel ()
305+ // runExperiment compares the primary VA's local result against the experimental
306+ // VA's result and records a concurrence metric. On disagreement, it logs a
307+ // structured event with both results. The primary argument must not be nil.
308+ // Callers should invoke this in a goroutine.
309+ func (va * ValidationAuthorityImpl ) runExperiment (ctx context.Context , operation string , primary remoteResult , experimentFunc func (context.Context ) (remoteResult , error )) {
310+ ctx , cancel := context .WithTimeout (context .WithoutCancel (ctx ), va .experimentalVATimeout )
311+ defer cancel ()
306312
307- experimentResult , err := experimentFunc (ctx )
313+ experimentResult , err := experimentFunc (ctx )
308314
309- primaryPassed := primary .GetProblem () == nil
310- experimentPassed := (err == nil ) && (experimentResult .GetProblem () == nil )
315+ primaryPassed := primary .GetProblem () == nil
316+ experimentPassed := (err == nil ) && (experimentResult .GetProblem () == nil )
311317
312- if primaryPassed == experimentPassed {
313- va .metrics .experimentConcurrence .WithLabelValues (operation , "true" ).Inc ()
314- return
315- }
316- va .metrics .experimentConcurrence .WithLabelValues (operation , "false" ).Inc ()
317-
318- logArgs := map [string ]any {
319- "operation" : operation ,
320- "primaryPassed" : primaryPassed ,
321- "primaryResult" : primary ,
322- "experimentPassed" : experimentPassed ,
323- "experimentResult" : experimentResult ,
324- }
325- if err != nil {
326- logArgs ["experimentErr" ] = err .Error ()
327- }
328- va .log .AuditInfo ("Primary VA disagreed with experimental VA" , logArgs )
329- }()
318+ if primaryPassed == experimentPassed {
319+ va .metrics .experimentConcurrence .WithLabelValues (operation , "true" ).Inc ()
320+ return
321+ }
322+ va .metrics .experimentConcurrence .WithLabelValues (operation , "false" ).Inc ()
323+
324+ logArgs := map [string ]any {
325+ "operation" : operation ,
326+ "primaryPassed" : primaryPassed ,
327+ "primaryResult" : primary ,
328+ "experimentPassed" : experimentPassed ,
329+ "experimentResult" : experimentResult ,
330+ }
331+ if err != nil {
332+ logArgs ["experimentErr" ] = err .Error ()
333+ }
334+ va .log .AuditInfo ("Primary VA disagreed with experimental VA" , logArgs )
330335}
331336
332337// maxAllowedFailures returns the maximum number of allowed failures
@@ -819,23 +824,25 @@ func (va *ValidationAuthorityImpl) DoDCV(ctx context.Context, req *vapb.PerformV
819824 prob = detailedError (err )
820825 }
821826
822- var localResult remoteResult
823- if va .shouldDispatchExperiment () {
824- defer func () {
825- va .dispatchExperiment (opDCV , localResult , func (ctx context.Context ) (remoteResult , error ) {
826- return va .experimentalVA .DoDCV (ctx , req )
827- })
828- }()
829- }
830-
831827 // Capture the local validation result for experimental resolver comparison
832828 // before MPIC can influence the outcome.
833- localResult , err = bgrpc .ValidationResultToPB (records , filterProblemDetails (prob ), va .perspective , va .rir )
829+ localResult , err : = bgrpc .ValidationResultToPB (records , filterProblemDetails (prob ), va .perspective , va .rir )
834830 if err != nil {
835831 return nil , err
836832 }
833+
834+ if va .shouldRunExperiment () {
835+ go va .runExperiment (
836+ ctx ,
837+ opDCV ,
838+ proto .Clone (localResult ).(* vapb.ValidationResult ),
839+ func (ctx context.Context ) (remoteResult , error ) {
840+ return va .experimentalVA .DoDCV (ctx , req )
841+ })
842+ }
843+
837844 if prob != nil {
838- return localResult .( * vapb. ValidationResult ) , nil
845+ return localResult , nil
839846 }
840847
841848 if va .isPrimaryVA () {
0 commit comments