Browse Source

GetMulti support.

Brad Fitzpatrick 14 years ago
parent
commit
3ee276038b
2 changed files with 84 additions and 14 deletions
  1. 60 10
      memcache/memcache.go
  2. 24 4
      memcache/memcache_test.go

+ 60 - 10
memcache/memcache.go

@@ -24,8 +24,10 @@ import (
 	"io"
 	"io"
 	"io/ioutil"
 	"io/ioutil"
 	"log"
 	"log"
-	"os"
 	"net"
 	"net"
+	"os"
+	"strings"
+	"sync"
 )
 )
 
 
 var _ = log.Printf
 var _ = log.Printf
@@ -62,6 +64,8 @@ var (
 	ErrNoServers = os.NewError("memcache: no servers configured or available")
 	ErrNoServers = os.NewError("memcache: no servers configured or available")
 )
 )
 
 
+const buffered = 8 // arbitrary buffered channel size
+
 // resumableError returns true if err is only a protocol-level cache error.
 // resumableError returns true if err is only a protocol-level cache error.
 // This is used to determine whether or not a server connection should
 // This is used to determine whether or not a server connection should
 // be re-used or not. If an error occurs, by default we don't reuse the
 // be re-used or not. If an error occurs, by default we don't reuse the
@@ -203,25 +207,71 @@ func (c *Client) Get(key string) (item *Item, err os.Error) {
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+	err = c.getFromAddr(addr, []string{key}, func(it *Item) { item = it })
+	if err == nil && item == nil {
+		err = ErrCacheMiss
+	}
+	return
+}
+
+func (c *Client) getFromAddr(addr net.Addr, keys []string, cb func(*Item)) os.Error {
 	cn, err := c.getConn(addr)
 	cn, err := c.getConn(addr)
 	if err != nil {
 	if err != nil {
-		return nil, err
+		return err
 	}
 	}
 	defer cn.condRelease(&err)
 	defer cn.condRelease(&err)
 
 
-	if _, err = fmt.Fprintf(cn.rw, "gets %s\r\n", key); err != nil {
-		return
+	if _, err = fmt.Fprintf(cn.rw, "gets %s\r\n", strings.Join(keys, " ")); err != nil {
+		return err
 	}
 	}
 	if err = cn.rw.Flush(); err != nil {
 	if err = cn.rw.Flush(); err != nil {
-		return
+		return err
 	}
 	}
-	if err = parseGetResponse(cn.rw.Reader, func(it *Item) { item = it }); err != nil {
-		return
+	if err = parseGetResponse(cn.rw.Reader, cb); err != nil {
+		return err
 	}
 	}
-	if item == nil {
-		err = ErrCacheMiss
+	return nil
+}
+
+// GetMulti is a batch version of Get. The returned map from keys to
+// items may have fewer elements than the input slice, due to memcache
+// cache misses. Each key must be at most 250 bytes in length.
+// If no error is returned, the returned map will also be non-nil.
+func (c *Client) GetMulti(keys []string) (map[string]*Item, os.Error) {
+	var lk sync.Mutex
+	m := make(map[string]*Item)
+	addItemToMap := func(it *Item) {
+		lk.Lock()
+		defer lk.Unlock()
+		m[it.Key] = it
 	}
 	}
-	return
+
+	keyMap := make(map[net.Addr][]string)
+	for _, key := range keys {
+		if !legalKey(key) {
+			return nil, ErrMalformedKey
+		}
+		addr, err := c.selector.PickServer(key)
+		if err != nil {
+			return nil, err
+		}
+		keyMap[addr] = append(keyMap[addr], key)
+	}
+
+	ch := make(chan os.Error, buffered)
+	for addr, keys := range keyMap {
+		go func(addr net.Addr, keys []string) {
+			ch <- c.getFromAddr(addr, keys, addItemToMap)
+		}(addr, keys)
+	}
+
+	var err os.Error
+	for _ = range keyMap {
+		if ge := <-ch; ge != nil {
+			err = ge
+		}
+	}
+	return m, err
 }
 }
 
 
 // parseGetResponse reads a GET response from r and calls cb for each
 // parseGetResponse reads a GET response from r and calls cb for each

+ 24 - 4
memcache/memcache_test.go

@@ -41,7 +41,7 @@ func TestMemcache(t *testing.T) {
 	}
 	}
 	c := New(testServer)
 	c := New(testServer)
 
 
-	foo := &Item{Key: "foo", Value: []byte("bar"), Flags: 123}
+	foo := &Item{Key: "foo", Value: []byte("fooval"), Flags: 123}
 	if err := c.Set(foo); err != nil {
 	if err := c.Set(foo); err != nil {
 		t.Fatalf("first set(foo): %v", err)
 		t.Fatalf("first set(foo): %v", err)
 	}
 	}
@@ -55,14 +55,14 @@ func TestMemcache(t *testing.T) {
 	if it.Key != "foo" {
 	if it.Key != "foo" {
 		t.Errorf("get(foo) Key = %q, want foo", it.Key)
 		t.Errorf("get(foo) Key = %q, want foo", it.Key)
 	}
 	}
-	if string(it.Value) != "bar" {
-		t.Errorf("get(foo) Value = %q, want bar", string(it.Value))
+	if string(it.Value) != "fooval" {
+		t.Errorf("get(foo) Value = %q, want fooval", string(it.Value))
 	}
 	}
 	if it.Flags != 123 {
 	if it.Flags != 123 {
 		t.Errorf("get(foo) Flags = %v, want 123", it.Flags)
 		t.Errorf("get(foo) Flags = %v, want 123", it.Flags)
 	}
 	}
 
 
-	bar := &Item{Key: "bar", Value: []byte("bar2")}
+	bar := &Item{Key: "bar", Value: []byte("barval")}
 	if err := c.Add(bar); err != nil {
 	if err := c.Add(bar); err != nil {
 		t.Fatalf("first add(foo): %v", err)
 		t.Fatalf("first add(foo): %v", err)
 	}
 	}
@@ -70,4 +70,24 @@ func TestMemcache(t *testing.T) {
 		t.Fatalf("second add(foo) want ErrNotStored, got %v", err)
 		t.Fatalf("second add(foo) want ErrNotStored, got %v", err)
 	}
 	}
 
 
+	m, err := c.GetMulti([]string{"foo", "bar"})
+	if err != nil {
+		t.Fatalf("GetMulti: %v", err)
+	}
+	if g, e := len(m), 2; g != e {
+		t.Errorf("GetMulti: got len(map) = %d, want = %d", g, e)
+	}
+	if _, ok := m["foo"]; !ok {
+		t.Fatalf("GetMulti: didn't get key 'foo'")
+	}
+	if _, ok := m["bar"]; !ok {
+		t.Fatalf("GetMulti: didn't get key 'bar'")
+	}
+	if g, e := string(m["foo"].Value), "fooval"; g != e {
+		t.Errorf("GetMulti: foo: got %q, want %q", g, e)
+	}
+	if g, e := string(m["bar"].Value), "barval"; g != e {
+		t.Errorf("GetMulti: bar: got %q, want %q", g, e)
+	}
+
 }
 }