@@ -334,8 +334,10 @@ type Cmd struct {
334
334
lookPathErr error
335
335
336
336
// cachedLookExtensions caches the result of calling lookExtensions.
337
+ // It is set when Command is called with an absolute path, letting it do
338
+ // the work of resolving the extension, so Start doesn't need to do it again.
337
339
// This is only used on Windows.
338
- cachedLookExtensions string
340
+ cachedLookExtensions struct { in , out string }
339
341
}
340
342
341
343
// A ctxResult reports the result of watching the Context associated with a
@@ -436,12 +438,12 @@ func Command(name string, arg ...string) *Cmd {
436
438
// Since the path is absolute, its extension should be unambiguous
437
439
// and independent of cmd.Dir, and we can go ahead and cache the lookup now.
438
440
//
439
- // Note that we cannot add an extension here for relative paths, because
440
- // cmd.Dir may be set after we return from this function and that may cause
441
- // the command to resolve to a different extension.
442
- lp , err := lookExtensions (name , "" )
443
- cmd .cachedLookExtensions = lp
444
- if err != nil {
441
+ // Note that we don't cache anything here for relative paths, because
442
+ // cmd.Dir may be set after we return from this function and that may
443
+ // cause the command to resolve to a different extension.
444
+ if lp , err := lookExtensions (name , "" ); err == nil {
445
+ cmd .cachedLookExtensions . in , cmd . cachedLookExtensions . out = name , lp
446
+ } else {
445
447
cmd .Err = err
446
448
}
447
449
}
@@ -642,29 +644,32 @@ func (c *Cmd) Start() error {
642
644
return c .Err
643
645
}
644
646
lp := c .Path
645
- if c .cachedLookExtensions != "" {
646
- lp = c .cachedLookExtensions
647
- }
648
- if runtime .GOOS == "windows" && c .cachedLookExtensions == "" {
649
- // If c.Path is relative, we had to wait until now
650
- // to resolve it in case c.Dir was changed.
651
- // (If it is absolute, we already resolved its extension in Command
652
- // and shouldn't need to do so again.)
653
- //
654
- // Unfortunately, we cannot write the result back to c.Path because programs
655
- // may assume that they can call Start concurrently with reading the path.
656
- // (It is safe and non-racy to do so on Unix platforms, and users might not
657
- // test with the race detector on all platforms;
658
- // see https://go.dev/issue/62596.)
659
- //
660
- // So we will pass the fully resolved path to os.StartProcess, but leave
661
- // c.Path as is: missing a bit of logging information seems less harmful
662
- // than triggering a surprising data race, and if the user really cares
663
- // about that bit of logging they can always use LookPath to resolve it.
664
- var err error
665
- lp , err = lookExtensions (c .Path , c .Dir )
666
- if err != nil {
667
- return err
647
+ if runtime .GOOS == "windows" {
648
+ if c .Path == c .cachedLookExtensions .in {
649
+ // If Command was called with an absolute path, we already resolved
650
+ // its extension and shouldn't need to do so again (provided c.Path
651
+ // wasn't set to another value between the calls to Command and Start).
652
+ lp = c .cachedLookExtensions .out
653
+ } else {
654
+ // If *Cmd was made without using Command at all, or if Command was
655
+ // called with a relative path, we had to wait until now to resolve
656
+ // it in case c.Dir was changed.
657
+ //
658
+ // Unfortunately, we cannot write the result back to c.Path because programs
659
+ // may assume that they can call Start concurrently with reading the path.
660
+ // (It is safe and non-racy to do so on Unix platforms, and users might not
661
+ // test with the race detector on all platforms;
662
+ // see https://go.dev/issue/62596.)
663
+ //
664
+ // So we will pass the fully resolved path to os.StartProcess, but leave
665
+ // c.Path as is: missing a bit of logging information seems less harmful
666
+ // than triggering a surprising data race, and if the user really cares
667
+ // about that bit of logging they can always use LookPath to resolve it.
668
+ var err error
669
+ lp , err = lookExtensions (c .Path , c .Dir )
670
+ if err != nil {
671
+ return err
672
+ }
668
673
}
669
674
}
670
675
if c .Cancel != nil && c .ctx == nil {
0 commit comments