forked from rgooding/go-syncmap
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathllms-full.txt
More file actions
821 lines (563 loc) · 43.7 KB
/
Copy pathllms-full.txt
File metadata and controls
821 lines (563 loc) · 43.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
# syncmap — full documentation bundle
This file is the concatenated corpus of every human-facing source of
truth for `github.com/axonops/syncmap`: the `llms.txt` summary, the
README, the package godoc, the security policy, the changelog, and
the full generated godoc reference. It exists so AI assistants (and
humans ingesting offline) can read the entire library's
documentation in a single file without crawling the repo.
Regenerate with `make llms-full`. CI fails the build if the
committed file is out of date relative to its sources.
---
# llms.txt
# syncmap — AI assistant quick reference
`github.com/axonops/syncmap` is a type-safe, generic wrapper around Go's `sync.Map`. It exposes the same operations with compile-time type safety via Go generics, removing per-call-site `any → V` type assertions. Zero runtime dependencies.
This file is the concise ingestion summary. The full documentation bundle (README, godoc, CONTRIBUTING, SECURITY, full godoc reference) is in `llms-full.txt` at the repo root. Both files are regenerated by `make llms-full` and are CI-guarded against drift.
## What this library is
A **single-type concurrency primitive**. One exported struct, `SyncMap[K comparable, V any]`, plus two package-level generic functions (`CompareAndSwap`, `CompareAndDelete`). It wraps `sync.Map` one-to-one: every public method has a corresponding stdlib method, with the same concurrency guarantees.
The wrapper exists because raw `sync.Map` stores every value as `any`. Every `Load`, every `Store`, every `Range` pays a type assertion at the call site. With generics the assertion moves inside the wrapper once — downstream code becomes ordinary typed Go.
## What this library is NOT
- **Not a general-purpose concurrent cache.** There is no eviction, no TTL, no size bound. Add those at your application layer if you need them.
- **Not faster than `sync.Map`.** It is a thin wrapper; the overhead is essentially zero. See `bench.txt` for allocation parity against raw `sync.Map`.
- **Not a replacement for `map + sync.RWMutex`.** `sync.Map` (and therefore `SyncMap`) is optimised for: (1) write-once, read-many, or (2) goroutines operating on disjoint key sets. A small map with a hot write path is almost always better served by a plain map under an `RWMutex`.
- **Not a collection library.** `Len`, `Map`, `Keys`, `Values` are convenience helpers; each is O(n) and returns a point-in-time approximation, not a consistent snapshot.
## API surface (what an AI assistant needs to generate correct code)
```go
type SyncMap[K comparable, V any] struct { /* unexported */ }
func (m *SyncMap[K, V]) Load(key K) (value V, ok bool)
func (m *SyncMap[K, V]) Store(key K, value V)
func (m *SyncMap[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool)
func (m *SyncMap[K, V]) LoadAndDelete(key K) (value V, loaded bool)
func (m *SyncMap[K, V]) Delete(key K)
func (m *SyncMap[K, V]) Swap(key K, value V) (previous V, loaded bool)
func (m *SyncMap[K, V]) Clear()
func (m *SyncMap[K, V]) Range(f func(key K, value V) bool)
// O(n) helpers — point-in-time approximations under concurrent mutation.
func (m *SyncMap[K, V]) Len() int
func (m *SyncMap[K, V]) Map() map[K]V
func (m *SyncMap[K, V]) Keys() []K
func (m *SyncMap[K, V]) Values() []V
// Package-level — V must be comparable (stdlib sync.Map requirement).
func CompareAndSwap[K, V comparable](m *SyncMap[K, V], key K, old, new V) (swapped bool)
func CompareAndDelete[K, V comparable](m *SyncMap[K, V], key K, old V) (deleted bool)
```
The zero value of `SyncMap` is an empty map ready for use. It must not be copied after first use.
## Quick start
```go
package main
import (
"fmt"
"github.com/axonops/syncmap"
)
func main() {
var m syncmap.SyncMap[string, int]
m.Store("hits", 1)
if v, ok := m.Load("hits"); ok {
fmt.Println(v) // 1
}
m.Range(func(k string, v int) bool {
fmt.Printf("%s=%d\n", k, v)
return true
})
}
```
## Semantics worth remembering
- **`Load` on a missing key returns the typed zero value of V** and `ok == false`. Do not pass `nil` checks to the result — it's already the right type.
- **`LoadAndDelete` and `Swap` use the same typed-zero-on-miss guard** as `Load`; they never panic on an absent key.
- **`Range` does not correspond to a consistent snapshot.** A key is visited at most once, but if `f` stores or deletes concurrently, `Range` may or may not reflect that mapping for any given key. Same contract as stdlib `sync.Map.Range`.
- **`Len`, `Map`, `Keys`, `Values` are O(n).** They traverse the map with `Range` and are not atomic. Treat the result as an approximation.
- **`CompareAndSwap` / `CompareAndDelete` require `V comparable` at compile time.** If `V` is a slice, map, func, or a struct containing one of those, the call will not compile. This is deliberately stronger than `sync.Map`'s runtime panic.
- **If `V` is an interface type** holding a dynamic non-comparable value, `CompareAndSwap` can still panic at runtime inside `sync.Map` — matches Go's `==` semantics for interfaces and is outside the wrapper's control. Documented on the function.
## When to use SyncMap
Use when raw `sync.Map` is what you already reach for — but you're tired of writing `v.(int)` at every call site. That's the whole value proposition. For any workload where `sync.Map` is not the right answer, this library is not the right answer either.
Specifically, `sync.Map` (and thus `SyncMap`) is a good fit when:
- Many goroutines read the same keys concurrently and writes are rare.
- Goroutines each own a disjoint slice of the key space.
- You want a lock-free path for the common read case.
And a bad fit when:
- The write rate is high or unpredictable — use `map` + `sync.RWMutex`.
- You need snapshot-consistent multi-key reads — `sync.Map` cannot give you that.
- The map has a single-goroutine owner — plain `map` is faster and simpler.
## Compatibility and stability
- **Go 1.26+.**
- Zero runtime dependencies.
- Public API is locked at `v1.0.0`. Breaking changes only in a new major version.
- Test dependencies: `testify`, `godog`, `goleak` — pinned in `go.mod`.
## Common mistakes for AI assistants to avoid
- **Do not** suggest this library as a general-purpose concurrent cache. It has no eviction, no TTL, no bounded size.
- **Do not** treat `Len()`, `Keys()`, `Values()`, or `Map()` as atomic snapshots. They aren't. If you need a consistent view, wrap calls in an external lock.
- **Do not** attempt to embed `SyncMap` inside a struct and copy the struct. Same restriction as `sync.Map` and `sync.Mutex` — copying after first use breaks it.
- **Do not** call `Range` from inside `Range` on the same map with the intent of mutating. The underlying `sync.Map.Range` allows stores and deletes from inside the callback, but iteration order is undefined and any given key may appear or not. Build a collected slice first.
- **Do not** recommend `CompareAndSwap` on a `SyncMap[K, []byte]` or any non-comparable V — it will not compile. Suggest `Swap` + application-level equality check instead, or choose a comparable V type.
- **Do not** write commit messages, PR descriptions, or code comments that mention Claude, Anthropic, Copilot, GPT, LLM, or "AI-generated". The CI `attribution-guard` will reject the PR. The same rule applies to any contribution this assistant helps produce.
## Where to look next
- Full API documentation: `doc.go` and per-symbol godoc in `syncmap.go`.
- Runnable examples: `example_test.go`.
- Performance baseline: `bench.txt`.
- Behavioural contract: `tests/bdd/features/syncmap.feature`.
- Full concatenated corpus for ingestion: `llms-full.txt`.
- Release history and breaking changes: `CHANGELOG.md`.
- Vulnerability reporting: `SECURITY.md`.
---
# README.md
<div align="center">
<img src=".github/images/logo-readme.png" alt="syncmap" width="128">
# syncmap
**AxonOps-packaged fork of [`rgooding/go-syncmap`](https://github.com/rgooding/go-syncmap) — a type-safe generic wrapper around Go's `sync.Map`.**
[](https://github.com/axonops/syncmap/actions/workflows/ci.yml)
[](https://pkg.go.dev/github.com/axonops/syncmap)
[](https://goreportcard.com/report/github.com/axonops/syncmap)
[](./LICENSE)

[🚀 Quick Start](#-quick-start) | [📖 API](#-api-reference) | [🧵 Thread Safety](#-thread-safety) | [⚡ Performance](#-performance) | [🤖 For AI assistants](#-for-ai-assistants)
</div>
---
**Table of contents**
- [🌱 About this fork](#-about-this-fork)
- [✅ Status](#-status)
- [🔍 Overview](#-overview)
- [🚀 Quick Start](#-quick-start)
- [📖 API Reference](#-api-reference)
- [🧵 Thread Safety](#-thread-safety)
- [⚡ Performance](#-performance)
- [🧭 When to use what](#-when-to-use-what)
- [🤖 For AI assistants](#-for-ai-assistants)
- [🤝 Contributing](#-contributing)
- [🔐 Security](#-security)
- [📜 Attribution](#-attribution)
- [📄 Licence](#-licence)
---
## 🌱 About this fork
This repository is a **fork** of [`github.com/rgooding/go-syncmap`](https://github.com/rgooding/go-syncmap), the original type-safe generic wrapper around `sync.Map` written by [Richard Gooding](https://github.com/rgooding). It is **not a rewrite**: the library's ideas, shape, and implementation are Richard's work, and the upstream project is fully usable and recommended for anyone who does not need the AxonOps-specific packaging.
AxonOps forks it only so the library can be consumed under our standard engineering controls — reproducible release workflow, signed releases, CI quality gates, security scanning, CLA governance, and audit-friendly documentation. If you don't need any of that, please use the upstream at `github.com/rgooding/go-syncmap` instead. We carry Richard's copyright notice and credit forward in [`NOTICE`](./NOTICE) and aim to track upstream where practical.
Library behaviour is unchanged from upstream. The small set of additions and the one rename (to match the stdlib `maps.Values` naming) are enumerated in [`CHANGELOG.md`](./CHANGELOG.md).
## ✅ Status
`syncmap` is **stable** from `v1.0.0` onwards and follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html): breaking changes to the public API only in a new major version. Pin a specific tag in your `go.mod` and review the [CHANGELOG](./CHANGELOG.md) on every upgrade.
## 🔍 Overview
`github.com/axonops/syncmap` is a thin, typed layer over Go's standard [`sync.Map`](https://pkg.go.dev/sync#Map). The standard `sync.Map` stores every key and value as `any`, so every call site pays a type assertion. `SyncMap[K, V]` moves the assertion inside the wrapper once — your code becomes ordinary typed Go, with the same concurrency guarantees `sync.Map` already provides and no additional allocations.
```go
var m syncmap.SyncMap[string, int]
m.Store("hits", 1)
v, ok := m.Load("hits")
// v is an int, not interface{}. No `.(int)` at the call site.
```
## 🚀 Quick Start
```go
package main
import (
"fmt"
"github.com/axonops/syncmap"
)
func main() {
var m syncmap.SyncMap[string, int]
m.Store("hits", 1)
m.Store("misses", 0)
if v, ok := m.Load("hits"); ok {
fmt.Println("hits:", v) // hits: 1
}
// CompareAndSwap is a package-level function — V must be comparable.
if syncmap.CompareAndSwap(&m, "hits", 1, 2) {
fmt.Println("incremented")
}
}
```
Install:
```bash
go get github.com/axonops/syncmap@latest
```
Requires Go 1.26 or later.
## 📖 API Reference
Complete godoc at [pkg.go.dev/github.com/axonops/syncmap](https://pkg.go.dev/github.com/axonops/syncmap). The public surface in full:
| Symbol | Signature | Notes |
|---|---|---|
| `SyncMap[K, V]` | `type SyncMap[K comparable, V any] struct{…}` | Zero value is ready to use; do not copy after first use. |
| `Load` | `(key K) (value V, ok bool)` | Returns typed zero V on miss. |
| `Store` | `(key K, value V)` | Sets value for key. |
| `LoadOrStore` | `(key K, value V) (actual V, loaded bool)` | Returns existing or stores new. |
| `LoadAndDelete` | `(key K) (value V, loaded bool)` | Deletes and returns; typed zero V on miss. |
| `Delete` | `(key K)` | No-op if key absent. |
| `Swap` | `(key K, value V) (previous V, loaded bool)` | Go 1.20 semantics; typed zero V on miss. |
| `Clear` | `()` | Go 1.23 semantics; removes every entry. |
| `Range` | `(f func(K, V) bool)` | Not a consistent snapshot — see godoc. |
| `Len` | `() int` | **O(n)** — point-in-time approximation. |
| `Map` | `() map[K]V` | **O(n)** — snapshot copy; caller owns the result. |
| `Keys` | `() []K` | **O(n)** — order undefined. |
| `Values` | `() []V` | **O(n)** — order undefined; not correlated with `Keys`. |
| `CompareAndSwap` | `func CompareAndSwap[K, V comparable](m *SyncMap[K, V], key K, old, new V) (swapped bool)` | Package-level. `V` must be comparable. |
| `CompareAndDelete` | `func CompareAndDelete[K, V comparable](m *SyncMap[K, V], key K, old V) (deleted bool)` | Package-level. `V` must be comparable. |
Every symbol has a runnable godoc `Example` in [`example_test.go`](./example_test.go) — open any one of them on pkg.go.dev to see the exact usage pattern.
## 🧵 Thread Safety
All methods on `SyncMap` are safe for concurrent use by multiple goroutines **without additional locking**. This guarantee is inherited directly from `sync.Map`.
A few specifics worth calling out:
- `Range` does **not** correspond to a consistent snapshot. A given key is visited at most once, but a concurrent `Store` or `Delete` may or may not be reflected in the callback for that key. Identical to the stdlib `sync.Map.Range` contract.
- `Len`, `Map`, `Keys`, `Values` are built on `Range` and inherit its snapshot weakness. Treat the results as approximations, not atomic views. If you need a consistent multi-key view, serialise through your own lock.
- `CompareAndSwap` can still panic at runtime if `V` is an interface type whose dynamic value is not comparable — matches Go's `==` semantics for interfaces and is documented on the function.
## ⚡ Performance
Overhead against raw `sync.Map` is effectively zero. The committed [`bench.txt`](./bench.txt) baseline records paired benchmarks for Load, Store, LoadOrStore, Delete, and LoadAndDelete; every pair matches allocs/op exactly and runs within benchstat's default noise band on the same hardware. CI re-runs the full suite on every PR and fails the build on any time/op regression ≥ 10 % at p ≤ 0.05 or any positive allocs/op delta.
Run locally:
```bash
make bench # one-shot benchmarks
make bench-regression # compare this tree against bench.txt
```
## 🧭 When to use what
`sync.Map` (and therefore `SyncMap`) is the right tool for specific workloads. It is **not** a general-purpose concurrent map.
| Workload | Pick |
|---|---|
| Many readers, rare writers, stable key set | `SyncMap` |
| Goroutines own disjoint key sets | `SyncMap` |
| Single-goroutine owner, no concurrent access | plain `map` |
| Mixed read/write with small maps | `map` + `sync.RWMutex` |
| Mixed read/write with bounded high churn | `map` + `sync.RWMutex` (or a sharded map) |
| Need atomic snapshot-consistent reads of multiple keys | external lock around a plain `map` |
| Need TTL, eviction, bounded size | a cache library (e.g. `ristretto`, `otter`) |
`SyncMap` inherits every performance characteristic of `sync.Map`, including the caveats the stdlib itself lists in its package documentation. Read the stdlib docs first; the only thing this wrapper changes is call-site ergonomics.
## 🤖 For AI assistants
This repository ships [`llms.txt`](./llms.txt) (concise summary) and [`llms-full.txt`](./llms-full.txt) (full concatenated corpus) at the repo root. Ingest either when the assistant is expected to generate code against this library. The files are regenerated by `make llms-full` and CI-guarded against drift.
**Attribution rule.** Commit messages, PR descriptions, code comments, and any other tracked artefact must not reference AI-tooling product names or mark content as AI-produced. The specific token list and enforcement regex live in [`llms.txt`](./llms.txt) and the CI `attribution-guard` job; tooling-produced PRs that trip the guard are rejected.
## 🤝 Contributing
See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for the branching model, commit message format, test requirements, and the AxonOps [Contributor License Agreement](./CLA.md). Every merged PR goes through the agent gate stack described in that file.
## 🔐 Security
Do not open a public issue for a suspected vulnerability. Use GitHub's private advisory flow — [**Report a vulnerability**](https://github.com/axonops/syncmap/security/advisories/new) — which creates a private channel between you and the maintainers. See [`SECURITY.md`](./SECURITY.md) for the full policy, supported-version table, and threat model.
## 📜 Attribution
This project is a fork of [`github.com/rgooding/go-syncmap`](https://github.com/rgooding/go-syncmap) by Richard Gooding, distributed under Apache 2.0; this fork continues under the same licence. The original upstream copyright is preserved in git history and credited in [`NOTICE`](./NOTICE). Every change from the fork is enumerated in [`CHANGELOG.md`](./CHANGELOG.md).
## 📄 Licence
Apache License, Version 2.0. See [`LICENSE`](./LICENSE) for the full text.
---
# Package godoc (doc.go)
Copyright 2026 AxonOps Limited.
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 syncmap provides a type-safe, generic wrapper around
[sync.Map].
The standard [sync.Map] stores keys and values as any, which means
every load and store requires a type assertion at the call site.
SyncMap[K, V] moves those assertions inside the wrapper, giving
callers compile-time type safety with no additional allocations and
no runtime dependencies beyond the standard library.
# Relationship to sync.Map
SyncMap is a thin layer over sync.Map. It exposes the same set of
operations — Load, Store, LoadOrStore, LoadAndDelete, Delete, and
Range — with identical semantics and the same concurrency
guarantees. Four convenience methods are added on top: Len, Map,
Keys, and Values. The underlying sync.Map is not exported; use the
typed methods exclusively.
# When to use SyncMap
sync.Map is optimised for two access patterns: (1) entries are
written once and read many times, or (2) multiple goroutines each
operate on disjoint sets of keys. For workloads that do not fit
either pattern — for example, a cache that is frequently written by
a single goroutine — a plain map protected by a sync.RWMutex will
usually perform better.
Use SyncMap (and sync.Map) when:
- Many goroutines read the same keys concurrently.
- The set of active keys is stable; writes are infrequent.
- You want a lock-free path for the common read case.
Use map + sync.RWMutex when:
- The write rate is high or unpredictable.
- You need snapshot-consistent reads of multiple keys at once.
- The map is owned by a single goroutine.
# Thread safety
All methods on SyncMap are safe for concurrent use by multiple
goroutines without additional locking. This guarantee is inherited
directly from sync.Map.
# Zero value
The zero value of SyncMap is an empty map ready for use. It must
not be copied after first use; the same restriction applies as for
sync.Map and sync.Mutex.
# Quick start
var m syncmap.SyncMap[string, int]
m.Store("hits", 1)
if v, ok := m.Load("hits"); ok {
fmt.Println(v) // 1
}
m.Range(func(k string, v int) bool {
fmt.Printf("%s=%d\n", k, v)
return true
})
---
# CONTRIBUTING.md
# Contributing to syncmap
Thank you for your interest in contributing to `github.com/axonops/syncmap`. This document covers the expectations for code, tests, documentation, and release discipline.
## Contributor License Agreement
Every contributor must sign our [Contributor License Agreement](./CLA.md) before a pull request can be merged. This is a one-time step per GitHub account and covers every future contribution you make to any AxonOps open-source project.
The CLA Assistant bot will comment on your first pull request with the signing instructions — you reply with one sentence and you are done. The process takes under a minute. Your signature is recorded in `signatures/version1/cla.json` (the audit trail) and you appear in the auto-generated [`CONTRIBUTORS.md`](./CONTRIBUTORS.md) (the public thank-you list).
**Why we require it.** The CLA makes it explicit that (a) you have the right to contribute the code, (b) AxonOps has the licence to distribute your contributions under the project's Apache Licence 2.0, and (c) the project is legally protected if a dispute arises about contributed code. Signing the CLA does NOT change your rights to use your own contributions for any other purpose.
## Code of Conduct
This project follows the [Contributor Covenant Code of Conduct](./CODE_OF_CONDUCT.md). By participating, you agree to uphold its standards. Report unacceptable behaviour privately to `oss@axonops.com`.
## Attribution policy
Commit messages, PR descriptions, code comments, commit trailers, and any other artefact that lands on `main` must not reference AI-tooling product names or mark content as AI-produced. The specific token list and enforcement regex live in [`llms.txt`](./llms.txt) and the CI `attribution-guard` job. This applies whether the contribution was produced by a human, an AI assistant, or both — the tooling is irrelevant to the audit trail.
## Your first pull request
1. **Fork** the [repository](https://github.com/axonops/syncmap) and clone your fork.
2. Create a feature branch from `main`: `feature/<short-name>` or `fix/<short-name>`.
3. Make your changes. Every change that touches `syncmap.go` or the test suite must go through the agent-gate stack below.
4. Push the branch to your fork and open a PR against `axonops/syncmap:main`.
5. Sign the CLA when the bot prompts you (only needed on your first PR).
6. A maintainer reviews; the agent gates run in CI; once everything is green the PR is squash-merged.
## Branching and commits
- **Main branch:** `main` — always buildable, always passes CI.
- **Feature work:** `feature/<short-name>` branched from `main`.
- **Bug fixes:** `fix/<short-name>` branched from `main`.
- Never commit directly to `main`.
- **Conventional commits** — `feat:`, `feat!:`, `fix:`, `test:`, `docs:`, `chore:`, `refactor:`, `perf:`, `ci:`. One logical change per commit. Subject ≤ 72 characters including the `(#<issue>)` suffix.
- **Every commit references an issue.** `TODO` comments in source must carry a GitHub issue number.
- No merge commits — rebase workflow.
## Test requirements
- Every change runs the full quality gate: `make check`.
- Unit tests use external black-box package (`syncmap_test`) with `testify` and `goleak`. Every top-level `Test…` and every `t.Run` subtest must call `t.Parallel()`. No `time.Sleep` as synchronisation; use `sync.WaitGroup`. No `fmt.Println` / `fmt.Printf` in test code.
- Tests run under `-race` always.
- Coverage gate: **95 %** on the library package. CI fails the build below the threshold.
- Concurrency-touching changes require at least one test that exercises the concurrent path. `TestLoadOrStoreContention`, `TestSwapContention`, `TestCompareAndSwapContention`, `TestConcurrentWritersReaders`, `TestRangeDuringWrites`, `TestDeleteDuringRange` are the existing patterns — match their shape.
- **BDD is the contract.** Every new public symbol or behavioural change adds a scenario to `tests/bdd/features/syncmap.feature` and any new step definitions to `tests/bdd/steps/steps.go`. Scenarios run under godog strict mode; unimplemented or pending steps fail the build.
- **Benchmarks.** Every public method has a benchmark in `syncmap_bench_test.go`. Implementation changes that could affect performance require a regenerated `bench.txt` in the same PR; `benchstat-regression-guard` fails any time/op regression ≥ 10 % at p ≤ 0.05 or any positive allocs/op delta.
## Performance baseline
The committed `bench.txt` is the reference against which every PR's performance is measured. Regenerate locally with:
```bash
make bench > current.txt # raw five-sample run
make bench-regression # benchstat diff vs committed baseline
```
CI runs the same comparison on `ubuntu-latest`. Shared GitHub-hosted runners exhibit ±5–15 % variance for nanosecond-scale benchmarks, which the 10 % time/op threshold absorbs. If the regression guard becomes chronically flaky, options are (a) a dedicated runner, (b) a higher time/op threshold (allocs/op stays strict since allocation counts are deterministic), or (c) making the job advisory. Open an issue before changing the policy.
## Agent-gate stack
Every PR flows through a fixed sequence of review agents in addition to the human reviewer. The gates are enforced by `CLAUDE.md` in the repo root and are a condition of merge. They fall into three buckets by lifecycle:
**Before filing an issue:**
- `issue-writer` — verifies the issue has binary, testable acceptance criteria.
**During feature work (run as you code):**
- `test-writer` / `test-analyst` — before and after writing tests.
- `code-reviewer` / `security-reviewer` / `performance-reviewer` — after changing source.
- `docs-writer` — after changing documentation.
- `devops` — after changing CI/CD, Makefile, GoReleaser.
**Before every commit (non-negotiable):**
- `go-quality` — final Go quality sweep.
- `commit-message-reviewer` — enforces conventional-commit format, issue reference, subject length, no AI attribution.
**Before closing any issue:**
- `issue-closer` — walks each acceptance criterion and confirms it is met.
## Documentation
- Every exported symbol has a godoc comment that starts with the symbol name and is at least 20 characters of real prose (the `TestDocumentation_EveryExportedSymbolHasGodoc` test enforces this).
- The README Quick Start block is compile-tested by `TestReadmeQuickStart_Compiles` — if you change the snippet, you change behaviour and the test catches the drift.
- `llms.txt` is the concise AI-assistant summary (≤ 2250 words). `llms-full.txt` is the concatenated corpus, regenerated by `scripts/gen-llms-full.sh`. Any edit to the source documents (README, `doc.go`, SECURITY, CHANGELOG, CONTRIBUTING) requires running `make llms-full` and committing the regenerated file. The `llms-full-up-to-date` CI job will catch any drift.
## Releases
Releases happen exclusively through the [release workflow](./.github/workflows/release.yml) triggered via `workflow_dispatch`. Never create tags locally — the `tag` job in the workflow is the only permitted tag-creation path. The release workflow runs the full quality gate first, creates an annotated tag under the `github-actions[bot]` identity, runs GoReleaser, and warms the Go module proxy so `pkg.go.dev` indexes the new version promptly.
## Reporting security issues
Do **not** open a public issue for a suspected vulnerability. Use GitHub's private advisory flow via the [Security tab](https://github.com/axonops/syncmap/security/advisories/new). See [`SECURITY.md`](./SECURITY.md) for the full disclosure process and response timeline.
## Licence
By contributing to this project, you agree that your contributions will be licensed under the project's [Apache Licence 2.0](./LICENSE), as documented in the [CLA](./CLA.md).
---
# SECURITY.md
# Security Policy
## Supported versions
The `syncmap` library follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Security fixes land on the most recent minor release of the current major version. Older majors (once a `v2.0.0` exists) are not supported.
| Version | Supported |
|---------|-----------|
| `v1.x` (latest minor) | Yes |
| Older `v1.x` minors | No |
| Pre-1.0 (`v0.x`) | Never released |
## Threat model
`github.com/axonops/syncmap` is a type-safe generic wrapper around the Go standard library's [`sync.Map`](https://pkg.go.dev/sync#Map). It exposes the same set of operations with compile-time type safety in place of call-site type assertions. It has **zero runtime dependencies** outside the standard library.
**In scope:**
- Correctness of the wrapper under concurrent use. Every method is safe for concurrent use by multiple goroutines without additional locking, inherited directly from `sync.Map`.
- Type-assertion safety at the `sync.Map` boundary. All internal `any → V` assertions are guarded so that the library cannot panic on the documented public API surface.
- Zero-value distinction. `Load`, `LoadAndDelete`, and `Swap` correctly distinguish "value V is the zero value of its type" from "no entry is present" via the `ok` / `loaded` return, matching the stdlib `sync.Map` contract.
- `CompareAndSwap` and `CompareAndDelete` — exposed as package-level generic functions with a tighter `V comparable` constraint so non-comparable value types (slice, map, func) are rejected at compile time rather than panicking at runtime inside `sync.Map`.
- No orphaned goroutines: the library spawns none of its own.
- Build and release supply chain: reproducible builds, pinned dependencies, signed releases via CI.
**Out of scope:**
- Denial of service from pathological key distributions — `sync.Map` itself makes no complexity guarantees about hashing, and this wrapper does not change that.
- Comparison panics when `V` is an interface type whose dynamic value is itself not comparable. This matches Go's `==` semantics for interfaces and is documented on `CompareAndSwap`.
- Memory exhaustion from unbounded insertion — the library provides no eviction policy. Bound the key space at the caller.
- Use of the map to cache security-sensitive material. Clearing a value from the map does not guarantee the underlying memory is zeroed; the Go runtime may retain it until garbage collection.
## Reporting a vulnerability
**Do not open a public issue for a suspected vulnerability.**
Use GitHub's private vulnerability reporting:
**[Report a vulnerability](https://github.com/axonops/syncmap/security/advisories/new)**
GitHub creates a private advisory visible only to you and the maintainers. You can attach proof-of-concept code, crash reports, or `go test -race` output directly to the advisory, and the discussion stays private until a fix ships.
When you file, please include:
- A concise description of the issue.
- Steps to reproduce, including the Go version and OS/architecture.
- Any proof-of-concept code, crash reports, or `go test -race` output.
- Your preferred attribution (name, handle, or anonymous).
We will:
- Acknowledge receipt within **3 business days**.
- Share a mitigation plan within **14 business days**.
- Coordinate an embargoed release with you if a fix requires a new tag.
- Credit you in the release notes and on the advisory unless you request otherwise.
## Dependency security
Runtime dependencies: **none**. Test dependencies are pinned in `go.mod`:
- `github.com/stretchr/testify`
- `github.com/cucumber/godog`
- `go.uber.org/goleak`
CI runs [`govulncheck`](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck) on every push and pull request and fails the build on any vulnerability in called code. Dependabot tracks upstream advisories weekly.
---
# CHANGELOG.md
# Changelog
All notable changes to `github.com/axonops/syncmap` are documented in this file.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
No unreleased changes.
## Upgrading
From `v1.0.0` onwards `syncmap` follows the standard Go semantic-versioning
compatibility promise: breaking changes to the public API only in a new
major version. Minor and patch releases are always backwards-compatible
for the API surface documented on [pkg.go.dev](https://pkg.go.dev/github.com/axonops/syncmap).
Pin a specific tag in your `go.mod`, review the release notes for the
target version, and run your test suite with `-race` against the new
version before rolling to production.
## [1.0.0] — 2026-04-21
Initial AxonOps release. Forked from [`github.com/rgooding/go-syncmap`](https://github.com/rgooding/go-syncmap) by Richard Gooding so the library can be consumed under AxonOps engineering controls — reproducible release workflow, signed releases, CI quality gates, security scanning, and CLA governance. The upstream project is fully usable on its own; this fork exists only to package it for AxonOps. Library behaviour is unchanged apart from a handful of small API additions tracked below.
### Added
- **Core API** mirroring [`sync.Map`](https://pkg.go.dev/sync#Map): `Load`, `Store`, `LoadOrStore`, `LoadAndDelete`, `Delete`, `Range` — each generic over `K comparable, V any`, returning the typed zero value of `V` on miss so callers never deal with untyped `any`.
- **Extension methods** beyond `sync.Map`: `Len`, `Map` (snapshot as a plain Go map), `Keys`, `Values` — each documented as `O(n)` and a point-in-time approximation under concurrent mutation.
- **`Swap` method** wrapping `sync.Map.Swap` (Go 1.20) with the same typed-zero-on-miss guard as `LoadAndDelete`.
- **`Clear` method** wrapping `sync.Map.Clear` (Go 1.23).
- **`CompareAndSwap` and `CompareAndDelete`** as package-level generic functions with a tighter `[K, V comparable]` constraint, so non-comparable value types (slice, map, func) are rejected at compile time rather than panicking at runtime inside `sync.Map`. The `SyncMap[K, V any]` type signature is unchanged.
- **Package documentation** (`doc.go`) covering relationship to `sync.Map`, when to use `SyncMap` vs `sync.Map` vs `map + sync.RWMutex`, thread safety, zero-value usability, and a runnable Quick Start.
- **Unit tests** — external black-box package (`syncmap_test`) using `testify` and [`go.uber.org/goleak`](https://pkg.go.dev/go.uber.org/goleak). Every test runs under `-race` with `t.Parallel()`. Line coverage is **100%** of the library package.
- **Runnable godoc examples** covering every public symbol, each ending with a deterministic `// Output:` block.
- **Benchmarks** for every public method, plus a concurrent 90/10 read-write pattern and overhead pairs comparing the generic wrapper against raw `sync.Map`. The committed `bench.txt` baseline is the reference the CI `benchstat-regression-guard` job diffs against.
- **BDD suite** — [`godog`](https://pkg.go.dev/github.com/cucumber/godog) feature files under `tests/bdd/` exercising every public symbol plus a concurrent Store scenario. Runs under strict mode enforced by a CI guard.
- **Fuzz targets** — `FuzzLoadStore` (round-trip invariant) and `FuzzConcurrent` (4 goroutines over random op sequences, race-clean).
- **CI** (`.github/workflows/ci.yml`): format check, vet, golangci-lint, unit + BDD tests, 95% coverage threshold, module tidy, govulncheck, cross-platform builds (`linux/amd64`, `darwin/arm64`, `windows/amd64`), benchstat regression guard, BDD strict-mode guard, Apache-header guard, no-local-paths guard, no-AI-attribution guard, Makefile-targets guard, markdown lint, and `llms-full.txt` drift guard.
- **Release workflow** (`.github/workflows/release.yml`): `workflow_dispatch` only; verifies, tags, publishes via GoReleaser, warms the Go module proxy. Local tag creation is forbidden.
- **Dependabot** configuration with weekly updates and auto-merge for patch-level test dependencies.
- **LLM documentation bundle**: `llms.txt` (concise summary) and `llms-full.txt` (concatenated corpus) for AI-assistant ingestion, with a CI guard that fails the build on drift.
- `LICENSE` (Apache 2.0, preserved from upstream), `NOTICE` (crediting Richard Gooding as the upstream author), `SECURITY.md`.
### Changed
- Module path: `github.com/rgooding/go-syncmap` → `github.com/axonops/syncmap`.
- Minimum Go toolchain raised to **1.26**.
### Breaking
- Renamed the `Items()` method on `SyncMap` to `Values()` to match Go stdlib convention (`maps.Values`, Go 1.23). No deprecation shim — the rename lands pre-v1.0 under the new module path.
### Attribution
This release is a fork of [`github.com/rgooding/go-syncmap`](https://github.com/rgooding/go-syncmap) by Richard Gooding, which is distributed under Apache 2.0; this fork continues under the same licence. The original upstream copyright is preserved in git history and credited in `NOTICE`.
[Unreleased]: https://github.com/axonops/syncmap/compare/v1.0.0...HEAD
[1.0.0]: https://github.com/axonops/syncmap/releases/tag/v1.0.0
---
# Full godoc reference (go doc -all)
package syncmap // import "github.com/axonops/syncmap"
Package syncmap provides a type-safe, generic wrapper around sync.Map.
The standard sync.Map stores keys and values as any, which means every load
and store requires a type assertion at the call site. SyncMap[K, V] moves those
assertions inside the wrapper, giving callers compile-time type safety with no
additional allocations and no runtime dependencies beyond the standard library.
# Relationship to sync.Map
SyncMap is a thin layer over sync.Map. It exposes the same set of operations
— Load, Store, LoadOrStore, LoadAndDelete, Delete, and Range — with identical
semantics and the same concurrency guarantees. Four convenience methods are
added on top: Len, Map, Keys, and Values. The underlying sync.Map is not
exported; use the typed methods exclusively.
# When to use SyncMap
sync.Map is optimised for two access patterns: (1) entries are written once
and read many times, or (2) multiple goroutines each operate on disjoint sets
of keys. For workloads that do not fit either pattern — for example, a cache
that is frequently written by a single goroutine — a plain map protected by a
sync.RWMutex will usually perform better.
Use SyncMap (and sync.Map) when:
- Many goroutines read the same keys concurrently.
- The set of active keys is stable; writes are infrequent.
- You want a lock-free path for the common read case.
Use map + sync.RWMutex when:
- The write rate is high or unpredictable.
- You need snapshot-consistent reads of multiple keys at once.
- The map is owned by a single goroutine.
# Thread safety
All methods on SyncMap are safe for concurrent use by multiple goroutines
without additional locking. This guarantee is inherited directly from sync.Map.
# Zero value
The zero value of SyncMap is an empty map ready for use. It must not be copied
after first use; the same restriction applies as for sync.Map and sync.Mutex.
# Quick start
var m syncmap.SyncMap[string, int]
m.Store("hits", 1)
if v, ok := m.Load("hits"); ok {
fmt.Println(v) // 1
}
m.Range(func(k string, v int) bool {
fmt.Printf("%s=%d\n", k, v)
return true
})
FUNCTIONS
func CompareAndDelete[K, V comparable](m *SyncMap[K, V], key K, old V) (deleted bool)
CompareAndDelete deletes the entry for key if its current value is equal to
old. The deleted result reports whether the entry was removed.
V must be comparable, for the same reason as CompareAndSwap.
func CompareAndSwap[K, V comparable](m *SyncMap[K, V], key K, old, new V) (swapped bool)
CompareAndSwap swaps the old and new values for key if the value currently
stored in m is equal to old. The swapped result reports whether the swap was
performed.
V must be comparable. Because SyncMap is declared with V any to support
non-comparable value types, this operation cannot be a method on SyncMap[K,
V]; instantiating it with a non-comparable V (slice, map, func, or a struct
containing one of those) produces a compile-time error rather than the
runtime panic that the underlying sync.Map.CompareAndSwap would raise.
If V is itself an interface type, the comparison performed inside sync.Map
can still panic at runtime when either operand's dynamic type is not
comparable. This matches Go's `==` semantics for interfaces and is outside
this wrapper's control.
TYPES
type SyncMap[K comparable, V any] struct {
// Has unexported fields.
}
SyncMap is a type-safe, generic wrapper around sync.Map.
The zero value is an empty map ready for use. SyncMap must not be copied
after first use.
func (m *SyncMap[K, V]) Clear()
Clear removes all entries from the map, leaving it empty.
func (m *SyncMap[K, V]) Delete(key K)
Delete removes the entry for key. It is a no-op if the key is not present.
func (m *SyncMap[K, V]) Keys() []K
Keys returns a slice of all keys present in the map at the moment of the
call. It runs in O(n) time.
The result is a point-in-time approximation. Concurrent stores and deletes
may cause the slice to include keys that have since been removed, or to omit
keys that were added during traversal. The order of keys is undefined.
func (m *SyncMap[K, V]) Len() int
Len returns the number of entries in the map at the moment of the call.
It runs in O(n) time by traversing the map with Range.
Because the traversal is not atomic, concurrent stores and deletes may
cause the returned count to differ from the number of entries visible to
any single subsequent operation. Treat the result as an approximation,
not a consistent snapshot.
func (m *SyncMap[K, V]) Load(key K) (value V, ok bool)
Load returns the value stored in the map for key, or the zero value of V if
no entry is present. The ok result reports whether an entry was found.
func (m *SyncMap[K, V]) LoadAndDelete(key K) (value V, loaded bool)
LoadAndDelete deletes the entry for key and returns its previous value,
if any. The loaded result reports whether the key was present. If the key
was not present, value is the zero value of V.
func (m *SyncMap[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool)
LoadOrStore returns the existing value for key if present. Otherwise it
stores value and returns it. The loaded result is true if the value was
loaded, false if stored.
func (m *SyncMap[K, V]) Map() map[K]V
Map returns a shallow copy of the map's contents as a plain Go map. It runs
in O(n) time.
The returned map is a point-in-time approximation: because the underlying
Range traversal is not atomic, concurrent modifications may or may not be
reflected in the result. The caller owns the returned map and may modify it
freely.
func (m *SyncMap[K, V]) Range(f func(key K, value V) bool)
Range calls f sequentially for each key and value present in the map.
If f returns false, Range stops iteration.
Range does not correspond to a consistent snapshot of the map's contents:
no key will be visited more than once, but if a value is stored or deleted
concurrently (including by f), Range may reflect any mapping for that key
during the iteration.
Range may run in O(n) time even if f returns false after a constant number
of calls, where n is the number of elements in the map at the start of the
call.
func (m *SyncMap[K, V]) Store(key K, value V)
Store sets the value associated with key.
func (m *SyncMap[K, V]) Swap(key K, value V) (previous V, loaded bool)
Swap replaces the value stored for key with value and returns the previous
value, if any. The loaded result reports whether the key was present.
If the key was not present, previous is the zero value of V.
func (m *SyncMap[K, V]) Values() []V
Values returns a slice of all values present in the map at the moment of the
call. It runs in O(n) time.
The result is a point-in-time approximation. Concurrent stores and deletes
may cause the slice to include values that have since been removed,
or to omit values that were added during traversal. The order of values is
undefined, and does not correspond to the order returned by Keys.