@@ -16,6 +16,7 @@ import (
16
16
"path/filepath"
17
17
"regexp"
18
18
"strings"
19
+ "sync"
19
20
"time"
20
21
21
22
"github.com/cenkalti/backoff/v4"
@@ -762,11 +763,15 @@ func (c *DockerContainer) startLogProduction(ctx context.Context, opts ...LogPro
762
763
// Setup the log production context which will be used to stop the log production.
763
764
c .logProductionCtx , c .logProductionCancel = context .WithCancelCause (ctx )
764
765
765
- go func () {
766
- err := c .logProducer (stdout , stderr )
767
- // Set context cancel cause, if not already set.
768
- c .logProductionCancel (err )
769
- }()
766
+ // We capture context cancel function to avoid data race with multiple
767
+ // calls to startLogProduction.
768
+ go func (cancel context.CancelCauseFunc ) {
769
+ // Ensure the context is cancelled when log productions completes
770
+ // so that GetLogProductionErrorChannel functions correctly.
771
+ defer cancel (nil )
772
+
773
+ c .logProducer (stdout , stderr )
774
+ }(c .logProductionCancel )
770
775
771
776
return nil
772
777
}
@@ -775,40 +780,49 @@ func (c *DockerContainer) startLogProduction(ctx context.Context, opts ...LogPro
775
780
// - logProductionCtx is done
776
781
// - A fatal error occurs
777
782
// - No more logs are available
778
- func (c * DockerContainer ) logProducer (stdout , stderr io.Writer ) error {
783
+ func (c * DockerContainer ) logProducer (stdout , stderr io.Writer ) {
779
784
// Clean up idle client connections.
780
785
defer c .provider .Close ()
781
786
782
787
// Setup the log options, start from the beginning.
783
- options := container.LogsOptions {
788
+ options := & container.LogsOptions {
784
789
ShowStdout : true ,
785
790
ShowStderr : true ,
786
791
Follow : true ,
787
792
}
788
793
789
- for {
790
- timeoutCtx , cancel := context .WithTimeout (c .logProductionCtx , * c .logProductionTimeout )
791
- defer cancel ()
794
+ // Use a separate method so that timeout cancel function is
795
+ // called correctly.
796
+ for c .copyLogsTimeout (stdout , stderr , options ) {
797
+ }
798
+ }
792
799
793
- err := c .copyLogs (timeoutCtx , stdout , stderr , options )
794
- switch {
795
- case err == nil :
796
- // No more logs available.
797
- return nil
798
- case c .logProductionCtx .Err () != nil :
799
- // Log production was stopped or caller context is done.
800
- return nil
801
- case timeoutCtx .Err () != nil , errors .Is (err , net .ErrClosed ):
802
- // Timeout or client connection closed, retry.
803
- default :
804
- // Unexpected error, retry.
805
- Logger .Printf ("Unexpected error reading logs: %v" , err )
806
- }
800
+ // copyLogsTimeout copies logs from the container to stdout and stderr with a timeout.
801
+ // It returns true if the log production should be retried, false otherwise.
802
+ func (c * DockerContainer ) copyLogsTimeout (stdout , stderr io.Writer , options * container.LogsOptions ) bool {
803
+ timeoutCtx , cancel := context .WithTimeout (c .logProductionCtx , * c .logProductionTimeout )
804
+ defer cancel ()
807
805
808
- // Retry from the last log received.
809
- now := time .Now ()
810
- options .Since = fmt .Sprintf ("%d.%09d" , now .Unix (), int64 (now .Nanosecond ()))
806
+ err := c .copyLogs (timeoutCtx , stdout , stderr , * options )
807
+ switch {
808
+ case err == nil :
809
+ // No more logs available.
810
+ return false
811
+ case c .logProductionCtx .Err () != nil :
812
+ // Log production was stopped or caller context is done.
813
+ return false
814
+ case timeoutCtx .Err () != nil , errors .Is (err , net .ErrClosed ):
815
+ // Timeout or client connection closed, retry.
816
+ default :
817
+ // Unexpected error, retry.
818
+ Logger .Printf ("Unexpected error reading logs: %v" , err )
811
819
}
820
+
821
+ // Retry from the last log received.
822
+ now := time .Now ()
823
+ options .Since = fmt .Sprintf ("%d.%09d" , now .Unix (), int64 (now .Nanosecond ()))
824
+
825
+ return true
812
826
}
813
827
814
828
// copyLogs copies logs from the container to stdout and stderr.
@@ -866,10 +880,12 @@ func (c *DockerContainer) GetLogProductionErrorChannel() <-chan error {
866
880
}
867
881
868
882
errCh := make (chan error , 1 )
869
- go func () {
870
- <- c .logProductionCtx .Done ()
871
- errCh <- context .Cause (c .logProductionCtx )
872
- }()
883
+ go func (ctx context.Context ) {
884
+ <- ctx .Done ()
885
+ errCh <- context .Cause (ctx )
886
+ close (errCh )
887
+ }(c .logProductionCtx )
888
+
873
889
return errCh
874
890
}
875
891
@@ -906,6 +922,7 @@ type DockerProvider struct {
906
922
host string
907
923
hostCache string
908
924
config config.Config
925
+ mtx sync.Mutex
909
926
}
910
927
911
928
// Client gets the docker client used by the provider
@@ -984,29 +1001,26 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque
984
1001
// defer the close of the Docker client connection the soonest
985
1002
defer p .Close ()
986
1003
987
- // Make sure that bridge network exists
988
- // In case it is disabled we will create reaper_default network
989
- if p .DefaultNetwork == "" {
990
- p .DefaultNetwork , err = p .getDefaultNetwork (ctx , p .client )
991
- if err != nil {
992
- return nil , err
993
- }
1004
+ var defaultNetwork string
1005
+ defaultNetwork , err = p .ensureDefaultNetwork (ctx )
1006
+ if err != nil {
1007
+ return nil , fmt .Errorf ("ensure default network: %w" , err )
994
1008
}
995
1009
996
1010
// If default network is not bridge make sure it is attached to the request
997
1011
// as container won't be attached to it automatically
998
1012
// in case of Podman the bridge network is called 'podman' as 'bridge' would conflict
999
- if p . DefaultNetwork != p .defaultBridgeNetworkName {
1013
+ if defaultNetwork != p .defaultBridgeNetworkName {
1000
1014
isAttached := false
1001
1015
for _ , net := range req .Networks {
1002
- if net == p . DefaultNetwork {
1016
+ if net == defaultNetwork {
1003
1017
isAttached = true
1004
1018
break
1005
1019
}
1006
1020
}
1007
1021
1008
1022
if ! isAttached {
1009
- req .Networks = append (req .Networks , p . DefaultNetwork )
1023
+ req .Networks = append (req .Networks , defaultNetwork )
1010
1024
}
1011
1025
}
1012
1026
@@ -1461,12 +1475,8 @@ func (p *DockerProvider) CreateNetwork(ctx context.Context, req NetworkRequest)
1461
1475
// defer the close of the Docker client connection the soonest
1462
1476
defer p .Close ()
1463
1477
1464
- // Make sure that bridge network exists
1465
- // In case it is disabled we will create reaper_default network
1466
- if p .DefaultNetwork == "" {
1467
- if p .DefaultNetwork , err = p .getDefaultNetwork (ctx , p .client ); err != nil {
1468
- return nil , err
1469
- }
1478
+ if _ , err = p .ensureDefaultNetwork (ctx ); err != nil {
1479
+ return nil , fmt .Errorf ("ensure default network: %w" , err )
1470
1480
}
1471
1481
1472
1482
if req .Labels == nil {
@@ -1537,14 +1547,12 @@ func (p *DockerProvider) GetNetwork(ctx context.Context, req NetworkRequest) (ne
1537
1547
1538
1548
func (p * DockerProvider ) GetGatewayIP (ctx context.Context ) (string , error ) {
1539
1549
// Use a default network as defined in the DockerProvider
1540
- if p .DefaultNetwork == "" {
1541
- var err error
1542
- p .DefaultNetwork , err = p .getDefaultNetwork (ctx , p .client )
1543
- if err != nil {
1544
- return "" , err
1545
- }
1550
+ defaultNetwork , err := p .ensureDefaultNetwork (ctx )
1551
+ if err != nil {
1552
+ return "" , fmt .Errorf ("ensure default network: %w" , err )
1546
1553
}
1547
- nw , err := p .GetNetwork (ctx , NetworkRequest {Name : p .DefaultNetwork })
1554
+
1555
+ nw , err := p .GetNetwork (ctx , NetworkRequest {Name : defaultNetwork })
1548
1556
if err != nil {
1549
1557
return "" , err
1550
1558
}
@@ -1563,43 +1571,50 @@ func (p *DockerProvider) GetGatewayIP(ctx context.Context) (string, error) {
1563
1571
return ip , nil
1564
1572
}
1565
1573
1566
- func ( p * DockerProvider ) getDefaultNetwork ( ctx context. Context , cli client. APIClient ) ( string , error ) {
1567
- // Get list of available networks
1568
- networkResources , err := cli . NetworkList ( ctx , network. ListOptions {})
1569
- if err != nil {
1570
- return "" , err
1571
- }
1574
+ // ensureDefaultNetwork ensures that defaultNetwork is set and creates
1575
+ // it if it does not exist, returning its value.
1576
+ // It is safe to call this method concurrently.
1577
+ func ( p * DockerProvider ) ensureDefaultNetwork ( ctx context. Context ) ( string , error ) {
1578
+ p . mtx . Lock ()
1579
+ defer p . mtx . Unlock ()
1572
1580
1573
- reaperNetwork := ReaperDefault
1581
+ if p .defaultNetwork != "" {
1582
+ // Already set.
1583
+ return p .defaultNetwork , nil
1584
+ }
1574
1585
1575
- reaperNetworkExists := false
1586
+ networkResources , err := p .client .NetworkList (ctx , network.ListOptions {})
1587
+ if err != nil {
1588
+ return "" , fmt .Errorf ("network list: %w" , err )
1589
+ }
1576
1590
1577
1591
for _ , net := range networkResources {
1578
- if net .Name == p .defaultBridgeNetworkName {
1579
- return p .defaultBridgeNetworkName , nil
1580
- }
1581
-
1582
- if net .Name == reaperNetwork {
1583
- reaperNetworkExists = true
1592
+ switch net .Name {
1593
+ case p .defaultBridgeNetworkName :
1594
+ p .defaultNetwork = p .defaultBridgeNetworkName
1595
+ return p .defaultNetwork , nil
1596
+ case ReaperDefault :
1597
+ p .defaultNetwork = ReaperDefault
1598
+ return p .defaultNetwork , nil
1584
1599
}
1585
1600
}
1586
1601
1587
- // Create a bridge network for the container communications
1588
- if ! reaperNetworkExists {
1589
- _ , err = cli .NetworkCreate (ctx , reaperNetwork , network.CreateOptions {
1590
- Driver : Bridge ,
1591
- Attachable : true ,
1592
- Labels : GenericLabels (),
1593
- })
1594
- // If the network already exists, we can ignore the error as that can
1595
- // happen if we are running multiple tests in parallel and we only
1596
- // need to ensure that the network exists.
1597
- if err != nil && ! errdefs .IsConflict (err ) {
1598
- return "" , err
1599
- }
1602
+ // Create a bridge network for the container communications.
1603
+ _ , err = p .client .NetworkCreate (ctx , ReaperDefault , network.CreateOptions {
1604
+ Driver : Bridge ,
1605
+ Attachable : true ,
1606
+ Labels : GenericLabels (),
1607
+ })
1608
+ // If the network already exists, we can ignore the error as that can
1609
+ // happen if we are running multiple tests in parallel and we only
1610
+ // need to ensure that the network exists.
1611
+ if err != nil && ! errdefs .IsConflict (err ) {
1612
+ return "" , fmt .Errorf ("network create: %w" , err )
1600
1613
}
1601
1614
1602
- return reaperNetwork , nil
1615
+ p .defaultNetwork = ReaperDefault
1616
+
1617
+ return p .defaultNetwork , nil
1603
1618
}
1604
1619
1605
1620
// containerFromDockerResponse builds a Docker container struct from the response of the Docker API
0 commit comments