code: func (s *Semaphore) Release() error {
  	// Hold the lock as we try to release
  	s.l.Lock()
  	defer s.l.Unlock()
  
  	// Ensure the lock is actually held
  	if !s.isHeld {
  		return ErrSemaphoreNotHeld
  	}
  
  	// Set that we no longer own the lock
  	s.isHeld = false
  
  	// Stop the session renew
  	if s.sessionRenew != nil {
  		defer func() {
  			close(s.sessionRenew)
  			s.sessionRenew = nil
  		}()
  	}
  
  	// Get and clear the lock session
  	lockSession := s.lockSession
  	s.lockSession = ""
  
  	// Remove ourselves as a lock holder
  	kv := s.c.KV()
  	key := path.Join(s.opts.Prefix, DefaultSemaphoreKey)
  READ:
  	pair, _, err := kv.Get(key, nil)
  	if err != nil {
  		return err
  	}
  	if pair == nil {
  		pair = &KVPair{}
  	}
  	lock, err := s.decodeLock(pair)
  	if err != nil {
  		return err
  	}
  
  	// Create a new lock without us as a holder
  	if _, ok := lock.Holders[lockSession]; ok {
  		delete(lock.Holders, lockSession)
  		newLock, err := s.encodeLock(lock, pair.ModifyIndex)
  		if err != nil {
  			return err
  		}
  
  		// Swap the locks
  		didSet, _, err := kv.CAS(newLock, nil)
  		if err != nil {
  			return fmt.Errorf("failed to update lock: %v", err)
  		}
  		if !didSet {
  			goto READ
  		}
  	}
  
  	// Destroy the contender entry
  	contenderKey := path.Join(s.opts.Prefix, lockSession)
  	if _, err := kv.Delete(contenderKey, nil); err != nil {
  		return err
  	}
  	return nil
  }
docs: // Release is used to voluntarily give up our semaphore slot. It is
  // an error to call this if the semaphore has not been acquired.