github.com/macb/etcd@v0.3.1-0.20140227003422-a60481c6b1a0/mod/lock/v2/acquire_handler.go (about)

     1  package v2
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net/http"
     7  	"path"
     8  	"strconv"
     9  	"time"
    10  
    11  	etcdErr "github.com/coreos/etcd/error"
    12  	"github.com/coreos/etcd/third_party/github.com/coreos/go-etcd/etcd"
    13  	"github.com/coreos/etcd/third_party/github.com/gorilla/mux"
    14  )
    15  
    16  // acquireHandler attempts to acquire a lock on the given key.
    17  // The "key" parameter specifies the resource to lock.
    18  // The "value" parameter specifies a value to associate with the lock.
    19  // The "ttl" parameter specifies how long the lock will persist for.
    20  // The "timeout" parameter specifies how long the request should wait for the lock.
    21  func (h *handler) acquireHandler(w http.ResponseWriter, req *http.Request) error {
    22  	h.client.SyncCluster()
    23  
    24  	// Setup connection watcher.
    25  	closeNotifier, _ := w.(http.CloseNotifier)
    26  	closeChan := closeNotifier.CloseNotify()
    27  	stopChan := make(chan bool)
    28  
    29  	// Parse the lock "key".
    30  	vars := mux.Vars(req)
    31  	keypath := path.Join(prefix, vars["key"])
    32  	value := req.FormValue("value")
    33  
    34  	// Parse "timeout" parameter.
    35  	var timeout int
    36  	var err error
    37  	if req.FormValue("timeout") == "" {
    38  		timeout = -1
    39  	} else if timeout, err = strconv.Atoi(req.FormValue("timeout")); err != nil {
    40  		return etcdErr.NewError(etcdErr.EcodeTimeoutNaN, "Acquire", 0)
    41  	}
    42  	timeout = timeout + 1
    43  
    44  	// Parse TTL.
    45  	ttl, err := strconv.Atoi(req.FormValue("ttl"))
    46  	if err != nil {
    47  		return etcdErr.NewError(etcdErr.EcodeTTLNaN, "Acquire", 0)
    48  	}
    49  
    50  	// If node exists then just watch it. Otherwise create the node and watch it.
    51  	node, index, pos := h.findExistingNode(keypath, value)
    52  	if index > 0 {
    53  		if pos == 0 {
    54  			// If lock is already acquired then update the TTL.
    55  			h.client.Update(node.Key, node.Value, uint64(ttl))
    56  		} else {
    57  			// Otherwise watch until it becomes acquired (or errors).
    58  			err = h.watch(keypath, index, nil)
    59  		}
    60  	} else {
    61  		index, err = h.createNode(keypath, value, ttl, closeChan, stopChan)
    62  	}
    63  
    64  	// Stop all goroutines.
    65  	close(stopChan)
    66  
    67  	// Check for an error.
    68  	if err != nil {
    69  		return err
    70  	}
    71  
    72  	// Write response.
    73  	w.Write([]byte(strconv.Itoa(index)))
    74  	return nil
    75  }
    76  
    77  // createNode creates a new lock node and watches it until it is acquired or acquisition fails.
    78  func (h *handler) createNode(keypath string, value string, ttl int, closeChan <-chan bool, stopChan chan bool) (int, error) {
    79  	// Default the value to "-" if it is blank.
    80  	if len(value) == 0 {
    81  		value = "-"
    82  	}
    83  
    84  	// Create an incrementing id for the lock.
    85  	resp, err := h.client.AddChild(keypath, value, uint64(ttl))
    86  	if err != nil {
    87  		return 0, err
    88  	}
    89  	indexpath := resp.Node.Key
    90  	index, _ := strconv.Atoi(path.Base(indexpath))
    91  
    92  	// Keep updating TTL to make sure lock request is not expired before acquisition.
    93  	go h.ttlKeepAlive(indexpath, value, ttl, stopChan)
    94  
    95  	// Watch until we acquire or fail.
    96  	err = h.watch(keypath, index, closeChan)
    97  
    98  	// Check for connection disconnect before we write the lock index.
    99  	if err != nil {
   100  		select {
   101  		case <-closeChan:
   102  			err = errors.New("user interrupted")
   103  		default:
   104  		}
   105  	}
   106  
   107  	// Update TTL one last time if acquired. Otherwise delete.
   108  	if err == nil {
   109  		h.client.Update(indexpath, value, uint64(ttl))
   110  	} else {
   111  		h.client.Delete(indexpath, false)
   112  	}
   113  
   114  	return index, err
   115  }
   116  
   117  // findExistingNode search for a node on the lock with the given value.
   118  func (h *handler) findExistingNode(keypath string, value string) (*etcd.Node, int, int) {
   119  	if len(value) > 0 {
   120  		resp, err := h.client.Get(keypath, true, true)
   121  		if err == nil {
   122  			nodes := lockNodes{resp.Node.Nodes}
   123  			if node, pos := nodes.FindByValue(value); node != nil {
   124  				index, _ := strconv.Atoi(path.Base(node.Key))
   125  				return node, index, pos
   126  			}
   127  		}
   128  	}
   129  	return nil, 0, 0
   130  }
   131  
   132  // ttlKeepAlive continues to update a key's TTL until the stop channel is closed.
   133  func (h *handler) ttlKeepAlive(k string, value string, ttl int, stopChan chan bool) {
   134  	for {
   135  		select {
   136  		case <-time.After(time.Duration(ttl/2) * time.Second):
   137  			h.client.Update(k, value, uint64(ttl))
   138  		case <-stopChan:
   139  			return
   140  		}
   141  	}
   142  }
   143  
   144  // watch continuously waits for a given lock index to be acquired or until lock fails.
   145  // Returns a boolean indicating success.
   146  func (h *handler) watch(keypath string, index int, closeChan <-chan bool) error {
   147  	// Wrap close chan so we can pass it to Client.Watch().
   148  	stopWatchChan := make(chan bool)
   149  	stopWrapChan := make(chan bool)
   150  	go func() {
   151  		select {
   152  		case <-closeChan:
   153  			stopWatchChan <- true
   154  		case <- stopWrapChan:
   155  			stopWatchChan <- true
   156  		case <- stopWatchChan:
   157  		}
   158  	}()
   159  	defer close(stopWrapChan)
   160  
   161  	for {
   162  		// Read all nodes for the lock.
   163  		resp, err := h.client.Get(keypath, true, true)
   164  		if err != nil {
   165  			return fmt.Errorf("lock watch lookup error: %s", err.Error())
   166  		}
   167  		nodes := lockNodes{resp.Node.Nodes}
   168  		prevIndex, modifiedIndex := nodes.PrevIndex(index)
   169  
   170  		// If there is no previous index then we have the lock.
   171  		if prevIndex == 0 {
   172  			return nil
   173  		}
   174  
   175  		// Wait from the last modification of the node.
   176  		waitIndex := modifiedIndex + 1
   177  
   178  		resp, err = h.client.Watch(path.Join(keypath, strconv.Itoa(prevIndex)), uint64(waitIndex), false, nil, stopWatchChan)
   179  		if err == etcd.ErrWatchStoppedByUser {
   180  			return fmt.Errorf("lock watch closed")
   181  		} else if err != nil {
   182  			return fmt.Errorf("lock watch error: %s", err.Error())
   183  		}
   184  	}
   185  }