X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/e1dbdc4b39eb8c75c088f971cee0e7bad92b2848..af6d31cba8346ac86bc0027eb0f675144fb43056:/sdk/go/arvadosclient/arvadosclient.go?ds=sidebyside diff --git a/sdk/go/arvadosclient/arvadosclient.go b/sdk/go/arvadosclient/arvadosclient.go index af8bce4d4a..18e1074bf6 100644 --- a/sdk/go/arvadosclient/arvadosclient.go +++ b/sdk/go/arvadosclient/arvadosclient.go @@ -14,6 +14,7 @@ import ( "os" "regexp" "strings" + "time" ) type StringMatcher func(string) bool @@ -25,6 +26,12 @@ var MissingArvadosApiHost = errors.New("Missing required environment variable AR var MissingArvadosApiToken = errors.New("Missing required environment variable ARVADOS_API_TOKEN") var ErrInvalidArgument = errors.New("Invalid argument") +// A common failure mode is to reuse a keepalive connection that has been +// terminated (in a way that we can't detect) for being idle too long. +// POST and DELETE are not safe to retry automatically, so we minimize +// such failures by always using a new or recently active socket. +var MaxIdleConnectionDuration = 30 * time.Second + // Indicates an error that was returned by the API server. type APIServerError struct { // Address of server returning error, of the form "host:port". @@ -76,44 +83,25 @@ type ArvadosClient struct { // Discovery document DiscoveryDoc Dict -} -// APIConfig struct consists of: -// APIToken string -// APIHost string -// APIHostInsecure bool -// ExternalClient bool -type APIConfig struct { - APIToken string - APIHost string - APIHostInsecure bool - ExternalClient bool + lastClosedIdlesAt time.Time } -// Create a new ArvadosClient, initialized with standard Arvados environment variables -// ARVADOS_API_HOST, ARVADOS_API_TOKEN, ARVADOS_API_HOST_INSECURE, ARVADOS_EXTERNAL_CLIENT. +// Create a new ArvadosClient, initialized with standard Arvados environment +// variables ARVADOS_API_HOST, ARVADOS_API_TOKEN, and (optionally) +// ARVADOS_API_HOST_INSECURE. func MakeArvadosClient() (ac ArvadosClient, err error) { - var config APIConfig - config.APIToken = os.Getenv("ARVADOS_API_TOKEN") - config.APIHost = os.Getenv("ARVADOS_API_HOST") - var matchTrue = regexp.MustCompile("^(?i:1|yes|true)$") + insecure := matchTrue.MatchString(os.Getenv("ARVADOS_API_HOST_INSECURE")) + external := matchTrue.MatchString(os.Getenv("ARVADOS_EXTERNAL_CLIENT")) - config.APIHostInsecure = matchTrue.MatchString(os.Getenv("ARVADOS_API_HOST_INSECURE")) - config.ExternalClient = matchTrue.MatchString(os.Getenv("ARVADOS_EXTERNAL_CLIENT")) - - return New(config) -} - -// Create a new ArvadosClient, using the given input parameters. -func New(config APIConfig) (ac ArvadosClient, err error) { ac = ArvadosClient{ - ApiServer: config.APIHost, - ApiToken: config.APIToken, - ApiInsecure: config.APIHostInsecure, + ApiServer: os.Getenv("ARVADOS_API_HOST"), + ApiToken: os.Getenv("ARVADOS_API_TOKEN"), + ApiInsecure: insecure, Client: &http.Client{Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: config.APIHostInsecure}}}, - External: config.ExternalClient} + TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}}}, + External: external} if ac.ApiServer == "" { return ac, MissingArvadosApiHost @@ -122,6 +110,8 @@ func New(config APIConfig) (ac ArvadosClient, err error) { return ac, MissingArvadosApiToken } + ac.lastClosedIdlesAt = time.Now() + return ac, err } @@ -179,6 +169,15 @@ func (c ArvadosClient) CallRaw(method string, resourceType string, uuid string, req.Header.Add("X-External-Client", "1") } + // POST and DELETE are not safe to retry automatically, so we minimize + // such failures by always using a new or recently active socket + if method == "POST" || method == "DELETE" { + if time.Since(c.lastClosedIdlesAt) > MaxIdleConnectionDuration { + c.lastClosedIdlesAt = time.Now() + c.Client.Transport.(*http.Transport).CloseIdleConnections() + } + } + // Make the request var resp *http.Response if resp, err = c.Client.Do(req); err != nil {