···47474848The writer does synchronize any actions with a central mutex lock. Keys and
4949values are kept at most three times: Once for both arenas, plus any
5050-non-flushed value in the operations log. The writer is in control over how
5151-long that operations log is: It is truncated after each flush operation. The
5252-delay of each flush is dependent on the readers: They need to preemptively
5353-leave the arena to allow the writer to carry out the flush operation.
5050+non-commited value in the operations log. The writer is in control over how
5151+long that operations log is: It is truncated after each commit operation. The
5252+delay of each commit is dependent on the readers: They need to preemptively
5353+leave the arena to allow the writer to carry out the commit operation.
54545555The "sign", which is in fact a simple pointer, is written by the writer and
5656read by the readers with atomic operations. The signaling of where the readers
5757-are is done with an unsigned integer counter (named "epoch") that is atomically
5757+are is done with an unsigned integer counter (named "serial") that is atomically
5858incremented by 1 when entering an arena and when leaving an arena. Thus, if a
5959-reader has entered an arena the epoch is odd, and even if the reader has left
6060-the arenas. The writer atomically reads each epoch and spins over every epoch
6161-that happened to be odd at that first read until all of those epochs differ
6262-from the recorded odd epoch. That ensures that every reader has at least once
5959+reader has entered an arena the serial is odd, and even if the reader has left
6060+the arenas. The writer atomically reads each serial and spins over every serial
6161+that happened to be odd at that first read until all of those serials differ
6262+from the recorded odd serial. That ensures that every reader has at least once
6363read the pointer after the writer swapped it (and also handles the case of
6464-overflowed epochs).
6464+overflowed serials).
65656666### Performance
6767
+10-10
lrmap.go
···161161 readers := make(map[*readHandlerInner[K, V]]uint64)
162162163163 for rh := range m.readHandlers {
164164- if epoch := atomic.LoadUint64(&(rh.epoch)); epoch%2 == 1 {
165165- readers[rh] = epoch
164164+ if serial := atomic.LoadUint64(&(rh.serial)); serial%2 == 1 {
165165+ readers[rh] = serial
166166 }
167167 }
168168169169 delay := time.Microsecond
170170171171 for {
172172- for reader, epoch := range readers {
173173- if e := atomic.LoadUint64(&(reader.epoch)); e != epoch {
172172+ for reader, serial := range readers {
173173+ if s := atomic.LoadUint64(&(reader.serial)); s != serial {
174174 delete(readers, reader)
175175 }
176176 }
···264264}
265265266266type readHandlerInner[K comparable, V any] struct {
267267- lrmap *LRMap[K, V]
268268- live arena[K, V]
269269- epoch uint64
267267+ lrmap *LRMap[K, V]
268268+ live arena[K, V]
269269+ serial uint64
270270}
271271272272func (r *readHandlerInner[K, V]) enter() {
···274274 panic("reader illegal state: must not Enter() twice")
275275 }
276276277277- atomic.AddUint64(&r.epoch, 1)
277277+ atomic.AddUint64(&r.serial, 1)
278278 r.live = *r.lrmap.readMap.Load()
279279}
280280···283283 panic("reader illegal state: must not Leave() twice")
284284 }
285285286286- atomic.AddUint64(&r.epoch, 1)
286286+ atomic.AddUint64(&r.serial, 1)
287287}
288288289289func (r *readHandlerInner[K, V]) get(key K) V {
···318318}
319319320320func (r *readHandlerInner[K, V]) entered() bool {
321321- return r.epoch%2 == 1
321321+ return r.serial%2 == 1
322322}