Skip to content

Commit 773162a

Browse files
committed
few changes
1 parent 7a683ce commit 773162a

File tree

5 files changed

+138
-110
lines changed

5 files changed

+138
-110
lines changed

cmap.go

Lines changed: 36 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package cmap
22

3+
import "context"
4+
35
type (
46

57
// KT is the KeyType of the map, used for generating specialized versions.
@@ -129,7 +131,7 @@ func (cm *CMap) Keys() []KT {
129131

130132
// ForEach loops over all the key/values in all the shards in order.
131133
// You can break early by returning an error.
132-
// It is safe to change the map during this call.
134+
// It **is** safe to modify the map while using this iterator, however it uses more memory and is slightly slower.
133135
func (cm *CMap) ForEach(fn func(key KT, val VT) error) error {
134136
for i := range cm.shards {
135137
if err := cm.shards[i].ForEach(fn); err != nil {
@@ -144,10 +146,10 @@ func (cm *CMap) ForEach(fn func(key KT, val VT) error) error {
144146

145147
// ForEachLocked loops over all the key/values in the map.
146148
// You can break early by returning an error.
147-
// It is **NOT** safe to change the map during this call.
149+
// It is **NOT* safe to modify the map while using this iterator.
148150
func (cm *CMap) ForEachLocked(fn func(key KT, val VT) error) error {
149151
for i := range cm.shards {
150-
if err := cm.shards[i].ForEach(fn); err != nil {
152+
if err := cm.shards[i].ForEachLocked(fn); err != nil {
151153
if err == Break {
152154
return nil
153155
}
@@ -158,49 +160,45 @@ func (cm *CMap) ForEachLocked(fn func(key KT, val VT) error) error {
158160
}
159161

160162
// Iter returns a channel to be used in for range.
161-
// **Warning** that breaking early will leak up to cm.NumShards() goroutines, use IterWithCancel if you intend to break early.
162-
// It is safe to modify the map while using the iterator.
163-
func (cm *CMap) Iter(buffer int) <-chan *KV {
164-
ch, _ := cm.IterWithCancel(buffer)
163+
// Use `context.WithCancel` if you intend to break early or goroutines will leak.
164+
// It **is** safe to modify the map while using this iterator, however it uses more memory and is slightly slower.
165+
func (cm *CMap) Iter(ctx context.Context, buffer int) <-chan *KV {
166+
ch := make(chan *KV, buffer)
167+
go func() {
168+
cm.iterContext(ctx, ch, false)
169+
close(ch)
170+
}()
165171
return ch
166172
}
167173

168-
// IterWithCancel returns a channel to be used in for range and
169-
// a cancelFn that can be called at any time to cleanly exit early.
170-
// Note that cancelFn will block until all the writers are notified.
171-
// It is safe to modify the map while using the iterator.
172-
func (cm *CMap) IterWithCancel(buffer int) (kvChan <-chan *KV, cancelFn func()) {
173-
var (
174-
ch = make(chan *KV, buffer)
175-
cancelCh = make(chan struct{})
176-
)
174+
// IterLocked returns a channel to be used in for range.
175+
// Use `context.WithCancel` if you intend to break early or goroutines will leak and map access will deadlock.
176+
// It is **NOT* safe to modify the map while using this iterator.
177+
func (cm *CMap) IterLocked(ctx context.Context, buffer int) <-chan *KV {
178+
ch := make(chan *KV, buffer)
179+
go func() {
180+
cm.iterContext(ctx, ch, false)
181+
close(ch)
182+
}()
183+
return ch
184+
}
177185

178-
kvChan, cancelFn = ch, func() {
186+
func (cm *CMap) iterContext(ctx context.Context, ch chan<- *KV, locked bool) {
187+
fn := func(k KT, v VT) error {
179188
select {
180-
case <-cancelCh:
181-
default:
182-
close(cancelCh)
183-
for range ch {
184-
}
189+
case <-ctx.Done():
190+
return Break
191+
case ch <- &KV{k, v}:
192+
return nil
185193
}
186194
}
187-
188-
go func() {
189-
for i := range cm.shards {
190-
cm.shards[i].ForEach(func(k KT, v VT) error {
191-
select {
192-
case <-cancelCh:
193-
return Break
194-
case ch <- &KV{k, v}:
195-
return nil
196-
}
197-
})
195+
for i := range cm.shards {
196+
if locked {
197+
cm.shards[i].ForEachLocked(fn)
198+
} else {
199+
cm.shards[i].ForEach(fn)
198200
}
199-
close(ch)
200-
cancelFn()
201-
}()
202-
203-
return
201+
}
204202
}
205203

206204
// Len returns the number of elements in the map.

cmap_go18_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cmap_test
22

33
import (
4+
"context"
45
"fmt"
56
"strconv"
67
"sync"
@@ -22,9 +23,10 @@ func TestIter(t *testing.T) {
2223
for i := 0; i < 100; i++ {
2324
cm.Set(i, i)
2425
}
25-
ch, cancel := cm.IterWithCancel(0)
26+
ctx, cancel := context.WithCancel(context.Background())
27+
defer cancel()
2628
i := 0
27-
for kv := range ch {
29+
for kv := range cm.IterLocked(ctx, 1) {
2830
t.Logf("%d: %+v", i, kv)
2931
if i++; i > 10 {
3032
cancel()

stringcmap/cmap.go

Lines changed: 19 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package stringcmap
22

33
import (
4+
"context"
5+
46
"github.com/OneOfOne/cmap"
57
)
68

@@ -145,49 +147,28 @@ type KV struct {
145147
}
146148

147149
// Iter returns a channel to be used in for range.
148-
// **Warning** that breaking early will leak up to cm.NumShards() goroutines, use IterWithCancel if you intend to break early.
149-
// It is safe to modify the map while using the iterator.
150-
func (cm *CMap) Iter(buffer int) <-chan *KV {
151-
ch, _ := cm.IterWithCancel(buffer)
150+
// **Warning** breaking early will leak up to cm.NumShards() goroutines, use IterWithCancel if you intend to break early.
151+
// It is safe to modify the map while using this iterator.
152+
func (cm *CMap) Iter(ctx context.Context, buffer int) <-chan *KV {
153+
ch := make(chan *KV, buffer)
154+
go func() {
155+
cm.iterContext(ctx, ch)
156+
close(ch)
157+
}()
152158
return ch
153159
}
154160

155-
// IterWithCancel returns a channel to be used in for range and
156-
// a cancelFn that can be called at any time to cleanly exit early.
157-
// Note that cancelFn will block until all the writers are notified.
158-
// It is safe to modify the map while using the iterator.
159-
func (cm *CMap) IterWithCancel(buffer int) (kvChan <-chan *KV, cancelFn func()) {
160-
var (
161-
ch = make(chan *KV, buffer)
162-
cancelCh = make(chan struct{})
163-
)
164-
165-
kvChan, cancelFn = ch, func() {
166-
select {
167-
case <-cancelCh:
168-
default:
169-
close(cancelCh)
170-
for range ch {
161+
func (cm *CMap) iterContext(ctx context.Context, ch chan<- *KV) {
162+
for i := range cm.shards {
163+
cm.shards[i].ForEach(func(k string, v interface{}) error {
164+
select {
165+
case <-ctx.Done():
166+
return cmap.Break
167+
case ch <- &KV{k, v}:
168+
return nil
171169
}
172-
}
170+
})
173171
}
174-
175-
go func() {
176-
for i := range cm.shards {
177-
cm.shards[i].ForEach(func(k string, v interface{}) error {
178-
select {
179-
case <-cancelCh:
180-
return cmap.Break
181-
case ch <- &KV{k, v}:
182-
return nil
183-
}
184-
})
185-
}
186-
close(ch)
187-
cancelFn()
188-
}()
189-
190-
return
191172
}
192173

193174
// Len returns the number of elements in the map.

stringcmap/json_support.go

Lines changed: 77 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package stringcmap
22

33
import (
4+
"bufio"
45
"bytes"
56
"encoding/json"
7+
"io"
68
)
79

810
// WithJSON returns a MapWithJSON with the specific unmarshal func.
@@ -22,30 +24,82 @@ type MapWithJSON struct {
2224
UnmarshalValueFn func(raw json.RawMessage) (interface{}, error)
2325
}
2426

25-
// UnmarshalJSON implements json.Unmarshaler
26-
func (mwj *MapWithJSON) UnmarshalJSON(p []byte) error {
27-
if mwj.CMap == nil {
28-
mwj.CMap = New()
27+
// WriteTo implements io.WriterTo, outputs the map as a json object.
28+
func (mjw *MapWithJSON) WriteTo(w io.Writer) (n int64, err error) {
29+
var buf writerWithBytes
30+
switch w := w.(type) {
31+
case writerWithBytes:
32+
buf = w
33+
default:
34+
buf = bufio.NewWriter(w)
2935
}
3036

31-
if mwj.UnmarshalValueFn == nil {
32-
var in map[string]interface{}
33-
if err := json.Unmarshal(p, &in); err != nil {
37+
buf.WriteByte('{')
38+
39+
if err := mjw.ForEach(func(key string, val interface{}) error {
40+
vj, err := json.Marshal(val)
41+
if err != nil {
3442
return err
3543
}
36-
37-
for k, v := range in {
38-
mwj.Set(k, v)
44+
if n > 0 {
45+
buf.WriteByte(',')
3946
}
40-
47+
kj, _ := json.Marshal(key)
48+
kn, _ := buf.Write(kj)
49+
buf.WriteByte(':')
50+
vn, _ := buf.Write(vj)
51+
n += int64(kn + vn + 1)
4152
return nil
53+
}); err != nil {
54+
return 0, err
55+
}
56+
57+
buf.WriteByte('}')
58+
n += 2 // {}
59+
60+
if buf, ok := buf.(flusher); ok {
61+
err = buf.Flush()
62+
}
63+
64+
return
65+
}
66+
67+
// UnmarshalJSON implements json.Unmarshaler
68+
func (mwj *MapWithJSON) UnmarshalJSON(p []byte) error {
69+
if mwj.UnmarshalValueFn != nil {
70+
return mwj.unmarshalJSONTyped(p)
71+
}
72+
73+
return mwj.unmarshalJSON(p)
74+
}
75+
76+
func (mwj *MapWithJSON) unmarshalJSON(p []byte) error {
77+
var in map[string]interface{}
78+
if err := json.Unmarshal(p, &in); err != nil {
79+
return err
4280
}
4381

82+
if len(in) > 0 && mwj.CMap == nil {
83+
mwj.CMap = New()
84+
}
85+
86+
for k, v := range in {
87+
mwj.Set(k, v)
88+
}
89+
90+
return nil
91+
}
92+
93+
func (mwj *MapWithJSON) unmarshalJSONTyped(p []byte) error {
4494
var in map[string]json.RawMessage
4595
if err := json.Unmarshal(p, &in); err != nil {
4696
return err
4797
}
4898

99+
if len(in) > 0 && mwj.CMap == nil {
100+
mwj.CMap = New()
101+
}
102+
49103
for k, rj := range in {
50104
v, err := mwj.UnmarshalValueFn(rj)
51105
if err != nil {
@@ -60,26 +114,18 @@ func (mwj *MapWithJSON) UnmarshalJSON(p []byte) error {
60114

61115
// MarshalJSON implements json.Marshaler.
62116
func (cm *CMap) MarshalJSON() ([]byte, error) {
63-
buf := bytes.NewBufferString("{")
64-
if err := cm.ForEach(func(key string, val interface{}) error {
65-
vj, err := json.Marshal(val)
66-
if err != nil {
67-
return err
68-
}
69-
kj, _ := json.Marshal(key)
70-
buf.Write(kj)
71-
buf.WriteByte(':')
72-
buf.Write(vj)
73-
buf.WriteByte(',')
74-
return nil
75-
}); err != nil {
117+
var buf bytes.Buffer
118+
if _, err := cm.WithJSON(nil).WriteTo(&buf); err != nil {
76119
return nil, err
77120
}
78-
out := buf.Bytes()
79-
if out[len(out)-1] == ',' {
80-
out[len(out)-1] = '}'
81-
} else {
82-
out = append(out, '}')
83-
}
84-
return out, nil
121+
return buf.Bytes(), nil
122+
}
123+
124+
type writerWithBytes interface {
125+
io.Writer
126+
io.ByteWriter
127+
}
128+
129+
type flusher interface {
130+
Flush() error
85131
}

stringcmap/json_support_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package stringcmap
22

33
import (
4+
"context"
45
"encoding/json"
56
"reflect"
67
"sort"
@@ -56,7 +57,7 @@ func TestJSONType(t *testing.T) {
5657
t.Fatal(err)
5758
}
5859

59-
for kv := range cm.Iter(0) {
60+
for kv := range cm.Iter(context.Background(), 0) {
6061
if v, ok := kv.Value.(uint64); !ok || kv.Key != "1" || v != 1 {
6162
t.Fatalf("bad kv: %#+v", kv)
6263
}

0 commit comments

Comments
 (0)