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 }