"os"
"regexp"
"strings"
+ "time"
)
type StringMatcher func(string) bool
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".
// 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
return ac, MissingArvadosApiToken
}
+ ac.lastClosedIdlesAt = time.Now()
+
return ac, err
}
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 {