mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-02 18:47:22 -05:00 
			
		
		
		
	caddytls: Extract locker into an interface (#1942)
This commit is contained in:
		
							parent
							
								
									63fd264043
								
							
						
					
					
						commit
						5f39cbef94
					
				@ -39,6 +39,7 @@ type ACMEClient struct {
 | 
				
			|||||||
	AllowPrompts bool
 | 
						AllowPrompts bool
 | 
				
			||||||
	config       *Config
 | 
						config       *Config
 | 
				
			||||||
	acmeClient   *acme.Client
 | 
						acmeClient   *acme.Client
 | 
				
			||||||
 | 
						locker       Locker
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// newACMEClient creates a new ACMEClient given an email and whether
 | 
					// newACMEClient creates a new ACMEClient given an email and whether
 | 
				
			||||||
@ -120,6 +121,10 @@ var newACMEClient = func(config *Config, allowPrompts bool) (*ACMEClient, error)
 | 
				
			|||||||
		AllowPrompts: allowPrompts,
 | 
							AllowPrompts: allowPrompts,
 | 
				
			||||||
		config:       config,
 | 
							config:       config,
 | 
				
			||||||
		acmeClient:   client,
 | 
							acmeClient:   client,
 | 
				
			||||||
 | 
							locker: &syncLock{
 | 
				
			||||||
 | 
								nameLocks:   make(map[string]*sync.WaitGroup),
 | 
				
			||||||
 | 
								nameLocksMu: sync.Mutex{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if config.DNSProvider == "" {
 | 
						if config.DNSProvider == "" {
 | 
				
			||||||
@ -210,7 +215,7 @@ func (c *ACMEClient) Obtain(name string) error {
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	waiter, err := storage.TryLock(name)
 | 
						waiter, err := c.locker.TryLock(name)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -220,7 +225,7 @@ func (c *ACMEClient) Obtain(name string) error {
 | 
				
			|||||||
		return nil // we assume the process with the lock succeeded, rather than hammering this execution path again
 | 
							return nil // we assume the process with the lock succeeded, rather than hammering this execution path again
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer func() {
 | 
						defer func() {
 | 
				
			||||||
		if err := storage.Unlock(name); err != nil {
 | 
							if err := c.locker.Unlock(name); err != nil {
 | 
				
			||||||
			log.Printf("[ERROR] Unable to unlock obtain call for %s: %v", name, err)
 | 
								log.Printf("[ERROR] Unable to unlock obtain call for %s: %v", name, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}()
 | 
						}()
 | 
				
			||||||
@ -286,7 +291,7 @@ func (c *ACMEClient) Renew(name string) error {
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	waiter, err := storage.TryLock(name)
 | 
						waiter, err := c.locker.TryLock(name)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -296,7 +301,7 @@ func (c *ACMEClient) Renew(name string) error {
 | 
				
			|||||||
		return nil // we assume the process with the lock succeeded, rather than hammering this execution path again
 | 
							return nil // we assume the process with the lock succeeded, rather than hammering this execution path again
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer func() {
 | 
						defer func() {
 | 
				
			||||||
		if err := storage.Unlock(name); err != nil {
 | 
							if err := c.locker.Unlock(name); err != nil {
 | 
				
			||||||
			log.Printf("[ERROR] Unable to unlock renew call for %s: %v", name, err)
 | 
								log.Printf("[ERROR] Unable to unlock renew call for %s: %v", name, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}()
 | 
						}()
 | 
				
			||||||
 | 
				
			|||||||
@ -22,7 +22,6 @@ import (
 | 
				
			|||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"sync"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/mholt/caddy"
 | 
						"github.com/mholt/caddy"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@ -40,8 +39,7 @@ var storageBasePath = filepath.Join(caddy.AssetsPath(), "acme")
 | 
				
			|||||||
// instance is guaranteed to be non-nil if there is no error.
 | 
					// instance is guaranteed to be non-nil if there is no error.
 | 
				
			||||||
func NewFileStorage(caURL *url.URL) (Storage, error) {
 | 
					func NewFileStorage(caURL *url.URL) (Storage, error) {
 | 
				
			||||||
	return &FileStorage{
 | 
						return &FileStorage{
 | 
				
			||||||
		Path:      filepath.Join(storageBasePath, caURL.Host),
 | 
							Path: filepath.Join(storageBasePath, caURL.Host),
 | 
				
			||||||
		nameLocks: make(map[string]*sync.WaitGroup),
 | 
					 | 
				
			||||||
	}, nil
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -49,9 +47,7 @@ func NewFileStorage(caURL *url.URL) (Storage, error) {
 | 
				
			|||||||
// directory. It is used to get file paths in a consistent,
 | 
					// directory. It is used to get file paths in a consistent,
 | 
				
			||||||
// cross-platform way or persisting ACME assets on the file system.
 | 
					// cross-platform way or persisting ACME assets on the file system.
 | 
				
			||||||
type FileStorage struct {
 | 
					type FileStorage struct {
 | 
				
			||||||
	Path        string
 | 
						Path string
 | 
				
			||||||
	nameLocks   map[string]*sync.WaitGroup
 | 
					 | 
				
			||||||
	nameLocksMu sync.Mutex
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// sites gets the directory that stores site certificate and keys.
 | 
					// sites gets the directory that stores site certificate and keys.
 | 
				
			||||||
@ -254,36 +250,6 @@ func (s *FileStorage) StoreUser(email string, data *UserData) error {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TryLock attempts to get a lock for name, otherwise it returns
 | 
					 | 
				
			||||||
// a Waiter value to wait until the other process is finished.
 | 
					 | 
				
			||||||
func (s *FileStorage) TryLock(name string) (Waiter, error) {
 | 
					 | 
				
			||||||
	s.nameLocksMu.Lock()
 | 
					 | 
				
			||||||
	defer s.nameLocksMu.Unlock()
 | 
					 | 
				
			||||||
	wg, ok := s.nameLocks[name]
 | 
					 | 
				
			||||||
	if ok {
 | 
					 | 
				
			||||||
		// lock already obtained, let caller wait on it
 | 
					 | 
				
			||||||
		return wg, nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	// caller gets lock
 | 
					 | 
				
			||||||
	wg = new(sync.WaitGroup)
 | 
					 | 
				
			||||||
	wg.Add(1)
 | 
					 | 
				
			||||||
	s.nameLocks[name] = wg
 | 
					 | 
				
			||||||
	return nil, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Unlock unlocks name.
 | 
					 | 
				
			||||||
func (s *FileStorage) Unlock(name string) error {
 | 
					 | 
				
			||||||
	s.nameLocksMu.Lock()
 | 
					 | 
				
			||||||
	defer s.nameLocksMu.Unlock()
 | 
					 | 
				
			||||||
	wg, ok := s.nameLocks[name]
 | 
					 | 
				
			||||||
	if !ok {
 | 
					 | 
				
			||||||
		return fmt.Errorf("FileStorage: no lock to release for %s", name)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	wg.Done()
 | 
					 | 
				
			||||||
	delete(s.nameLocks, name)
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// MostRecentUserEmail implements Storage.MostRecentUserEmail by finding the
 | 
					// MostRecentUserEmail implements Storage.MostRecentUserEmail by finding the
 | 
				
			||||||
// most recently written sub directory in the users' directory. It is named
 | 
					// most recently written sub directory in the users' directory. It is named
 | 
				
			||||||
// after the email address. This corresponds to the most recent call to
 | 
					// after the email address. This corresponds to the most recent call to
 | 
				
			||||||
 | 
				
			|||||||
@ -39,24 +39,9 @@ type UserData struct {
 | 
				
			|||||||
	Key []byte
 | 
						Key []byte
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Storage is an interface abstracting all storage used by Caddy's TLS
 | 
					// Locker provides support for mutual exclusion
 | 
				
			||||||
// subsystem. Implementations of this interface store both site and
 | 
					type Locker interface {
 | 
				
			||||||
// user data.
 | 
						// TryLock will return immediatedly with or without acquiring the lock.
 | 
				
			||||||
type Storage interface {
 | 
					 | 
				
			||||||
	// SiteExists returns true if this site exists in storage.
 | 
					 | 
				
			||||||
	// Site data is considered present when StoreSite has been called
 | 
					 | 
				
			||||||
	// successfully (without DeleteSite having been called, of course).
 | 
					 | 
				
			||||||
	SiteExists(domain string) (bool, error)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// TryLock is called before Caddy attempts to obtain or renew a
 | 
					 | 
				
			||||||
	// certificate for a certain name and store it. From the perspective
 | 
					 | 
				
			||||||
	// of this method and its companion Unlock, the actions of
 | 
					 | 
				
			||||||
	// obtaining/renewing and then storing the certificate are atomic,
 | 
					 | 
				
			||||||
	// and both should occur within a lock. This prevents multiple
 | 
					 | 
				
			||||||
	// processes -- maybe distributed ones -- from stepping on each
 | 
					 | 
				
			||||||
	// other's space in the same shared storage, and from spamming
 | 
					 | 
				
			||||||
	// certificate providers with multiple, redundant requests.
 | 
					 | 
				
			||||||
	//
 | 
					 | 
				
			||||||
	// If a lock could be obtained, (nil, nil) is returned and you may
 | 
						// If a lock could be obtained, (nil, nil) is returned and you may
 | 
				
			||||||
	// continue normally. If not (meaning another process is already
 | 
						// continue normally. If not (meaning another process is already
 | 
				
			||||||
	// working on that name), a Waiter value will be returned upon
 | 
						// working on that name), a Waiter value will be returned upon
 | 
				
			||||||
@ -75,6 +60,16 @@ type Storage interface {
 | 
				
			|||||||
	// the obtain/renew and store are finished, even if there was
 | 
						// the obtain/renew and store are finished, even if there was
 | 
				
			||||||
	// an error (or a timeout).
 | 
						// an error (or a timeout).
 | 
				
			||||||
	Unlock(name string) error
 | 
						Unlock(name string) error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Storage is an interface abstracting all storage used by Caddy's TLS
 | 
				
			||||||
 | 
					// subsystem. Implementations of this interface store both site and
 | 
				
			||||||
 | 
					// user data.
 | 
				
			||||||
 | 
					type Storage interface {
 | 
				
			||||||
 | 
						// SiteExists returns true if this site exists in storage.
 | 
				
			||||||
 | 
						// Site data is considered present when StoreSite has been called
 | 
				
			||||||
 | 
						// successfully (without DeleteSite having been called, of course).
 | 
				
			||||||
 | 
						SiteExists(domain string) (bool, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// LoadSite obtains the site data from storage for the given domain and
 | 
						// LoadSite obtains the site data from storage for the given domain and
 | 
				
			||||||
	// returns it. If data for the domain does not exist, an error value
 | 
						// returns it. If data for the domain does not exist, an error value
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										57
									
								
								caddytls/sync_locker.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								caddytls/sync_locker.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,57 @@
 | 
				
			|||||||
 | 
					// Copyright 2015 Light Code Labs, LLC
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// 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 caddytls
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var _ Locker = &syncLock{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type syncLock struct {
 | 
				
			||||||
 | 
						nameLocks   map[string]*sync.WaitGroup
 | 
				
			||||||
 | 
						nameLocksMu sync.Mutex
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TryLock attempts to get a lock for name, otherwise it returns
 | 
				
			||||||
 | 
					// a Waiter value to wait until the other process is finished.
 | 
				
			||||||
 | 
					func (s *syncLock) TryLock(name string) (Waiter, error) {
 | 
				
			||||||
 | 
						s.nameLocksMu.Lock()
 | 
				
			||||||
 | 
						defer s.nameLocksMu.Unlock()
 | 
				
			||||||
 | 
						wg, ok := s.nameLocks[name]
 | 
				
			||||||
 | 
						if ok {
 | 
				
			||||||
 | 
							// lock already obtained, let caller wait on it
 | 
				
			||||||
 | 
							return wg, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// caller gets lock
 | 
				
			||||||
 | 
						wg = new(sync.WaitGroup)
 | 
				
			||||||
 | 
						wg.Add(1)
 | 
				
			||||||
 | 
						s.nameLocks[name] = wg
 | 
				
			||||||
 | 
						return nil, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Unlock unlocks name.
 | 
				
			||||||
 | 
					func (s *syncLock) Unlock(name string) error {
 | 
				
			||||||
 | 
						s.nameLocksMu.Lock()
 | 
				
			||||||
 | 
						defer s.nameLocksMu.Unlock()
 | 
				
			||||||
 | 
						wg, ok := s.nameLocks[name]
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return fmt.Errorf("FileStorage: no lock to release for %s", name)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						wg.Done()
 | 
				
			||||||
 | 
						delete(s.nameLocks, name)
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -16,7 +16,6 @@ package caddytls
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"sync"
 | 
					 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/xenolf/lego/acme"
 | 
						"github.com/xenolf/lego/acme"
 | 
				
			||||||
@ -94,7 +93,7 @@ func TestQualifiesForManagedTLS(t *testing.T) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestSaveCertResource(t *testing.T) {
 | 
					func TestSaveCertResource(t *testing.T) {
 | 
				
			||||||
	storage := &FileStorage{Path: "./le_test_save", nameLocks: make(map[string]*sync.WaitGroup)}
 | 
						storage := &FileStorage{Path: "./le_test_save"}
 | 
				
			||||||
	defer func() {
 | 
						defer func() {
 | 
				
			||||||
		err := os.RemoveAll(storage.Path)
 | 
							err := os.RemoveAll(storage.Path)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
@ -140,7 +139,7 @@ func TestSaveCertResource(t *testing.T) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestExistingCertAndKey(t *testing.T) {
 | 
					func TestExistingCertAndKey(t *testing.T) {
 | 
				
			||||||
	storage := &FileStorage{Path: "./le_test_existing", nameLocks: make(map[string]*sync.WaitGroup)}
 | 
						storage := &FileStorage{Path: "./le_test_existing"}
 | 
				
			||||||
	defer func() {
 | 
						defer func() {
 | 
				
			||||||
		err := os.RemoveAll(storage.Path)
 | 
							err := os.RemoveAll(storage.Path)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
 | 
				
			|||||||
@ -21,7 +21,6 @@ import (
 | 
				
			|||||||
	"crypto/rand"
 | 
						"crypto/rand"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"sync"
 | 
					 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -196,7 +195,7 @@ func TestGetEmail(t *testing.T) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var testStorage = &FileStorage{Path: "./testdata", nameLocks: make(map[string]*sync.WaitGroup)}
 | 
					var testStorage = &FileStorage{Path: "./testdata"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *FileStorage) clean() error {
 | 
					func (s *FileStorage) clean() error {
 | 
				
			||||||
	return os.RemoveAll(s.Path)
 | 
						return os.RemoveAll(s.Path)
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user