From 393bc2992e3852c05dec0de336b95d51dee20aff Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Tue, 11 Dec 2018 12:13:48 -0700 Subject: [PATCH] Add clustering plugin types; use latest certmagic.Storage interface --- caddy.go | 23 +++ caddyhttp/caddyhttp_test.go | 4 +- caddytls/setup.go | 8 +- caddytls/tls.go | 10 -- plugins.go | 28 ++++ .../github.com/mholt/certmagic/certmagic.go | 59 +++---- vendor/github.com/mholt/certmagic/client.go | 60 ++++--- vendor/github.com/mholt/certmagic/config.go | 30 +--- .../github.com/mholt/certmagic/filestorage.go | 113 ++++++++++++++ .../mholt/certmagic/filestoragesync.go | 146 ------------------ vendor/github.com/mholt/certmagic/storage.go | 48 +++++- vendor/manifest | 2 +- 12 files changed, 268 insertions(+), 263 deletions(-) delete mode 100644 vendor/github.com/mholt/certmagic/filestoragesync.go diff --git a/caddy.go b/caddy.go index 08eacf4ac..c2911b789 100644 --- a/caddy.go +++ b/caddy.go @@ -41,10 +41,12 @@ import ( "strconv" "strings" "sync" + "sync/atomic" "time" "github.com/mholt/caddy/caddyfile" "github.com/mholt/caddy/telemetry" + "github.com/mholt/certmagic" ) // Configurable application parameters @@ -462,6 +464,25 @@ func (i *Instance) Caddyfile() Input { // // This function blocks until all the servers are listening. func Start(cdyfile Input) (*Instance, error) { + // set up the clustering plugin, if there is one (this should be done + // exactly once -- but we can't do it during init when they're still + // getting plugged in, so do it when starting the first instance) + if atomic.CompareAndSwapInt32(&clusterPluginSetup, 0, 1) { + clusterPluginName := os.Getenv("CADDY_CLUSTERING") + if clusterPluginName == "" { + clusterPluginName = "file" // name of default storage plugin as registered in caddytls package + } + clusterFn, ok := clusterProviders[clusterPluginName] + if !ok { + return nil, fmt.Errorf("unrecognized cluster plugin (was it included in the Caddy build?): %s", clusterPluginName) + } + storage, err := clusterFn() + if err != nil { + return nil, fmt.Errorf("constructing cluster plugin %s: %v", clusterPluginName, err) + } + certmagic.DefaultStorage = storage + } + inst := &Instance{serverType: cdyfile.ServerType(), wg: new(sync.WaitGroup), Storage: make(map[interface{}]interface{})} err := startWithListenerFds(cdyfile, inst, nil) if err != nil { @@ -985,5 +1006,7 @@ var ( DefaultConfigFile = "Caddyfile" ) +var clusterPluginSetup int32 // access atomically + // CtxKey is a value type for use with context.WithValue. type CtxKey string diff --git a/caddyhttp/caddyhttp_test.go b/caddyhttp/caddyhttp_test.go index d83e3eb45..4e759b54b 100644 --- a/caddyhttp/caddyhttp_test.go +++ b/caddyhttp/caddyhttp_test.go @@ -25,9 +25,9 @@ import ( // ensure that the standard plugins are in fact plugged in // and registered properly; this is a quick/naive way to do it. func TestStandardPlugins(t *testing.T) { - numStandardPlugins := 30 // importing caddyhttp plugs in this many plugins + numStandardPlugins := 31 // importing caddyhttp plugs in this many plugins s := caddy.DescribePlugins() - if got, want := strings.Count(s, "\n"), numStandardPlugins+5; got != want { + if got, want := strings.Count(s, "\n"), numStandardPlugins+7; got != want { t.Errorf("Expected all standard plugins to be plugged in, got:\n%s", s) } } diff --git a/caddytls/setup.go b/caddytls/setup.go index 9ba09b9b3..55a570a20 100644 --- a/caddytls/setup.go +++ b/caddytls/setup.go @@ -35,8 +35,8 @@ import ( func init() { caddy.RegisterPlugin("tls", caddy.Plugin{Action: setupTLS}) - // ensure TLS assets are stored and accessed from the CADDYPATH - certmagic.DefaultStorage = certmagic.FileStorage{Path: caddy.AssetsPath()} + // ensure the default Storage implementation is plugged in + caddy.RegisterClusterPlugin("file", constructDefaultClusterPlugin) } // setupTLS sets up the TLS configuration and installs certificates that @@ -442,3 +442,7 @@ func loadCertsInDir(cfg *Config, c *caddy.Controller, dir string) error { return nil }) } + +func constructDefaultClusterPlugin() (certmagic.Storage, error) { + return certmagic.FileStorage{Path: caddy.AssetsPath()}, nil +} diff --git a/caddytls/tls.go b/caddytls/tls.go index 9c39989b2..9a9d5c639 100644 --- a/caddytls/tls.go +++ b/caddytls/tls.go @@ -108,13 +108,3 @@ func RegisterDNSProvider(name string, provider DNSProviderConstructor) { dnsProviders[name] = provider caddy.RegisterPlugin("tls.dns."+name, caddy.Plugin{}) } - -// TODO... - -// var storageProviders = make(map[string]StorageConstructor) - -// // RegisterStorageProvider registers provider by name for storing tls data -// func RegisterStorageProvider(name string, provider StorageConstructor) { -// storageProviders[name] = provider -// caddy.RegisterPlugin("tls.storage."+name, caddy.Plugin{}) -// } diff --git a/plugins.go b/plugins.go index 3d51d1fc1..67282bb9c 100644 --- a/plugins.go +++ b/plugins.go @@ -22,6 +22,7 @@ import ( "sync" "github.com/mholt/caddy/caddyfile" + "github.com/mholt/certmagic" ) // These are all the registered plugins. @@ -73,6 +74,13 @@ func DescribePlugins() string { } } + if len(pl["clustering"]) > 0 { + str += "\nClustering plugins:\n" + for _, name := range pl["clustering"] { + str += " " + name + "\n" + } + } + str += "\nOther plugins:\n" for _, name := range pl["others"] { str += " " + name + "\n" @@ -99,6 +107,11 @@ func ListPlugins() map[string][]string { p["caddyfile_loaders"] = append(p["caddyfile_loaders"], defaultCaddyfileLoader.name) } + // cluster plugins in registration order + for name := range clusterProviders { + p["clustering"] = append(p["clsutering"], name) + } + // List the event hook plugins eventHooks.Range(func(k, _ interface{}) bool { p["event_hooks"] = append(p["event_hooks"], k.(string)) @@ -443,6 +456,21 @@ func loadCaddyfileInput(serverType string) (Input, error) { return caddyfileToUse, nil } +// ClusterPluginConstructor is a function type that is used to +// instantiate a new implementation of both certmagic.Storage +// and certmagic.Locker, which are required for successful +// use in cluster environments. +type ClusterPluginConstructor func() (certmagic.Storage, error) + +// clusterProviders is the list of storage providers +var clusterProviders = make(map[string]ClusterPluginConstructor) + +// RegisterClusterPlugin registers provider by name for facilitating +// cluster-wide operations like storage and synchronization. +func RegisterClusterPlugin(name string, provider ClusterPluginConstructor) { + clusterProviders[name] = provider +} + // OnProcessExit is a list of functions to run when the process // exits -- they are ONLY for cleanup and should not block, // return errors, or do anything fancy. They will be run with diff --git a/vendor/github.com/mholt/certmagic/certmagic.go b/vendor/github.com/mholt/certmagic/certmagic.go index 1ba00a5ad..5a356e2a1 100644 --- a/vendor/github.com/mholt/certmagic/certmagic.go +++ b/vendor/github.com/mholt/certmagic/certmagic.go @@ -12,6 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Package certmagic automates the obtaining and renewal of TLS certificates, +// including TLS & HTTPS best practices such as robust OCSP stapling, caching, +// HTTP->HTTPS redirects, and more. +// +// Its high-level API serves your HTTP handlers over HTTPS by simply giving +// the domain name(s) and the http.Handler; CertMagic will create and run +// the HTTPS server for you, fully managing certificates during the lifetime +// of the server. Similarly, it can be used to start TLS listeners or return +// a ready-to-use tls.Config -- whatever layer you need TLS for, CertMagic +// makes it easy. +// +// If you need more control, create a Config using New() and then call +// Manage() on the config; but you'll have to be sure to solve the HTTP +// and TLS-ALPN challenges yourself (unless you disabled them or use the +// DNS challenge) by using the provided Config.GetCertificate function +// in your tls.Config and/or Config.HTTPChallangeHandler in your HTTP +// handler. +// +// See the package's README for more instruction. package certmagic import ( @@ -166,46 +185,6 @@ func manageWithDefaultConfig(domainNames []string, disableHTTPChallenge bool) (* return cfg, cfg.Manage(domainNames) } -// Locker facilitates synchronization of certificate tasks across -// machines and networks. -type Locker interface { - // TryLock will attempt to acquire the lock for key. If a - // lock could be obtained, nil values are returned as no - // waiting is required. If not (meaning another process is - // already working on key), a Waiter value will be returned, - // upon which you should Wait() until it is finished. - // - // The key should be a carefully-chosen value that uniquely - // and precisely identifies the operation being locked. For - // example, if it is for a certificate obtain or renew with - // the ACME protocol to the same CA endpoint (remembering - // that an obtain and renew are the same according to ACME, - // thus both obtain and renew should share a lock key), a - // good key would identify that operation by some name, - // concatenated with the domain name and the CA endpoint. - // - // TryLock never blocks; it always returns without waiting. - // - // To prevent deadlocks, all implementations (where this concern - // is relevant) should put a reasonable expiration on the lock in - // case Unlock is unable to be called due to some sort of storage - // system failure or crash. - TryLock(key string) (Waiter, error) - - // Unlock releases the lock for key. This method must ONLY be - // called after a successful call to TryLock where no Waiter was - // returned, and only after the operation requiring the lock is - // finished, even if it returned an error or timed out. Unlock - // should also clean up any unused resources allocated during - // TryLock. - Unlock(key string) error -} - -// Waiter is a type that can block until a lock is released. -type Waiter interface { - Wait() -} - // OnDemandConfig contains some state relevant for providing // on-demand TLS. type OnDemandConfig struct { diff --git a/vendor/github.com/mholt/certmagic/client.go b/vendor/github.com/mholt/certmagic/client.go index 65004d0fd..b909908a8 100644 --- a/vendor/github.com/mholt/certmagic/client.go +++ b/vendor/github.com/mholt/certmagic/client.go @@ -217,23 +217,21 @@ func (cfg *Config) lockKey(op, domainName string) string { // Callers who have access to a Config value should use the ObtainCert // method on that instead of this lower-level method. func (c *acmeClient) Obtain(name string) error { - if c.config.Sync != nil { - lockKey := c.config.lockKey("cert_acme", name) - waiter, err := c.config.Sync.TryLock(lockKey) - if err != nil { - return err - } - if waiter != nil { - log.Printf("[INFO] Certificate for %s is already being obtained elsewhere and stored; waiting", name) - waiter.Wait() - return nil // we assume the process with the lock succeeded, rather than hammering this execution path again - } - defer func() { - if err := c.config.Sync.Unlock(lockKey); err != nil { - log.Printf("[ERROR] Unable to unlock obtain call for %s: %v", name, err) - } - }() + lockKey := c.config.lockKey("cert_acme", name) + waiter, err := c.config.certCache.storage.TryLock(lockKey) + if err != nil { + return err } + if waiter != nil { + log.Printf("[INFO] Certificate for %s is already being obtained elsewhere and stored; waiting", name) + waiter.Wait() + return nil // we assume the process with the lock succeeded, rather than hammering this execution path again + } + defer func() { + if err := c.config.certCache.storage.Unlock(lockKey); err != nil { + log.Printf("[ERROR] Unable to unlock obtain call for %s: %v", name, err) + } + }() for attempts := 0; attempts < 2; attempts++ { request := certificate.ObtainRequest{ @@ -276,23 +274,21 @@ func (c *acmeClient) Obtain(name string) error { // Callers who have access to a Config value should use the RenewCert // method on that instead of this lower-level method. func (c *acmeClient) Renew(name string) error { - if c.config.Sync != nil { - lockKey := c.config.lockKey("cert_acme", name) - waiter, err := c.config.Sync.TryLock(lockKey) - if err != nil { - return err - } - if waiter != nil { - log.Printf("[INFO] Certificate for %s is already being renewed elsewhere and stored; waiting", name) - waiter.Wait() - return nil // assume that the worker that renewed the cert succeeded; avoid hammering this path over and over - } - defer func() { - if err := c.config.Sync.Unlock(lockKey); err != nil { - log.Printf("[ERROR] Unable to unlock renew call for %s: %v", name, err) - } - }() + lockKey := c.config.lockKey("cert_acme", name) + waiter, err := c.config.certCache.storage.TryLock(lockKey) + if err != nil { + return err } + if waiter != nil { + log.Printf("[INFO] Certificate for %s is already being renewed elsewhere and stored; waiting", name) + waiter.Wait() + return nil // assume that the worker that renewed the cert succeeded to avoid hammering this path over and over + } + defer func() { + if err := c.config.certCache.storage.Unlock(lockKey); err != nil { + log.Printf("[ERROR] Unable to unlock renew call for %s: %v", name, err) + } + }() // Prepare for renewal (load PEM cert, key, and meta) certRes, err := c.config.loadCertResource(name) diff --git a/vendor/github.com/mholt/certmagic/config.go b/vendor/github.com/mholt/certmagic/config.go index 195e30f3f..3b2062122 100644 --- a/vendor/github.com/mholt/certmagic/config.go +++ b/vendor/github.com/mholt/certmagic/config.go @@ -38,15 +38,6 @@ type Config struct { // selecting an existing ACME server account Email string - // The synchronization implementation - although - // it is not strictly required to have a Sync - // value in general, all instances running in - // in a cluster for the same domain names must - // specify a Sync and use the same one, otherwise - // some cert operations will not be properly - // coordinated - Sync Locker - // Set to true if agreed to the CA's // subscriber agreement Agreed bool @@ -198,20 +189,6 @@ func NewWithCache(certCache *Cache, cfg Config) *Config { cfg.MustStaple = MustStaple } - // if no sync facility is provided, we'll default to - // a file system synchronizer backed by the storage - // given to certCache (if it is one), or just a simple - // in-memory sync facility otherwise (strictly speaking, - // a sync is not required; only if running multiple - // instances for the same domain names concurrently) - if cfg.Sync == nil { - if ccfs, ok := certCache.storage.(FileStorage); ok { - cfg.Sync = NewFileStorageLocker(ccfs) - } else { - cfg.Sync = NewMemoryLocker() - } - } - // ensure the unexported fields are valid cfg.certificates = make(map[string]string) cfg.certCache = certCache @@ -222,7 +199,12 @@ func NewWithCache(certCache *Cache, cfg Config) *Config { } // Manage causes the certificates for domainNames to be managed -// according to cfg. +// according to cfg. If cfg is enabled for OnDemand, then this +// simply whitelists the domain names. Otherwise, the certificate(s) +// for each name are loaded from storage or obtained from the CA; +// and if loaded from storage, renewed if they are expiring or +// expired. It then caches the certificate in memory and is +// prepared to serve them up during TLS handshakes. func (cfg *Config) Manage(domainNames []string) error { for _, domainName := range domainNames { // if on-demand is configured, simply whitelist this name diff --git a/vendor/github.com/mholt/certmagic/filestorage.go b/vendor/github.com/mholt/certmagic/filestorage.go index 2012f3802..aa03058f9 100644 --- a/vendor/github.com/mholt/certmagic/filestorage.go +++ b/vendor/github.com/mholt/certmagic/filestorage.go @@ -15,10 +15,13 @@ package certmagic import ( + "fmt" "io/ioutil" "os" "path/filepath" "runtime" + "sync" + "time" ) // FileStorage facilitates forming file paths derived from a root @@ -123,4 +126,114 @@ func dataDir() string { return filepath.Join(baseDir, "certmagic") } +// TryLock attempts to get a lock for name, otherwise it returns +// a Waiter value to wait until the other process is finished. +func (fs FileStorage) TryLock(key string) (Waiter, error) { + fileStorageNameLocksMu.Lock() + defer fileStorageNameLocksMu.Unlock() + + // see if lock already exists within this process - allows + // for faster unlocking since we don't have to poll the disk + fw, ok := fileStorageNameLocks[key] + if ok { + // lock already created within process, let caller wait on it + return fw, nil + } + + // attempt to persist lock to disk by creating lock file + + // parent dir must exist + lockDir := fs.lockDir() + if err := os.MkdirAll(lockDir, 0700); err != nil { + return nil, err + } + + fw = &fileStorageWaiter{ + filename: filepath.Join(lockDir, safeKey(key)+".lock"), + wg: new(sync.WaitGroup), + } + + // create the file in a special mode such that an + // error is returned if it already exists + lf, err := os.OpenFile(fw.filename, os.O_CREATE|os.O_EXCL, 0644) + if err != nil { + if os.IsExist(err) { + // another process has the lock; use it to wait + return fw, nil + } + // otherwise, this was some unexpected error + return nil, err + } + lf.Close() + + // looks like we get the lock + fw.wg.Add(1) + fileStorageNameLocks[key] = fw + + return nil, nil +} + +// Unlock releases the lock for name. +func (fs FileStorage) Unlock(key string) error { + fileStorageNameLocksMu.Lock() + defer fileStorageNameLocksMu.Unlock() + + fw, ok := fileStorageNameLocks[key] + if !ok { + return fmt.Errorf("FileStorage: no lock to release for %s", key) + } + + // remove lock file + os.Remove(fw.filename) + + // if parent folder is now empty, remove it too to keep it tidy + dir, err := os.Open(fs.lockDir()) // OK to ignore error here + if err == nil { + items, _ := dir.Readdirnames(3) // OK to ignore error here + if len(items) == 0 { + os.Remove(dir.Name()) + } + dir.Close() + } + + // clean up in memory + fw.wg.Done() + delete(fileStorageNameLocks, key) + + return nil +} + +func (fs FileStorage) lockDir() string { + return filepath.Join(fs.Path, "locks") +} + +// fileStorageWaiter waits for a file to disappear; it +// polls the file system to check for the existence of +// a file. It also uses a WaitGroup to optimize the +// polling in the case when this process is the only +// one waiting. (Other processes that are waiting for +// the lock will still block, but must wait for the +// polling to get their answer.) +type fileStorageWaiter struct { + filename string + wg *sync.WaitGroup +} + +// Wait waits until the lock is released. +func (fw *fileStorageWaiter) Wait() { + start := time.Now() + fw.wg.Wait() + for time.Since(start) < 1*time.Hour { + _, err := os.Stat(fw.filename) + if os.IsNotExist(err) { + return + } + time.Sleep(1 * time.Second) + } +} + +var fileStorageNameLocks = make(map[string]*fileStorageWaiter) +var fileStorageNameLocksMu sync.Mutex + var _ Storage = FileStorage{} +var _ Waiter = &fileStorageWaiter{} diff --git a/vendor/github.com/mholt/certmagic/filestoragesync.go b/vendor/github.com/mholt/certmagic/filestoragesync.go deleted file mode 100644 index e6c25de9c..000000000 --- a/vendor/github.com/mholt/certmagic/filestoragesync.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2015 Matthew Holt -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package certmagic - -import ( - "fmt" - "os" - "path/filepath" - "sync" - "time" -) - -// FileStorageLocker implements the Locker interface -// using the file system. An empty value is NOT VALID, -// so you must use NewFileStorageLocker() to get one. -type FileStorageLocker struct { - fs FileStorage -} - -// NewFileStorageLocker returns a valid Locker backed by fs. -func NewFileStorageLocker(fs FileStorage) *FileStorageLocker { - return &FileStorageLocker{fs: fs} -} - -// TryLock attempts to get a lock for name, otherwise it returns -// a Waiter value to wait until the other process is finished. -func (l *FileStorageLocker) TryLock(name string) (Waiter, error) { - fileStorageNameLocksMu.Lock() - defer fileStorageNameLocksMu.Unlock() - - // see if lock already exists within this process - fw, ok := fileStorageNameLocks[name] - if ok { - // lock already created within process, let caller wait on it - return fw, nil - } - - // attempt to persist lock to disk by creating lock file - - // parent dir must exist - lockDir := l.lockDir() - if err := os.MkdirAll(lockDir, 0700); err != nil { - return nil, err - } - - fw = &FileStorageWaiter{ - filename: filepath.Join(lockDir, safeKey(name)+".lock"), - wg: new(sync.WaitGroup), - } - - // create the file in a special mode such that an - // error is returned if it already exists - lf, err := os.OpenFile(fw.filename, os.O_CREATE|os.O_EXCL, 0644) - if err != nil { - if os.IsExist(err) { - // another process has the lock; use it to wait - return fw, nil - } - // otherwise, this was some unexpected error - return nil, err - } - lf.Close() - - // looks like we get the lock - fw.wg.Add(1) - fileStorageNameLocks[name] = fw - - return nil, nil -} - -// Unlock releases the lock for name. -func (l *FileStorageLocker) Unlock(name string) error { - fileStorageNameLocksMu.Lock() - defer fileStorageNameLocksMu.Unlock() - - fw, ok := fileStorageNameLocks[name] - if !ok { - return fmt.Errorf("FileStorageLocker: no lock to release for %s", name) - } - - // remove lock file - os.Remove(fw.filename) - - // if parent folder is now empty, remove it too to keep it tidy - dir, err := os.Open(l.lockDir()) // OK to ignore error here - if err == nil { - items, _ := dir.Readdirnames(3) // OK to ignore error here - if len(items) == 0 { - os.Remove(dir.Name()) - } - dir.Close() - } - - // clean up in memory - fw.wg.Done() - delete(fileStorageNameLocks, name) - - return nil -} - -func (l *FileStorageLocker) lockDir() string { - return filepath.Join(l.fs.Path, "locks") -} - -// FileStorageWaiter waits for a file to disappear; it -// polls the file system to check for the existence of -// a file. It also uses a WaitGroup to optimize the -// polling in the case when this process is the only -// one waiting. (Other processes that are waiting -// for the lock will still block, but must wait -// for the poll intervals to get their answer.) -type FileStorageWaiter struct { - filename string - wg *sync.WaitGroup -} - -// Wait waits until the lock is released. -func (fw *FileStorageWaiter) Wait() { - start := time.Now() - fw.wg.Wait() - for time.Since(start) < 1*time.Hour { - _, err := os.Stat(fw.filename) - if os.IsNotExist(err) { - return - } - time.Sleep(1 * time.Second) - } -} - -var fileStorageNameLocks = make(map[string]*FileStorageWaiter) -var fileStorageNameLocksMu sync.Mutex - -var _ Locker = &FileStorageLocker{} -var _ Waiter = &FileStorageWaiter{} diff --git a/vendor/github.com/mholt/certmagic/storage.go b/vendor/github.com/mholt/certmagic/storage.go index 1d492c788..d3ac96608 100644 --- a/vendor/github.com/mholt/certmagic/storage.go +++ b/vendor/github.com/mholt/certmagic/storage.go @@ -31,9 +31,9 @@ import ( // in order to share certificates and other TLS resources // with the cluster. type Storage interface { - // Exists returns true if the key exists - // and there was no error checking. - Exists(key string) bool + // Locker provides atomic synchronization + // operations, making Storage safe to share. + Locker // Store puts value at key. Store(key string, value []byte) error @@ -44,6 +44,10 @@ type Storage interface { // Delete deletes key. Delete(key string) error + // Exists returns true if the key exists + // and there was no error checking. + Exists(key string) bool + // List returns all keys that match prefix. List(prefix string) ([]string, error) @@ -51,6 +55,41 @@ type Storage interface { Stat(key string) (KeyInfo, error) } +// Locker facilitates synchronization of certificate tasks across +// machines and networks. +type Locker interface { + // TryLock will attempt to acquire the lock for key. If a + // lock could be obtained, nil values are returned as no + // waiting is required. If not (meaning another process is + // already working on key), a Waiter value will be returned, + // upon which you should Wait() until it is finished. + // + // The actual implementation of obtaining of a lock must be + // an atomic operation so that multiple TryLock calls at the + // same time always results in only one caller receiving the + // lock. TryLock always returns without waiting. + // + // To prevent deadlocks, all implementations (where this concern + // is relevant) should put a reasonable expiration on the lock in + // case Unlock is unable to be called due to some sort of network + // or system failure or crash. + TryLock(key string) (Waiter, error) + + // Unlock releases the lock for key. This method must ONLY be + // called after a successful call to TryLock where no Waiter was + // returned, and only after the operation requiring the lock is + // finished, even if it errored or timed out. It is INCORRECT to + // call Unlock if any non-nil value was returned from a call to + // TryLock or if Unlock was not called at all. Unlock should also + // clean up any unused resources allocated during TryLock. + Unlock(key string) error +} + +// Waiter is a type that can block until a lock is released. +type Waiter interface { + Wait() +} + // KeyInfo holds information about a key in storage. type KeyInfo struct { Key string @@ -207,6 +246,3 @@ var defaultFileStorage = FileStorage{Path: dataDir()} // DefaultStorage is the default Storage implementation. var DefaultStorage Storage = defaultFileStorage - -// DefaultSync is a default sync to use. -var DefaultSync Locker diff --git a/vendor/manifest b/vendor/manifest index 26ce34c61..6d4f50dbd 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -138,7 +138,7 @@ "importpath": "github.com/mholt/certmagic", "repository": "https://github.com/mholt/certmagic", "vcs": "git", - "revision": "4dd0c62355ec3c3732f18c506449fc9b5f9f4c59", + "revision": "8b6ddf223c912a863aaadd388bfdd29be295fb5d", "branch": "master", "notests": true },