unknwon/the-way-to-go_ZH_CN · error

key already exists

Error message

key already exists

What it means

Returned by URLStore.Set when the proposed short key is already present: Set takes the write lock, checks if _, present := s.urls[*key]; present, and refuses to overwrite, returning errors.New("key already exists"). By design, Put loops forever doing *key = genKey(s.Count()) until Set succeeds — so seeing this error escape means the retry loop was bypassed or Count() no longer reflects the stored keys.

Source

Thrown at eBook/19.8.md:68

		*key = genKey(s.Count())
			if err := s.Set(key, url); err == nil {
			break
		}
	}
	if s.save != nil {
		s.save <- record{*key, *url}
	}
	return nil
}
```

`Put()` 调用 `Set()`,由于后者也要做调整,`key` 和 `url` 参数现在是指针类型,还必须返回 `error` 取代 `boolean`:
```go
func (s *URLStore) Set(key, url *string) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if _, present := s.urls[*key]; present {
		return errors.New("key already exists")
	}
	s.urls[*key] = *url
	return nil
}
```

同样,当从 `load()` 调用 `Set()` 时,也必须做调整:
```go
s.Set(&r.Key, &r.URL)
```

还必须修改 HTTP 处理函数以适应 `URLStore` 上的更改。`Redirect()` 处理函数现在返回 `URLStore` 给出错误的字符串形式:
```go
func Redirect(w http.ResponseWriter, r *http.Request) {
	key := r.URL.Path[1:]
	var url string
	if err := store.Get(&key, &url); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)

View on GitHub (pinned to 7a54d34d36)

Solutions

  1. Never call Set directly from request handlers — always go through Put's for-loop, which re-derives *key = genKey(s.Count()) after every Set error and self-heals collisions
  2. After load(), sync the counter so genKey starts past existing keys: derive Count() from len(s.urls) or persist the counter alongside the records
  3. If your edit removed the loop around Set, restore it: for { *key = genKey(s.Count()); if err := s.Set(key, url); err == nil { break } }
  4. For long-lived stores, replace count-derived keys with a monotonic or random scheme so keys are never revisited

Example fix

// before: single attempt, collision escapes to the caller
if err := s.Set(&key, &url); err != nil {
	return err // 'key already exists' reaches the HTTP layer
}

// after: book's design — regenerate until a free key is found
for {
	*key = genKey(s.Count())
	if err := s.Set(key, url); err == nil {
		break
	}
}
Defensive patterns

Strategy: retry

Validate before calling

if _, present := urls[key]; present {
	// pick the next candidate before calling Set
	key = genKey(count + 1)
}

Try / catch

for {
	*key = genKey(s.Count())
	if err := s.Set(key, url); err == nil {
		break // key accepted
	}
	// 'key already exists': loop regenerates a fresh candidate
}

Prevention

When it happens

Trigger: load() replays a persisted data file into Set while genKey still derives keys from s.Count(), so regenerated keys collide with already-loaded ones; or a direct call to Set from handler code skips Put's regeneration loop; duplicated lines in the store file also make Count() drift from len(s.urls).

Common situations: Restarting the service with a populated data_store.txt; counter/len divergence after partial loads or failed saves; deleting entries without adjusting the counter; shortening genKey's alphabet so its cycle is exhausted and collisions become routine.

Related errors


AI-assisted analysis of unknwon/the-way-to-go_ZH_CN@7a54d34d36 (2026-08-15). Data as JSON: /api/errors/2dd7e60dbd62eeb2. Report an issue: GitHub.