Merge branch 'master' into todoshacks
diff --git a/lib/discovery/global/advertise.go b/lib/discovery/global/advertise.go
index da3c45a..312c68e 100644
--- a/lib/discovery/global/advertise.go
+++ b/lib/discovery/global/advertise.go
@@ -11,7 +11,6 @@
 	"v.io/v23/naming"
 	"v.io/v23/security"
 	"v.io/v23/security/access"
-
 	idiscovery "v.io/x/ref/lib/discovery"
 )
 
@@ -43,7 +42,10 @@
 	// TODO(jhahn): There is no atomic way to check and reserve the name under mounttable.
 	// For example, the name can be overwritten by other applications of the same owner.
 	// But this would be OK for now.
-	name := ad.Id.String()
+	name, err := encodeAdToSuffix(ad)
+	if err != nil {
+		return nil, err
+	}
 	if err := d.ns.SetPermissions(ctx, name, perms, "", naming.IsLeaf(true)); err != nil {
 		d.removeAd(ad)
 		return nil, err
@@ -58,8 +60,8 @@
 		defer d.removeAd(ad)
 		// We need a context that is detached from the deadlines and cancellation
 		// of ctx since we have to unmount after ctx is canceled.
-		rctx, _ := context.WithRootCancel(ctx)
-		defer d.unmount(rctx, name)
+		rctx, rcancel := context.WithRootCancel(ctx)
+		defer d.unmount(rctx, rcancel, name)
 
 		for {
 			d.mount(ctx, name, ad.Addresses)
@@ -99,8 +101,9 @@
 	}
 }
 
-func (d *gdiscovery) unmount(ctx *context.T, name string) {
+func (d *gdiscovery) unmount(ctx *context.T, cancel context.CancelFunc, name string) {
 	if err := d.ns.Delete(ctx, name, true); err != nil {
 		ctx.Infof("unmount(%q) failed: %v", name, err)
 	}
+	cancel()
 }
diff --git a/lib/discovery/global/encoding.go b/lib/discovery/global/encoding.go
new file mode 100644
index 0000000..9bb0a59
--- /dev/null
+++ b/lib/discovery/global/encoding.go
@@ -0,0 +1,51 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package global
+
+import (
+	"strings"
+
+	"v.io/v23/discovery"
+	"v.io/v23/naming"
+	"v.io/v23/vom"
+)
+
+// encodeAdToSuffix encodes the ad.Id and the ad.Attributes into the suffix at
+// which we mount the advertisement.
+//
+// TODO(suharshs): Currently only the id and the attributes are encoded; we may
+// want to encode the rest of the advertisement someday?
+func encodeAdToSuffix(ad *discovery.Advertisement) (string, error) {
+	b, err := vom.Encode(ad.Attributes)
+	if err != nil {
+		return "", err
+	}
+	// Escape suffixDelim to use it as our delimeter between the id and the attrs.
+	id := ad.Id.String()
+	attr := naming.EncodeAsNameElement(string(b))
+	return naming.Join(id, attr), nil
+}
+
+// decodeAdFromSuffix decodes s into an advertisement.
+func decodeAdFromSuffix(in string) (*discovery.Advertisement, error) {
+	parts := strings.SplitN(in, "/", 2)
+	if len(parts) != 2 {
+		return nil, NewErrAdInvalidEncoding(nil, in)
+	}
+	id, attrs := parts[0], parts[1]
+	attrs, ok := naming.DecodeFromNameElement(attrs)
+	if !ok {
+		return nil, NewErrAdInvalidEncoding(nil, in)
+	}
+	ad := &discovery.Advertisement{}
+	var err error
+	if ad.Id, err = discovery.ParseAdId(id); err != nil {
+		return nil, err
+	}
+	if err = vom.Decode([]byte(attrs), &ad.Attributes); err != nil {
+		return nil, err
+	}
+	return ad, nil
+}
diff --git a/lib/discovery/global/encoding_test.go b/lib/discovery/global/encoding_test.go
new file mode 100644
index 0000000..cf4b0db
--- /dev/null
+++ b/lib/discovery/global/encoding_test.go
@@ -0,0 +1,34 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package global
+
+import (
+	"reflect"
+	"testing"
+
+	"v.io/v23/discovery"
+)
+
+func TestAdSuffix(t *testing.T) {
+	testCases := []discovery.Advertisement{
+		{},
+		{Id: discovery.AdId{1, 2, 3}},
+		{Attributes: discovery.Attributes{"k": "v"}},
+		{Id: discovery.AdId{1, 2, 3}, Attributes: discovery.Attributes{"k": "v"}},
+	}
+	for _, want := range testCases {
+		encAd, err := encodeAdToSuffix(&want)
+		if err != nil {
+			t.Error(err)
+		}
+		got, err := decodeAdFromSuffix(encAd)
+		if err != nil {
+			t.Error(err)
+		}
+		if !reflect.DeepEqual(*got, want) {
+			t.Errorf("got %v, want %v", *got, want)
+		}
+	}
+}
diff --git a/lib/discovery/global/errors.vdl b/lib/discovery/global/errors.vdl
index f11c648..4bd5228 100644
--- a/lib/discovery/global/errors.vdl
+++ b/lib/discovery/global/errors.vdl
@@ -5,7 +5,6 @@
 package global
 
 error (
-	NoNamespace() {
-		"en": " namespace not found",
-	}
+	NoNamespace() {"en": "namespace not found"}
+	AdInvalidEncoding(ad string) {"en": "ad ({ad}) has invalid encoding"}
 )
diff --git a/lib/discovery/global/global.vdl.go b/lib/discovery/global/global.vdl.go
index f0fbd9a..2e725ee 100644
--- a/lib/discovery/global/global.vdl.go
+++ b/lib/discovery/global/global.vdl.go
@@ -19,7 +19,8 @@
 // Error definitions
 
 var (
-	ErrNoNamespace = verror.Register("v.io/x/ref/lib/discovery/global.NoNamespace", verror.NoRetry, "{1:}{2:}  namespace not found")
+	ErrNoNamespace       = verror.Register("v.io/x/ref/lib/discovery/global.NoNamespace", verror.NoRetry, "{1:}{2:} namespace not found")
+	ErrAdInvalidEncoding = verror.Register("v.io/x/ref/lib/discovery/global.AdInvalidEncoding", verror.NoRetry, "{1:}{2:} ad ({3}) has invalid encoding")
 )
 
 // NewErrNoNamespace returns an error with the ErrNoNamespace ID.
@@ -27,6 +28,11 @@
 	return verror.New(ErrNoNamespace, ctx)
 }
 
+// NewErrAdInvalidEncoding returns an error with the ErrAdInvalidEncoding ID.
+func NewErrAdInvalidEncoding(ctx *context.T, ad string) error {
+	return verror.New(ErrAdInvalidEncoding, ctx, ad)
+}
+
 var __VDLInitCalled bool
 
 // __VDLInit performs vdl initialization.  It is safe to call multiple times.
@@ -49,7 +55,8 @@
 	__VDLInitCalled = true
 
 	// Set error format strings.
-	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrNoNamespace.ID), "{1:}{2:}  namespace not found")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrNoNamespace.ID), "{1:}{2:} namespace not found")
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrAdInvalidEncoding.ID), "{1:}{2:} ad ({3}) has invalid encoding")
 
 	return struct{}{}
 }
diff --git a/lib/discovery/global/global_test.go b/lib/discovery/global/global_test.go
index 2aa1d02..b889e7f 100644
--- a/lib/discovery/global/global_test.go
+++ b/lib/discovery/global/global_test.go
@@ -29,8 +29,9 @@
 
 	ads := []discovery.Advertisement{
 		{
-			Id:        discovery.AdId{1, 2, 3},
-			Addresses: []string{"/h1:123/x", "/h2:123/y"},
+			Id:         discovery.AdId{1, 2, 3},
+			Addresses:  []string{"/h1:123/x", "/h2:123/y"},
+			Attributes: discovery.Attributes{"k": "v"},
 		},
 		{
 			Addresses: []string{"/h1:123/x", "/h2:123/z"},
diff --git a/lib/discovery/global/scan.go b/lib/discovery/global/scan.go
index 26dab17..dec7be0 100644
--- a/lib/discovery/global/scan.go
+++ b/lib/discovery/global/scan.go
@@ -21,10 +21,6 @@
 	if err != nil {
 		return nil, err
 	}
-	target := matcher.TargetKey()
-	if len(target) == 0 {
-		target = "*"
-	}
 
 	updateCh := make(chan discovery.Update, 10)
 	go func() {
@@ -32,7 +28,7 @@
 
 		var prevFound map[discovery.AdId]*discovery.Advertisement
 		for {
-			found, err := d.doScan(ctx, target, matcher)
+			found, err := d.doScan(ctx, matcher.TargetKey(), matcher)
 			if found == nil {
 				if err != nil {
 					ctx.Error(err)
@@ -53,6 +49,22 @@
 }
 
 func (d *gdiscovery) doScan(ctx *context.T, target string, matcher idiscovery.Matcher) (map[discovery.AdId]*discovery.Advertisement, error) {
+	// If the target is neither empty nor a valid AdId, we return without an error,
+	// since there will be not entries with the requested target length in the namespace.
+	if len(target) > 0 {
+		if _, err := discovery.ParseAdId(target); err != nil {
+			return nil, nil
+		}
+	}
+
+	// In the case of empty, we need to scan for everything.
+	// In the case where target is a AdId we need to scan for entries prefixed with
+	// the AdId with any encoded attributes afterwards.
+	if len(target) == 0 {
+		target = naming.Join("*", "*")
+	} else {
+		target = naming.Join(target, "*")
+	}
 	scanCh, err := d.ns.Glob(ctx, target)
 	if err != nil {
 		return nil, err
@@ -74,7 +86,7 @@
 				ctx.Error(err)
 				continue
 			}
-			// Since mount operation is not atomic, we may not have addresses yet.
+			// Since mount operations are not atomic, we may not have addresses yet.
 			// Ignore it. It will be re-scanned in the next cycle.
 			if len(ad.Addresses) == 0 {
 				continue
@@ -108,7 +120,7 @@
 func convToAd(glob naming.GlobReply) (*discovery.Advertisement, error) {
 	switch g := glob.(type) {
 	case *naming.GlobReplyEntry:
-		id, err := discovery.ParseAdId(g.Value.Name)
+		ad, err := decodeAdFromSuffix(g.Value.Name)
 		if err != nil {
 			return nil, err
 		}
@@ -116,9 +128,10 @@
 		for _, server := range g.Value.Servers {
 			addrs = append(addrs, server.Server)
 		}
-		// We sort the addresses to avoid false update.
+		// We sort the addresses to avoid false updates.
 		sort.Strings(addrs)
-		return &discovery.Advertisement{Id: id, Addresses: addrs}, nil
+		ad.Addresses = addrs
+		return ad, nil
 	case *naming.GlobReplyError:
 		return nil, fmt.Errorf("glob error on %s: %v", g.Value.Name, g.Value.Error)
 	default:
diff --git a/lib/discovery/global/validate.go b/lib/discovery/global/validate.go
index e7e0efa..83eef8e 100644
--- a/lib/discovery/global/validate.go
+++ b/lib/discovery/global/validate.go
@@ -21,11 +21,8 @@
 	if len(ad.Addresses) == 0 {
 		return errors.New("address not provided")
 	}
-	if len(ad.Attributes) > 0 {
-		return errors.New("attributes name not supported")
-	}
 	if len(ad.Attachments) > 0 {
-		return errors.New("attachments name not supported")
+		return errors.New("attachments not supported")
 	}
 	return nil
 }
diff --git a/lib/discovery/global/validate_test.go b/lib/discovery/global/validate_test.go
index 792165f..e9a4a6b 100644
--- a/lib/discovery/global/validate_test.go
+++ b/lib/discovery/global/validate_test.go
@@ -17,8 +17,9 @@
 	}{
 		{
 			discovery.Advertisement{
-				Id:        discovery.AdId{1, 2, 3},
-				Addresses: []string{"/h:123/x"},
+				Id:         discovery.AdId{1, 2, 3},
+				Addresses:  []string{"/h:123/x"},
+				Attributes: discovery.Attributes{"k": "v"},
 			},
 			true,
 		},
@@ -47,16 +48,6 @@
 			discovery.Advertisement{
 				Id:        discovery.AdId{1, 2, 3},
 				Addresses: []string{"/h:123/x"},
-				Attributes: discovery.Attributes{ // Has attributes.
-					"k": "v",
-				},
-			},
-			false,
-		},
-		{
-			discovery.Advertisement{
-				Id:        discovery.AdId{1, 2, 3},
-				Addresses: []string{"/h:123/x"},
 				Attachments: discovery.Attachments{ // Has attachments.
 					"k": []byte{1},
 				},
diff --git a/runtime/internal/rpc/client.go b/runtime/internal/rpc/client.go
index 31530bf..f422f32 100644
--- a/runtime/internal/rpc/client.go
+++ b/runtime/internal/rpc/client.go
@@ -454,23 +454,23 @@
 			status.serverErr = suberr(err)
 			return
 		}
-		if write := c.typeCache.writer(flw.Conn()); write != nil {
-			// Create the type flow with a root-cancellable context.
-			// This flow must outlive the flow we're currently creating.
-			// It lives as long as the connection to which it is bound.
-			tctx, tcancel := context.WithRootCancel(ctx)
-			tflow, err := c.flowMgr.DialSideChannel(tctx, flw.RemoteEndpoint(), typeFlowAuthorizer{}, 0)
-			if err != nil {
-				write(nil, tcancel)
-			} else if tflow.Conn() != flw.Conn() {
-				tflow.Close()
-				write(nil, tcancel)
-			} else if _, err = tflow.Write([]byte{typeFlow}); err != nil {
-				tflow.Close()
-				write(nil, tcancel)
-			} else {
-				write(tflow, tcancel)
-			}
+	}
+	if write := c.typeCache.writer(flw.Conn()); write != nil {
+		// Create the type flow with a root-cancellable context.
+		// This flow must outlive the flow we're currently creating.
+		// It lives as long as the connection to which it is bound.
+		tctx, tcancel := context.WithRootCancel(ctx)
+		tflow, err := c.flowMgr.DialSideChannel(tctx, flw.RemoteEndpoint(), typeFlowAuthorizer{}, 0)
+		if err != nil {
+			write(nil, tcancel)
+		} else if tflow.Conn() != flw.Conn() {
+			tflow.Close()
+			write(nil, tcancel)
+		} else if _, err = tflow.Write([]byte{typeFlow}); err != nil {
+			tflow.Close()
+			write(nil, tcancel)
+		} else {
+			write(tflow, tcancel)
 		}
 	}
 
diff --git a/services/allocator/allocator.vdl.go b/services/allocator/allocator.vdl.go
index 1f1851b..e4989eb 100644
--- a/services/allocator/allocator.vdl.go
+++ b/services/allocator/allocator.vdl.go
@@ -28,6 +28,7 @@
 	BlessingNames []string
 	CreationTime  time.Time
 	Replicas      int32
+	Version       string
 }
 
 func (Instance) __VDLReflect(struct {
@@ -51,6 +52,9 @@
 	if x.Replicas != 0 {
 		return false
 	}
+	if x.Version != "" {
+		return false
+	}
 	return true
 }
 
@@ -93,6 +97,11 @@
 			return err
 		}
 	}
+	if x.Version != "" {
+		if err := enc.NextFieldValueString("Version", vdl.StringType, x.Version); err != nil {
+			return err
+		}
+	}
 	if err := enc.NextField(""); err != nil {
 		return err
 	}
@@ -163,6 +172,13 @@
 			default:
 				x.Replicas = int32(value)
 			}
+		case "Version":
+			switch value, err := dec.ReadValueString(); {
+			case err != nil:
+				return err
+			default:
+				x.Version = value
+			}
 		default:
 			if err := dec.SkipValue(); err != nil {
 				return err
diff --git a/services/allocator/allocator/main.go b/services/allocator/allocator/main.go
index 9026695..8dfbafc 100644
--- a/services/allocator/allocator/main.go
+++ b/services/allocator/allocator/main.go
@@ -38,6 +38,7 @@
 
 const listDetailsEntry = `Handle: {{.Handle}}
 Created: {{.CreationTime}}
+Version: {{.Version}}
 MountName: {{.MountName}}
 BlessingPatterns: {{.BlessingNames}}
 Replicas: {{.Replicas}}
diff --git a/services/allocator/allocatord/assets/assets.go b/services/allocator/allocatord/assets/assets.go
index 1472d13..c0d5c34 100644
--- a/services/allocator/allocatord/assets/assets.go
+++ b/services/allocator/allocatord/assets/assets.go
@@ -204,7 +204,7 @@
 	return a, nil
 }
 
-var _homeTmplHtml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xb4\x58\x7f\x6f\xdb\x36\x10\xfd\xdf\x9f\xe2\xaa\x06\x90\xbc\x44\x72\xb2\x62\xff\x24\xb6\x81\xa6\xe9\xd0\x02\x75\x1a\x24\xe9\x80\xa2\x28\x06\x5a\x3a\x5b\x6c\x24\xd2\x25\x29\xbb\x86\xa1\x7d\xf6\x1d\xf5\xcb\x96\xe3\xc4\xc9\xba\x16\xa8\x6d\x51\xc7\xbb\x77\x8f\x8f\xe4\x5d\xfa\x2f\x22\x19\x9a\xe5\x0c\x21\x36\x69\x32\xec\xf4\xeb\x2f\x64\xd1\xb0\x03\xd0\x37\xdc\x24\x38\xfc\x8b\x09\x16\xf1\x2c\x85\xd5\xaa\x18\x80\xe0\x06\xd5\x1c\xd5\x25\x4b\x31\xcf\xe1\x75\x92\xc8\x90\x19\xa9\xfa\xbd\xd2\x9e\x66\x92\x25\xa6\xb3\x84\x19\x04\xc7\x7a\x73\x20\xc8\xf3\x4e\xbf\x57\x7a\xee\xbf\xf0\x7d\xb8\xfc\x78\xfb\xf6\x14\x16\x08\xee\x58\x2a\x25\x17\x2e\x30\x48\xa4\x01\x39\x01\x13\x23\x68\xb3\x4c\xb8\x98\xda\x47\x1e\xa1\x20\xcf\xcb\xe8\x08\x62\x14\x21\xda\xf7\x1d\x8d\x61\xa6\x68\xd0\x57\x68\xc3\x44\x60\x50\xa5\x5c\xc8\x44\x4e\x97\xc0\x45\xe3\x03\x01\x13\x4c\xc9\x81\x3e\x82\x84\xdf\x61\xe3\x0d\x98\x88\x3a\xe3\x04\xb5\xa6\x30\x01\xf8\x3e\xe1\x1a\xcb\x68\x09\x61\xc2\xb4\x1e\x38\xb5\xdd\x4c\xc9\x39\xfd\x56\x7e\xc2\x96\x32\x33\xc0\xea\x74\x23\x67\x57\xa6\xa8\xca\x5c\x89\xbd\x89\x54\x29\xa4\x68\x62\x19\x0d\x9c\xab\x8f\x37\xb7\x0e\x05\x1f\x38\x76\xd8\x01\x25\x13\xac\x7e\x5b\x37\x64\xce\xc5\x8c\xdc\xdb\xe5\x18\x38\x06\x7f\x18\xa7\x46\x12\xf3\x88\xb0\x38\x20\x88\xef\x81\xb3\x5a\x05\x6f\x6e\xae\xff\xbc\x62\x8a\xa5\x79\xee\xc0\x9c\x25\xd9\x7a\xf8\x56\xde\xa1\xa0\xe1\x5e\xb1\x7e\x3a\x54\x7c\x66\x4a\xff\x93\x4c\x84\x86\x4b\x01\x61\xcc\xc4\x14\xcf\x8d\xf0\x38\x31\x2a\x70\xf1\xee\x76\xf4\xa1\x0b\xab\xc2\x8a\x04\x91\x59\xb6\x82\x29\x9a\xb7\x25\x71\xe7\xcb\xf7\x11\x99\x76\x03\x2e\x04\x2a\x6b\x0c\x03\x70\x28\x3b\x61\x20\x24\xbe\xd5\xc0\x9d\x2a\xb6\x74\x87\xce\x61\xe5\xec\xd0\xe9\xf7\xec\xeb\xa1\x73\x56\x38\xcd\x8b\xcf\x39\x53\x30\xa1\xa9\x4d\x08\x9b\xbc\xfe\xe2\xda\x2f\xf7\xab\xb5\xec\xf7\xd6\x80\xfb\x11\x9f\xd7\x04\xa4\x8c\x8b\x9a\xa5\xf8\xa4\x1e\x9d\xb1\x29\xfa\x85\xb8\x86\x6f\x14\xda\x05\xa0\x15\x85\x11\x89\x75\x8a\xf0\x5e\x68\xc3\x48\x2b\xda\x0a\x68\xa7\x6e\x49\x8c\x27\x95\xcf\x8d\x50\xb5\x20\xb4\x9f\x70\x6d\xaa\xa0\x0f\x99\x54\xeb\x5d\x1b\x15\xe8\x86\x9f\x65\xa6\x48\x80\x55\xf8\x75\x14\x9b\x1e\x79\xa9\x1f\x56\x2b\x65\x97\x01\x0e\xb8\x88\xf0\xc7\x11\x1c\x54\x32\x85\xd3\x01\x04\x0d\xfa\x3c\x7f\x14\x00\x27\xed\x6d\x86\xdf\x61\xe4\x47\x68\x18\x4f\xf4\x86\x99\x0d\xbe\xe0\x26\x5e\xc7\x69\xc2\x94\x49\xbc\x1a\x92\x9a\xde\x11\x9b\x89\xdd\xdf\xab\x15\x9f\x00\x7e\x87\xe0\x1a\x67\x09\x0f\x99\x86\xe3\x3c\xff\x72\x93\xe9\x19\x12\xf4\xe8\xeb\x6a\x45\xdf\x05\x9f\xaf\x36\x63\xf4\x67\xf7\xa0\x84\x6c\x4e\xeb\xd4\x86\x62\x55\x3a\x63\xa2\x5c\x42\x2b\xcf\x5b\x9e\x22\x09\xc1\x8e\xf5\xc7\xaa\xb7\xc3\xb6\xf6\x9b\x09\xfe\xc3\x90\xb5\x03\x11\x33\xcc\xaf\x1f\x07\x76\x2b\x54\xde\xac\xb3\xe0\x13\xbd\xc8\xf3\xe1\xf6\xf0\x8d\x51\x04\xca\x02\x2f\x82\x6d\x42\xef\xcd\x7e\x22\x93\xd7\x51\xa4\xc8\xec\xc1\x1c\x08\xc7\x48\x66\xc2\x94\x32\xfc\xdf\xc2\x9e\x57\x46\x70\xc5\x0c\x9d\x85\xe2\x31\x00\xa5\xf4\x82\x7a\x8a\x45\xa2\x5b\x50\x2a\x9c\x44\xce\x8e\xd9\xc5\x72\x3f\x8c\x7b\xdb\xa0\x25\xfb\x2d\x91\x1a\xa1\x7d\x3a\x43\x5a\x09\x6d\x19\x6c\x27\xcb\x20\x56\x38\x29\x0e\xbc\x0b\xa6\xe3\xb1\x64\x2a\xfa\x74\xfd\xc1\x1e\x85\xf5\xa4\xcc\x18\x29\xfc\x19\x3d\xf0\x39\xa9\xc3\x30\x45\xa7\xd9\xc0\xf9\x7b\x9c\x30\x71\xe7\x0c\x9b\x69\xfd\x1e\x6b\x2f\x7b\x0b\xe8\xb3\x90\xe0\x38\x9b\x3e\x13\x85\x9d\xb2\x0f\x41\xb3\xf7\xea\x9d\xba\xb1\x09\xa1\xbd\x08\x5b\x58\x8b\xab\x86\x74\x48\x47\x2d\x1d\xf5\xab\x55\x79\xce\x10\xbe\x07\x92\x78\xf9\x20\x70\x29\x42\x8a\x78\x37\x70\xd6\x17\x87\xbb\xc3\xb1\x7b\x04\xee\xb5\x1d\xb6\x1a\xf4\x0c\xbb\xa3\xb3\x97\xc1\x04\x17\x40\x57\xb5\x14\x91\xee\x42\x10\x04\x6e\xf7\x0c\x26\x01\x2b\x6e\xa2\x81\x4b\xcc\x15\x53\xb0\xa0\xce\xb5\xaf\x74\x36\x4e\xb9\xf1\xc8\x4c\xa1\xc9\x94\x80\x09\x4b\x34\x3a\xc3\xd2\x6e\x3f\x5f\x48\xd6\xfb\x89\xd1\xe5\xe9\xf5\x0b\x98\xd9\xe5\xd9\x52\x53\x9d\x97\xcf\x23\xa7\x9a\xb4\x9f\x9d\xca\xf0\x09\xf4\x6c\xef\xdd\x36\x3b\x40\xff\xfd\x05\x53\x82\x70\x36\x12\x42\xf3\x6b\x14\xb4\xe5\xb7\x12\x10\x1a\xb3\x41\xd2\x3f\xbf\x1f\xef\xd5\x0f\x9a\x27\xc9\x07\xcd\x33\xf7\xfb\x7d\x32\x22\xd4\x46\xc9\xe5\x2f\xa0\x63\x97\x67\x4b\xc8\x45\x39\xbe\xc9\xc8\xc9\x1e\x46\xaa\x29\xfb\x39\xa9\x0c\x1f\x67\xa5\xf5\xb8\xf1\xb0\xb5\xd3\x2e\xa5\x40\x98\xd0\xb5\x16\x05\x9d\x6d\xa5\x15\xb4\x5a\xfa\xc2\xa2\x4c\x3b\xb7\x27\xe9\x7f\xa4\xa9\xf1\x60\xb9\x29\x6f\xf3\x4d\xad\xfc\xf1\x38\x33\x65\x99\xb8\x9f\x98\xaa\x9c\xbc\xc4\x85\xe5\xa6\x95\x74\x59\x37\x8d\xe8\xde\xb4\x55\xa6\xbf\x9d\x61\x5a\xbe\x70\xd6\x55\x5e\x50\x9b\xb4\xa8\xab\xb8\x69\x06\x6d\xbd\xac\xd2\x8d\x9a\x1d\xb4\x0a\x8b\x9b\xe5\xb5\x26\xe1\xea\x2b\xe2\xca\x16\x31\xbd\xba\x2b\xe9\xa5\xb2\xa8\xa1\xbf\x51\x31\xd0\x2a\x9b\x9f\x38\xfd\xdb\xf7\x0c\xd5\xf2\xa1\xe9\xf6\x67\xd3\x2f\x90\x03\x5b\x2f\xdd\x52\x4b\xe2\xd9\x22\xb5\x6e\x15\x6c\x3d\x6f\xeb\x2d\xba\x98\xd2\x19\xd5\xf5\xf6\x5d\x60\x2b\x31\x6f\x5d\x99\x75\xcf\x1a\xd3\x94\x4c\x4a\xd0\x5e\x33\xeb\xb7\x93\xe3\xe3\xe3\xe0\x78\xc3\xaa\x6c\xd9\x5a\xce\x8a\xa1\xda\x13\x5d\x87\x5e\x65\x33\xa0\x2e\x84\x8d\xb5\x4c\x32\x43\xaf\x2b\x50\x50\xce\xb4\x8d\xac\xe7\xd4\x22\x73\x5f\xba\x8d\x9a\xdc\xcd\xa5\x3e\xa3\xa6\x05\x0e\x21\x2d\x5a\x11\x66\x3c\x67\x34\x1a\xc1\xc5\xc5\x11\x7c\xa6\x7f\x10\x9f\xa6\xe9\xa9\xa6\x83\x9a\xbc\x1f\x52\xc7\x43\x5a\xa8\x61\xc0\x7d\x80\x47\xe0\x4c\x94\x4c\x2f\xe5\xa2\x36\xca\xc1\x6e\x91\x9f\x04\x56\xba\xf4\x9e\x88\x60\xcd\xc7\xba\xf3\xca\x3b\xf4\x71\xe0\xd5\x6d\x57\x37\x20\x75\x47\x4b\xaf\x5e\x5f\xaf\xa6\xee\xc0\x73\x82\xf5\xc2\x05\xc8\xc2\xf8\xbe\x11\x40\xaf\x07\x05\x60\xbb\xf1\x6c\x93\xbd\x96\x80\x8e\x65\x96\x50\x23\x2e\xa7\xd3\xa4\x68\xd2\x21\xe2\x9a\x5a\xe3\x25\x94\xf4\x06\x95\x87\x03\xcf\xc4\x5c\x77\x83\xc2\x4d\x2b\x44\x4b\x6b\x95\x19\xed\xce\xbc\xc9\x79\xe7\xfb\x32\xd3\xe2\xbb\xfc\x5c\x2b\xba\xdf\xb3\x2d\xbd\xfd\x2e\xff\xb4\xf1\x6f\x00\x00\x00\xff\xff\x9d\x55\x06\x4c\xf2\x10\x00\x00")
+var _homeTmplHtml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xb4\x58\xdd\x6e\xdb\x38\x13\xbd\xf7\x53\x4c\xd5\x00\x92\xbf\x44\x72\xf2\x15\x7b\x93\xd8\x06\x9a\xa6\x8b\x16\xa8\xd3\x20\x49\x0b\x14\x45\xb1\xa0\xa5\xb1\xc5\x46\x22\x5d\x92\xb2\x6b\x18\xda\x67\xdf\xa1\xfe\x6c\x39\x4e\x9c\x6c\xb7\x01\x62\x59\xe4\x70\xe6\xf0\xf0\x90\x9c\x71\xff\x45\x24\x43\xb3\x9c\x21\xc4\x26\x4d\x86\x9d\x7e\xfd\x40\x16\x0d\x3b\x00\x7d\xc3\x4d\x82\xc3\xcf\x4c\xb0\x88\x67\x29\xac\x56\x45\x03\x04\x37\xa8\xe6\xa8\x2e\x59\x8a\x79\x0e\xaf\x93\x44\x86\xcc\x48\xd5\xef\x95\xf6\x34\x92\x2c\x31\x9d\x25\xcc\x20\x38\xd6\x9b\x03\x41\x9e\x77\xfa\xbd\xd2\x73\xff\x85\xef\xc3\xe5\xc7\xdb\xb7\xa7\xb0\x40\x70\xc7\x52\x29\xb9\x70\x81\x41\x22\x0d\xc8\x09\x98\x18\x41\x9b\x65\xc2\xc5\xd4\xbe\xf2\x08\x05\x79\x5e\x46\x47\x10\xa3\x08\xd1\xf6\x77\x34\x86\x99\xa2\x46\x5f\xa1\x0d\x13\x81\x41\x95\x72\x21\x13\x39\x5d\x02\x17\x8d\x0f\x04\x4c\x30\x25\x07\xfa\x08\x12\x7e\x87\x8d\x37\x60\x22\xea\x8c\x13\xd4\x9a\xc2\x04\xe0\xfb\x84\x6b\x2c\xa3\x25\x84\x09\xd3\x7a\xe0\xd4\x76\x33\x25\xe7\xf4\x5d\xf9\x09\x5b\xca\xcc\x00\xab\xa7\x1b\x39\xbb\x66\x8a\xaa\x9c\x2b\xb1\x37\x91\x2a\x85\x14\x4d\x2c\xa3\x81\x73\xf5\xf1\xe6\xd6\xa1\xe0\x03\xc7\x36\x3b\xa0\x64\x82\xd5\x77\xeb\x86\xcc\xb9\x98\x91\x7b\xbb\x1c\x03\xc7\xe0\x4f\xe3\xd4\x48\x62\x1e\x11\x16\x07\x04\xf1\x3d\x70\x56\xab\xe0\xcd\xcd\xf5\x9f\x57\x4c\xb1\x34\xcf\x1d\x98\xb3\x24\x5b\x37\xdf\xca\x3b\x14\xd4\xdc\x2b\xd6\x4f\x87\x8a\xcf\x4c\xe9\x7f\x92\x89\xd0\x70\x29\x20\x8c\x99\x98\xe2\xb9\x11\x1e\x27\x46\x05\x2e\xde\xdd\x8e\x3e\x74\x61\x55\x58\x91\x20\x32\xcb\x56\x30\x45\xf3\xb6\x24\xee\x7c\xf9\x3e\x22\xd3\x6e\xc0\x85\x40\x65\x8d\x61\x00\x0e\xcd\x4e\x18\x08\x89\x6f\x35\x70\xa7\x8a\x2d\xdd\xa1\x73\x58\x39\x3b\x74\xfa\x3d\xdb\x3d\x74\xce\x0a\xa7\x79\xf1\x39\x67\x0a\x26\x34\xb4\x09\x61\x27\xaf\xbf\xba\xf6\xe1\x7e\xb3\x96\xfd\xde\x1a\x70\x3f\xe2\xf3\x9a\x80\x94\x71\x51\xb3\x14\x9f\xd4\xad\x33\x36\x45\xbf\x10\xd7\xf0\x8d\x42\xbb\x00\xb4\xa2\x30\x22\xb1\x4e\x11\xde\x0b\x6d\x18\x69\x45\x5b\x01\xed\xd4\x2d\x89\xf1\xa4\xf2\xb9\x11\xaa\x16\x84\xf6\x13\xae\x4d\x15\xf4\x21\x93\x6a\xbd\x6b\xa3\x02\xdd\xf0\x8b\xcc\x14\x09\xb0\x0a\xbf\x8e\x62\xa7\x47\x5e\xea\x97\xd5\x4a\xd9\x65\x80\x03\x2e\x22\xfc\x79\x04\x07\x95\x4c\xe1\x74\x00\x41\x83\x3e\xcf\x1f\x05\xc0\x49\x7b\x9b\xe1\x77\x18\xf9\x11\x1a\xc6\x13\xbd\x61\x66\x83\x2f\xb8\x89\xd7\x71\x9a\x30\xe5\x24\x5e\x0d\x49\x4d\xef\x88\xcd\xc4\xee\xef\xd5\x8a\x4f\x00\x7f\x40\x70\x8d\xb3\x84\x87\x4c\xc3\x71\x9e\x7f\xbd\xc9\xf4\x0c\x09\x7a\xf4\x6d\xb5\xa2\x67\xc1\xe7\xab\xcd\x18\xfd\xd9\x3d\x28\x21\x9b\xd3\x3a\xb5\xa1\x58\x95\xce\x98\x28\x97\xd0\xca\xf3\x96\xa7\x48\x42\xb0\x6d\xfd\xb1\xea\xed\xb0\xad\xfd\x66\x82\xff\x34\x64\xed\x40\xc4\x0c\xf3\xeb\xd7\x81\xdd\x0a\x95\x37\xeb\x2c\xf8\x44\x1d\x79\x3e\xdc\x6e\xbe\x31\x8a\x40\x59\xe0\x45\xb0\x4d\xe8\xbd\xd9\x2f\xcc\xe4\x33\x2a\x4d\x31\x1e\x9c\x03\xe1\xa8\x4c\xda\xb4\xff\x52\xd0\xd7\x51\xa4\xc8\xec\xb1\xa0\x23\x99\x09\x53\x6a\xff\x3f\x0b\x7b\x5e\x19\xc1\x15\x33\x74\x00\x8b\xc7\x00\x94\x7a\x0f\xea\x21\x16\x89\x6e\x41\xa9\x70\xd2\x8a\xec\x18\x5d\x68\xec\x61\xdc\xdb\x06\xad\xbd\xb6\xb5\x33\x8c\xd0\x3e\x1d\x5c\xad\x09\x6d\x19\x6c\x4f\x96\x41\xac\x70\x52\x9c\xb2\x17\x4c\xc7\x63\xc9\x54\xf4\xe9\xfa\x83\x3d\x7f\xeb\x41\x99\x31\x52\xf8\x33\x7a\xe1\x73\x92\xa4\x61\x8a\x8e\xd0\x81\xf3\xd7\x38\x61\xe2\xce\x19\x36\xc3\xfa\x3d\xd6\xd6\x5a\x0b\xe8\xb3\x90\xe0\x38\x9b\x3e\x13\x85\x1d\xb2\x0f\x41\xb3\xe1\xeb\xe3\x61\x63\xe7\x43\x7b\x11\xb6\xb0\x16\xf7\x1b\xe9\x90\xce\x77\xba\x5f\x56\xab\xf2\x70\x23\x7c\x0f\x4c\xe2\xe5\x83\xc0\xa5\x08\x29\xe2\xdd\xc0\x59\xdf\x56\xee\x0e\xc7\xee\x11\xb8\xd7\xb6\xd9\x6a\xd0\x33\xec\x8e\x0e\x7c\x06\x13\x5c\x00\xe5\x07\x52\x44\xba\x0b\x41\x10\xb8\xdd\x33\x98\x04\xac\xb8\xfe\x06\x2e\x31\x57\x0c\xc1\x82\x3a\xd7\x76\xe9\x6c\x9c\x72\xe3\x91\x99\x42\x93\x29\x01\x13\x96\x68\x74\x86\xa5\xdd\x7e\xbe\x90\xac\xf7\x13\xa3\xcb\x23\xf3\x37\x30\xb3\xcb\xb3\xa5\xa6\x3a\xa4\x9f\x47\x4e\x35\x68\x3f\x3b\x95\xe1\x13\xe8\xd9\xde\xbb\x6d\x76\x80\xfe\xfd\x05\x53\x82\x70\x36\x12\x42\xf3\x7b\x14\xb4\xe5\xb7\x12\x10\x1a\xb3\x41\xd2\xdf\xff\x3f\xde\xab\x1f\x34\x4f\x92\x0f\x9a\x67\xee\xf7\xfb\x64\x44\xa8\x8d\x92\xcb\xdf\x40\xc7\x2e\xcf\x96\x90\x8b\xb2\x7d\x93\x91\x93\x3d\x8c\x54\x43\xf6\x73\x52\x19\x3e\xce\x4a\xeb\x75\xe3\x65\x6b\xa7\x5d\x4a\x81\x30\xa1\x6b\x2d\x0a\x3a\xdb\x4a\x2b\x68\xb5\xf4\x85\x45\x6e\x78\x6e\x4f\xd2\x7f\x49\x53\xe3\xc1\x72\x53\xa6\x10\x9b\x5a\xf9\xe3\x71\x66\xca\xdc\x74\x3f\x31\x55\x0e\x7b\x89\x0b\xcb\x4d\x6b\xd2\x65\xb2\x36\xa2\x7b\xd3\xa6\xb6\xfe\xf6\x0c\xd3\xb2\xc3\x59\xa7\x96\x41\x6d\xd2\xa2\xae\xe2\xa6\x69\xb4\x49\xba\x4a\x37\x0a\x05\xd0\x2a\x2c\x6e\x96\xd7\x9a\x84\xab\xaf\x88\x2b\x9b\x39\xf5\xea\x52\xa8\x97\xca\x22\x71\xff\x4e\xc9\x40\x2b\x57\x7f\xe2\xf0\xef\x3f\x32\x54\xcb\x87\x86\xdb\xaf\x4d\x91\x42\x0e\x6c\x92\x76\x4b\x75\x90\x67\x33\xe3\xba\x3e\xb1\x45\x84\x4d\xf2\xe8\x62\x4a\x67\x54\x4c\xd8\xbe\xc0\xa6\x7f\xde\x3a\x1d\xec\x9e\x35\xa6\x29\x99\x94\xa0\xbd\x66\xd4\xff\x4e\x8e\x8f\x8f\x83\xe3\x0d\xab\xb2\x4e\x6c\x39\x2b\x9a\x6a\x4f\x74\x1d\x7a\x95\xcd\x80\x4a\x1f\x36\xd6\x32\xc9\x0c\x75\x57\xa0\xa0\x1c\x69\xab\x67\xcf\xa9\x45\xe6\xbe\x74\x1b\x35\xb9\x9b\x4b\x7d\x46\x95\x12\x1c\x42\x5a\xd4\x3f\xcc\x78\xce\x68\x34\x82\x8b\x8b\x23\xf8\x42\x7f\x10\x9f\xa6\xe9\xa9\xa6\x83\x9a\xbc\x1f\x52\x99\x45\x5a\xa8\x61\xc0\x7d\x80\x47\xe0\x4c\x94\x4c\x2f\xe5\xa2\x36\xca\xc1\x6e\x91\x5f\x04\x56\xba\xf4\x9e\x88\x60\xcd\xc7\xba\xdc\xcb\x3b\xf4\x71\xe0\xd5\xb5\x5e\x37\x20\x75\x47\x4b\xaf\x5e\x5f\xaf\xa6\xee\xc0\x73\x82\xf5\xc2\x05\xc8\xc2\xf8\xbe\x11\x40\xaf\x07\x05\x60\xbb\xf1\x6c\x65\xbf\x96\x80\x8e\x65\x96\x50\xf5\x2f\xa7\xd3\xa4\xf8\x65\x00\x22\xae\xa9\x1e\x5f\x42\x49\x6f\x50\x79\x38\xf0\x4c\xcc\x75\x37\x28\xdc\xb4\x42\xb4\xb4\x56\x99\xd1\xee\xcc\x9b\x39\xef\xec\x2f\x67\x5a\x3c\xcb\xcf\xb5\xa2\xfb\x3d\xfb\x3b\x82\x7d\x96\xbf\xa7\xfc\x13\x00\x00\xff\xff\x6e\xb3\x7f\x69\x67\x11\x00\x00")
 
 func homeTmplHtmlBytes() ([]byte, error) {
 	return bindataRead(
diff --git a/services/allocator/allocatord/assets/home.tmpl.html b/services/allocator/allocatord/assets/home.tmpl.html
index dfc7b39..259a889 100644
--- a/services/allocator/allocatord/assets/home.tmpl.html
+++ b/services/allocator/allocatord/assets/home.tmpl.html
@@ -33,6 +33,10 @@
             <span class="unixtime" data-unixtime={{.CreationTime.Unix}}>{{.CreationTime.String}}</span>
           </p>
           <p class="blessing-caveats">
+            <span>Version</span><br/>
+            {{.Version}}
+          </p>
+          <p class="blessing-caveats">
             <span>Address</span><br/>
             {{.MountName}}
           </p>
diff --git a/services/allocator/allocatord/service.go b/services/allocator/allocatord/service.go
index bec63ba..10ff1ad 100644
--- a/services/allocator/allocatord/service.go
+++ b/services/allocator/allocatord/service.go
@@ -338,6 +338,13 @@
 			} `json:"metadata"`
 			Spec struct {
 				Replicas int32 `json:"replicas"`
+				Template struct {
+					Metadata struct {
+						Labels struct {
+							Version string `json:"version"`
+						} `json:"labels"`
+					} `json:"metadata"`
+				} `json:"template"`
 			} `json:"spec"`
 		} `json:"items"`
 	}
@@ -360,6 +367,7 @@
 			BlessingNames: cInfo.BlessingNames,
 			CreationTime:  l.Metadata.CreationTime,
 			Replicas:      l.Spec.Replicas,
+			Version:       l.Spec.Template.Metadata.Labels.Version,
 		})
 	}
 	return instances, nil
diff --git a/services/allocator/service.vdl b/services/allocator/service.vdl
index a1dfdf9..da409b5 100644
--- a/services/allocator/service.vdl
+++ b/services/allocator/service.vdl
@@ -13,6 +13,7 @@
      BlessingNames []string
      CreationTime time.Time
      Replicas int32
+     Version string
 }
 
 type Allocator interface {
diff --git a/services/syncbase/bridge/cgo/impl.go b/services/syncbase/bridge/cgo/impl.go
index 4617232..ffa8b7b 100644
--- a/services/syncbase/bridge/cgo/impl.go
+++ b/services/syncbase/bridge/cgo/impl.go
@@ -53,17 +53,17 @@
 #include "lib.h"
 
 static void CallDbWatchPatternsCallbacksOnChange(v23_syncbase_DbWatchPatternsCallbacks cbs, v23_syncbase_WatchChange wc) {
-  cbs.onChange(cbs.hOnChange, wc);
+  cbs.onChange(cbs.handle, wc);
 }
 static void CallDbWatchPatternsCallbacksOnError(v23_syncbase_DbWatchPatternsCallbacks cbs, v23_syncbase_VError err) {
-  cbs.onError(cbs.hOnChange, cbs.hOnError, err);
+  cbs.onError(cbs.handle, err);
 }
 
 static void CallCollectionScanCallbacksOnKeyValue(v23_syncbase_CollectionScanCallbacks cbs, v23_syncbase_KeyValue kv) {
-  cbs.onKeyValue(cbs.hOnKeyValue, kv);
+  cbs.onKeyValue(cbs.handle, kv);
 }
 static void CallCollectionScanCallbacksOnDone(v23_syncbase_CollectionScanCallbacks cbs, v23_syncbase_VError err) {
-  cbs.onDone(cbs.hOnKeyValue, cbs.hOnDone, err);
+  cbs.onDone(cbs.handle, err);
 }
 */
 import "C"
diff --git a/services/syncbase/bridge/cgo/jni.go b/services/syncbase/bridge/cgo/jni.go
index a92db92..b68037a 100644
--- a/services/syncbase/bridge/cgo/jni.go
+++ b/services/syncbase/bridge/cgo/jni.go
@@ -8,7 +8,9 @@
 package main
 
 import (
+	"fmt"
 	"unsafe"
+	"v.io/x/ref/services/syncbase/bridge/cgo/refmap"
 )
 
 /*
@@ -24,22 +26,45 @@
 static void setJValueArrayElement(jvalue* arr, int index, jvalue val) {
   arr[index] = val;
 }
+
+void v23_syncbase_internal_onChange(v23_syncbase_Handle handle, v23_syncbase_WatchChange);
+void v23_syncbase_internal_onError(v23_syncbase_Handle handle, v23_syncbase_VError);
+
+static v23_syncbase_DbWatchPatternsCallbacks newVWatchPatternsCallbacks() {
+  v23_syncbase_DbWatchPatternsCallbacks cbs = {
+  	0, v23_syncbase_internal_onChange, v23_syncbase_internal_onError};
+  return cbs;
+}
+
+void v23_syncbase_internal_onKeyValue(v23_syncbase_Handle handle, v23_syncbase_KeyValue);
+void v23_syncbase_internal_onDone(v23_syncbase_Handle handle, v23_syncbase_VError);
+
+static v23_syncbase_CollectionScanCallbacks newVScanCallbacks() {
+  v23_syncbase_CollectionScanCallbacks cbs = {
+  	0, v23_syncbase_internal_onKeyValue, v23_syncbase_internal_onDone};
+  return cbs;
+}
 */
 import "C"
 
 var (
 	jVM                         *C.JavaVM
 	arrayListClass              jArrayListClass
+	collectionRowPatternClass   jCollectionRowPattern
 	hashMapClass                jHashMap
 	idClass                     jIdClass
+	keyValueClass               jKeyValue
 	permissionsClass            jPermissions
 	syncgroupMemberInfoClass    jSyncgroupMemberInfo
 	syncgroupSpecClass          jSyncgroupSpec
 	verrorClass                 jVErrorClass
 	versionedPermissionsClass   jVersionedPermissions
 	versionedSyncgroupSpecClass jVersionedSyncgroupSpec
+	watchChangeClass            jWatchChange
 )
 
+var globalRefMap = refmap.NewRefMap()
+
 // JNI_OnLoad is called when System.loadLibrary is called. We need to cache the
 // *JavaVM because that's the only way to get hold of a JNIEnv that is needed
 // for any JNI operation.
@@ -56,14 +81,17 @@
 
 	v23_syncbase_Init(C.v23_syncbase_Bool(1))
 	arrayListClass = newJArrayListClass(env)
+	collectionRowPatternClass = newJCollectionRowPattern(env)
 	hashMapClass = newJHashMap(env)
 	idClass = newJIdClass(env)
+	keyValueClass = newJKeyValue(env)
 	permissionsClass = newJPermissions(env)
 	syncgroupMemberInfoClass = newJSyncgroupMemberInfo(env)
 	syncgroupSpecClass = newJSyncgroupSpec(env)
 	verrorClass = newJVErrorClass(env)
 	versionedPermissionsClass = newJVersionedPermissions(env)
 	versionedSyncgroupSpecClass = newJVersionedSyncgroupSpec(env)
+	watchChangeClass = newJWatchChange(env)
 
 	return C.JNI_VERSION_1_6
 }
@@ -329,7 +357,58 @@
 	return cMembers.extractToJava(env)
 }
 
-func Java_io_v_syncbase_internal_Database_WatchPatterns(env *C.JNIEnv, cls C.jclass, name C.jstring, resumeMaker C.jbyteArray, patters C.jobject, callbacks C.jobject) {
+//export v23_syncbase_internal_onChange
+func v23_syncbase_internal_onChange(handle C.v23_syncbase_Handle, change C.v23_syncbase_WatchChange) {
+	// TODO(razvanm): Remove the panic and uncomment the code from below
+	// after the onChange starts working.
+	panic("v23_syncbase_internal_onChange not implemented")
+	//id := uint64(uintptr(handle))
+	//h := globalRrefMap.Get(id).(*watchPatternsCallbacksHandle)
+	//env, free := getEnv()
+	//obj := change.extractToJava(env)
+	//arg := *(*C.jvalue)(unsafe.Pointer(&obj))
+	//C.CallVoidMethodA(env, C.jobject(unsafe.Pointer(h.obj)), h.callbacks.onChange, &arg)
+	//if C.ExceptionOccurred(env) != nil {
+	//	C.ExceptionDescribe(env)
+	//	panic("java exception")
+	//}
+	//free()
+}
+
+//export v23_syncbase_internal_onError
+func v23_syncbase_internal_onError(handle C.v23_syncbase_Handle, error C.v23_syncbase_VError) {
+	id := uint64(uintptr(handle))
+	h := globalRefMap.Remove(id).(*watchPatternsCallbacksHandle)
+	env, free := getEnv()
+	obj := error.extractToJava(env)
+	arg := *(*C.jvalue)(unsafe.Pointer(&obj))
+	C.CallVoidMethodA(env, C.jobject(unsafe.Pointer(h.obj)), h.callbacks.onError, &arg)
+	if C.ExceptionOccurred(env) != nil {
+		C.ExceptionDescribe(env)
+		panic("java exception")
+	}
+	C.DeleteGlobalRef(env, unsafe.Pointer(h.obj))
+	free()
+}
+
+type watchPatternsCallbacksHandle struct {
+	obj       uintptr
+	callbacks jWatchPatternsCallbacks
+}
+
+//export Java_io_v_syncbase_internal_Database_WatchPatterns
+func Java_io_v_syncbase_internal_Database_WatchPatterns(env *C.JNIEnv, cls C.jclass, name C.jstring, resumeMaker C.jbyteArray, patterns C.jobject, callbacks C.jobject) {
+	cName := newVStringFromJava(env, name)
+	cResumeMarker := newVBytesFromJava(env, resumeMaker)
+	cPatterns := newVCollectionRowPatternsFromJava(env, patterns)
+	cbs := C.newVWatchPatternsCallbacks()
+	cbs.handle = C.v23_syncbase_Handle(uintptr(globalRefMap.Add(&watchPatternsCallbacksHandle{
+		obj:       uintptr(unsafe.Pointer(C.NewGlobalRef(env, callbacks))),
+		callbacks: newJWatchPatternsCallbacks(env, callbacks),
+	})))
+	var cErr C.v23_syncbase_VError
+	v23_syncbase_DbWatchPatterns(cName, cResumeMarker, cPatterns, cbs, &cErr)
+	maybeThrowException(env, &cErr)
 }
 
 //export Java_io_v_syncbase_internal_Collection_GetPermissions
@@ -396,7 +475,57 @@
 	maybeThrowException(env, &cErr)
 }
 
+//export v23_syncbase_internal_onKeyValue
+func v23_syncbase_internal_onKeyValue(handle C.v23_syncbase_Handle, keyValue C.v23_syncbase_KeyValue) {
+	id := uint64(uintptr(handle))
+	h := globalRefMap.Get(id).(*scanCallbacksHandle)
+	env, free := getEnv()
+	obj := keyValue.extractToJava(env)
+	arg := *(*C.jvalue)(unsafe.Pointer(&obj))
+	C.CallVoidMethodA(env, C.jobject(unsafe.Pointer(h.obj)), h.callbacks.onKeyValue, &arg)
+	if C.ExceptionOccurred(env) != nil {
+		C.ExceptionDescribe(env)
+		panic("java exception")
+	}
+	free()
+}
+
+//export v23_syncbase_internal_onDone
+func v23_syncbase_internal_onDone(handle C.v23_syncbase_Handle, error C.v23_syncbase_VError) {
+	id := uint64(uintptr(handle))
+	h := globalRefMap.Get(id).(*scanCallbacksHandle)
+	env, free := getEnv()
+	obj := error.extractToJava(env)
+	arg := *(*C.jvalue)(unsafe.Pointer(&obj))
+	C.CallVoidMethodA(env, C.jobject(unsafe.Pointer(h.obj)), h.callbacks.onDone, &arg)
+	if C.ExceptionOccurred(env) != nil {
+		C.ExceptionDescribe(env)
+		panic("java exception")
+	}
+	C.DeleteGlobalRef(env, unsafe.Pointer(h.obj))
+	free()
+	globalRefMap.Remove(id)
+}
+
+type scanCallbacksHandle struct {
+	obj       uintptr
+	callbacks jScanCallbacks
+}
+
+//export Java_io_v_syncbase_internal_Collection_Scan
 func Java_io_v_syncbase_internal_Collection_Scan(env *C.JNIEnv, cls C.jclass, name C.jstring, handle C.jstring, start C.jbyteArray, limit C.jbyteArray, callbacks C.jobject) {
+	cName := newVStringFromJava(env, name)
+	cHandle := newVStringFromJava(env, handle)
+	cStart := newVBytesFromJava(env, start)
+	cLimit := newVBytesFromJava(env, limit)
+	cbs := C.newVScanCallbacks()
+	cbs.handle = C.v23_syncbase_Handle(uintptr(globalRefMap.Add(&scanCallbacksHandle{
+		obj:       uintptr(unsafe.Pointer(C.NewGlobalRef(env, callbacks))),
+		callbacks: newJScanCallbacks(env, callbacks),
+	})))
+	var cErr C.v23_syncbase_VError
+	v23_syncbase_CollectionScan(cName, cHandle, cStart, cLimit, cbs, &cErr)
+	maybeThrowException(env, &cErr)
 }
 
 //export Java_io_v_syncbase_internal_Row_Exists
@@ -547,6 +676,13 @@
 	return obj
 }
 
+func (x *C.v23_syncbase_KeyValue) extractToJava(env *C.JNIEnv) C.jobject {
+	obj := C.NewObjectA(env, keyValueClass.class, keyValueClass.init, nil)
+	C.SetObjectField(env, obj, keyValueClass.key, x.key.extractToJava(env))
+	C.SetObjectField(env, obj, keyValueClass.value, x.value.extractToJava(env))
+	return obj
+}
+
 func (x *C.v23_syncbase_Permissions) extractToJava(env *C.JNIEnv) C.jobject {
 	obj := C.NewObjectA(env, permissionsClass.class, permissionsClass.init, nil)
 	C.SetObjectField(env, obj, permissionsClass.json, x.json.extractToJava(env))
@@ -651,3 +787,52 @@
 	x.free()
 	return obj
 }
+
+// newVCollectionRowPatternsFromJava creates a
+// v23_syncbase_CollectionRowPatterns from a List<CollectionRowPattern>.
+func newVCollectionRowPatternsFromJava(env *C.JNIEnv, obj C.jobject) C.v23_syncbase_CollectionRowPatterns {
+	if obj == nil {
+		return C.v23_syncbase_CollectionRowPatterns{}
+	}
+	listInterface := newJListInterface(env, obj)
+	iterObj := C.CallObjectMethod(env, obj, listInterface.iterator)
+	if C.ExceptionOccurred(env) != nil {
+		panic("newVCollectionRowPatternsFromJava exception while trying to call List.iterator()")
+	}
+
+	iteratorInterface := newJIteratorInterface(env, iterObj)
+	tmp := []C.v23_syncbase_CollectionRowPattern{}
+	for C.CallBooleanMethodA(env, iterObj, iteratorInterface.hasNext, nil) == C.JNI_TRUE {
+		idObj := C.CallObjectMethod(env, iterObj, iteratorInterface.next)
+		if C.ExceptionOccurred(env) != nil {
+			panic("newVCollectionRowPatternsFromJava exception while trying to call Iterator.next()")
+		}
+		tmp = append(tmp, newVCollectionRowPatternFromJava(env, idObj))
+	}
+
+	size := C.size_t(len(tmp)) * C.size_t(C.sizeof_v23_syncbase_CollectionRowPattern)
+	r := C.v23_syncbase_CollectionRowPatterns{
+		p: (*C.v23_syncbase_CollectionRowPattern)(unsafe.Pointer(C.malloc(size))),
+		n: C.int(len(tmp)),
+	}
+	for i := range tmp {
+		*r.at(i) = tmp[i]
+	}
+	return r
+}
+
+func jFindClass(env *C.JNIEnv, name string) (C.jclass, error) {
+	cName := C.CString(name)
+	defer C.free(unsafe.Pointer(cName))
+
+	class := C.FindClass(env, cName)
+	if C.ExceptionOccurred(env) != nil {
+		return nil, fmt.Errorf("couldn't find class %s", name)
+	}
+
+	globalRef := C.jclass(C.NewGlobalRef(env, class))
+	if globalRef == nil {
+		return nil, fmt.Errorf("couldn't allocate a global reference for class %s", name)
+	}
+	return globalRef, nil
+}
diff --git a/services/syncbase/bridge/cgo/jni_lib.go b/services/syncbase/bridge/cgo/jni_lib.go
index 8eef51a..a43e6b6 100644
--- a/services/syncbase/bridge/cgo/jni_lib.go
+++ b/services/syncbase/bridge/cgo/jni_lib.go
@@ -27,6 +27,25 @@
 	}
 }
 
+type jCollectionRowPattern struct {
+	class              C.jclass
+	init               C.jmethodID
+	collectionBlessing C.jfieldID
+	collectionName     C.jfieldID
+	rowKey             C.jfieldID
+}
+
+func newJCollectionRowPattern(env *C.JNIEnv) jCollectionRowPattern {
+	cls, init := initClass(env, "io/v/syncbase/internal/Database$CollectionRowPattern")
+	return jCollectionRowPattern{
+		class:              cls,
+		init:               init,
+		collectionBlessing: jGetFieldID(env, cls, "collectionBlessing", "Ljava/lang/String;"),
+		collectionName:     jGetFieldID(env, cls, "collectionName", "Ljava/lang/String;"),
+		rowKey:             jGetFieldID(env, cls, "rowKey", "Ljava/lang/String;"),
+	}
+}
+
 type jHashMap struct {
 	class C.jclass
 	init  C.jmethodID
@@ -42,19 +61,6 @@
 	}
 }
 
-type jIteratorInterface struct {
-	hasNext C.jmethodID
-	next    C.jmethodID
-}
-
-func newJIteratorInterface(env *C.JNIEnv, obj C.jobject) jIteratorInterface {
-	cls := C.GetObjectClass(env, obj)
-	return jIteratorInterface{
-		hasNext: jGetMethodID(env, cls, "hasNext", "()Z"),
-		next:    jGetMethodID(env, cls, "next", "()Ljava/lang/Object;"),
-	}
-}
-
 type jIdClass struct {
 	class    C.jclass
 	init     C.jmethodID
@@ -72,6 +78,37 @@
 	}
 }
 
+type jIteratorInterface struct {
+	hasNext C.jmethodID
+	next    C.jmethodID
+}
+
+func newJIteratorInterface(env *C.JNIEnv, obj C.jobject) jIteratorInterface {
+	cls := C.GetObjectClass(env, obj)
+	return jIteratorInterface{
+		hasNext: jGetMethodID(env, cls, "hasNext", "()Z"),
+		next:    jGetMethodID(env, cls, "next", "()Ljava/lang/Object;"),
+	}
+}
+
+type jKeyValue struct {
+	class C.jclass
+	init  C.jmethodID
+	key   C.jfieldID
+	value C.jfieldID
+}
+
+func newJKeyValue(env *C.JNIEnv) jKeyValue {
+	cls, init := initClass(env, "io/v/syncbase/internal/Collection$KeyValue")
+	return jKeyValue{
+		class: cls,
+		init:  init,
+		key:   jGetFieldID(env, cls, "key", "Ljava/lang/String;"),
+		value: jGetFieldID(env, cls, "value", "[B"),
+	}
+
+}
+
 type jListInterface struct {
 	iterator C.jmethodID
 	size     C.jmethodID
@@ -85,6 +122,34 @@
 	}
 }
 
+type jPermissions struct {
+	class C.jclass
+	init  C.jmethodID
+	json  C.jfieldID
+}
+
+func newJPermissions(env *C.JNIEnv) jPermissions {
+	cls, init := initClass(env, "io/v/syncbase/internal/Permissions")
+	return jPermissions{
+		class: cls,
+		init:  init,
+		json:  jGetFieldID(env, cls, "json", "[B"),
+	}
+}
+
+type jScanCallbacks struct {
+	onKeyValue C.jmethodID
+	onDone     C.jmethodID
+}
+
+func newJScanCallbacks(env *C.JNIEnv, obj C.jobject) jScanCallbacks {
+	cls := C.GetObjectClass(env, obj)
+	return jScanCallbacks{
+		onKeyValue: jGetMethodID(env, cls, "onKeyValue", "(Lio/v/syncbase/internal/Collection$KeyValue;)V"),
+		onDone:     jGetMethodID(env, cls, "onDone", "(Lio/v/syncbase/internal/VError;)V"),
+	}
+}
+
 type jSyncgroupMemberInfo struct {
 	class        C.jclass
 	init         C.jmethodID
@@ -148,6 +213,23 @@
 	}
 }
 
+type jVersionedPermissions struct {
+	class       C.jclass
+	init        C.jmethodID
+	version     C.jfieldID
+	permissions C.jfieldID
+}
+
+func newJVersionedPermissions(env *C.JNIEnv) jVersionedPermissions {
+	cls, init := initClass(env, "io/v/syncbase/internal/VersionedPermissions")
+	return jVersionedPermissions{
+		class:       cls,
+		init:        init,
+		version:     jGetFieldID(env, cls, "version", "Ljava/lang/String;"),
+		permissions: jGetFieldID(env, cls, "permissions", "Lio/v/syncbase/internal/Permissions;"),
+	}
+}
+
 type jVersionedSyncgroupSpec struct {
 	class         C.jclass
 	init          C.jmethodID
@@ -165,35 +247,43 @@
 	}
 }
 
-type jPermissions struct {
-	class C.jclass
-	init  C.jmethodID
-	json  C.jfieldID
+type jWatchChange struct {
+	class        C.jclass
+	init         C.jmethodID
+	collection   C.jfieldID
+	row          C.jfieldID
+	changeType   C.jfieldID
+	value        C.jfieldID
+	resumeMarker C.jfieldID
+	fromSync     C.jfieldID
+	continued    C.jfieldID
 }
 
-func newJPermissions(env *C.JNIEnv) jPermissions {
-	cls, init := initClass(env, "io/v/syncbase/internal/Permissions")
-	return jPermissions{
-		class: cls,
-		init:  init,
-		json:  jGetFieldID(env, cls, "json", "[B"),
+func newJWatchChange(env *C.JNIEnv) jWatchChange {
+	cls, init := initClass(env, "io/v/syncbase/internal/Database$WatchChange")
+	return jWatchChange{
+		class:        cls,
+		init:         init,
+		collection:   jGetFieldID(env, cls, "collection", "Lio/v/syncbase/internal/Id;"),
+		row:          jGetFieldID(env, cls, "row", "Ljava/lang/String;"),
+		changeType:   jGetFieldID(env, cls, "changeType", "Lio/v/syncbase/internal/Database$ChangeType;"),
+		value:        jGetFieldID(env, cls, "value", "[B"),
+		resumeMarker: jGetFieldID(env, cls, "resumeMarker", "Ljava/lang/String;"),
+		fromSync:     jGetFieldID(env, cls, "fromSync", "Z"),
+		continued:    jGetFieldID(env, cls, "continued", "Z"),
 	}
 }
 
-type jVersionedPermissions struct {
-	class       C.jclass
-	init        C.jmethodID
-	version     C.jfieldID
-	permissions C.jfieldID
+type jWatchPatternsCallbacks struct {
+	onChange C.jmethodID
+	onError  C.jmethodID
 }
 
-func newJVersionedPermissions(env *C.JNIEnv) jVersionedPermissions {
-	cls, init := initClass(env, "io/v/syncbase/internal/VersionedPermissions")
-	return jVersionedPermissions{
-		class:       cls,
-		init:        init,
-		version:     jGetFieldID(env, cls, "version", "Ljava/lang/String;"),
-		permissions: jGetFieldID(env, cls, "permissions", "Lio/v/syncbase/internal/Permissions;"),
+func newJWatchPatternsCallbacks(env *C.JNIEnv, obj C.jobject) jWatchPatternsCallbacks {
+	cls := C.GetObjectClass(env, obj)
+	return jWatchPatternsCallbacks{
+		onChange: jGetMethodID(env, cls, "onChange", "(Lio/v/syncbase/internal/Database$WatchChange;)V"),
+		onError:  jGetMethodID(env, cls, "onError", "(Lio/v/syncbase/internal/VError;)V"),
 	}
 }
 
diff --git a/services/syncbase/bridge/cgo/jni_types.go b/services/syncbase/bridge/cgo/jni_types.go
index 9c48df6..f605b98 100644
--- a/services/syncbase/bridge/cgo/jni_types.go
+++ b/services/syncbase/bridge/cgo/jni_types.go
@@ -262,3 +262,28 @@
 	}
 	return cPerms, newVStringFromJava(env, version)
 }
+
+// newVCollectionRowPatternFromJava creates a v23_syncbase_CollectionRowPattern
+// from a CollectionRowPattern object.
+func newVCollectionRowPatternFromJava(env *C.JNIEnv, obj C.jobject) C.v23_syncbase_CollectionRowPattern {
+	collectionBlessing := C.jstring(C.GetObjectField(env, obj, collectionRowPatternClass.collectionBlessing))
+	if C.ExceptionOccurred(env) != nil {
+		panic("newVCollectionRowPatternFromJava exception while retrieving CollectionRowPattern.collectionBlessing")
+	}
+
+	collectionName := C.jstring(C.GetObjectField(env, obj, collectionRowPatternClass.collectionName))
+	if C.ExceptionOccurred(env) != nil {
+		panic("newVCollectionRowPatternFromJava exception while retrieving CollectionRowPattern.collectionName")
+	}
+
+	rowKey := C.jstring(C.GetObjectField(env, obj, collectionRowPatternClass.rowKey))
+	if C.ExceptionOccurred(env) != nil {
+		panic("newVCollectionRowPatternFromJava exception while retrieving CollectionRowPattern.rowKey")
+	}
+
+	return C.v23_syncbase_CollectionRowPattern{
+		collectionBlessing: newVStringFromJava(env, collectionBlessing),
+		collectionName:     newVStringFromJava(env, collectionName),
+		rowKey:             newVStringFromJava(env, rowKey),
+	}
+}
diff --git a/services/syncbase/bridge/cgo/jni_util.go b/services/syncbase/bridge/cgo/jni_util.go
index 7385467..1baf81d 100644
--- a/services/syncbase/bridge/cgo/jni_util.go
+++ b/services/syncbase/bridge/cgo/jni_util.go
@@ -9,6 +9,7 @@
 
 import (
 	"fmt"
+	"runtime"
 	"unsafe"
 )
 
@@ -17,22 +18,6 @@
 // #include "lib.h"
 import "C"
 
-func jFindClass(env *C.JNIEnv, name string) (C.jclass, error) {
-	cName := C.CString(name)
-	defer C.free(unsafe.Pointer(cName))
-
-	class := C.FindClass(env, cName)
-	if C.ExceptionOccurred(env) != nil {
-		return nil, fmt.Errorf("couldn't find class %s", name)
-	}
-
-	globalRef := C.jclass(C.NewGlobalRef(env, class))
-	if globalRef == nil {
-		return nil, fmt.Errorf("couldn't allocate a global reference for class %s", name)
-	}
-	return globalRef, nil
-}
-
 func jGetMethodID(env *C.JNIEnv, cls C.jclass, name, sig string) C.jmethodID {
 	cName := C.CString(name)
 	defer C.free(unsafe.Pointer(cName))
@@ -59,10 +44,56 @@
 
 	field := C.GetFieldID(env, cls, cName, cSig)
 	if field == nil {
-		panic(fmt.Sprintf("couldn't get field %q with signature ", name, sig))
+		panic(fmt.Sprintf("couldn't get field %q with signature %s", name, sig))
 	}
 
 	// Note: the validity of the field is bounded by the lifetime of the
 	// ClassLoader that did the loading of the class.
 	return field
 }
+
+// The function from below was hoisted from jni/util/util.go and adapted to not
+// use custom types.
+
+// GetEnv returns the Java environment for the running thread, creating a new
+// one if it doesn't already exist.  This method also returns a function which
+// must be invoked when the returned environment is no longer needed. The
+// returned environment can only be used by the thread that invoked this method,
+// and the function must be invoked by the same thread as well.
+func getEnv() (*C.JNIEnv, func()) {
+	// Lock the goroutine to the current OS thread.  This is necessary as
+	// *C.JNIEnv must not be shared across threads.  The scenario that can
+	// break this requirement is:
+	//   - goroutine A executing on thread X, obtains a *C.JNIEnv pointer P.
+	//   - goroutine A gets re-scheduled on thread Y, maintaining the P.
+	//   - goroutine B starts executing on thread X, obtaining pointer P.
+	//
+	// By locking the goroutines to their OS thread while they hold the
+	// pointer to *C.JNIEnv, the above scenario can never occur.
+	runtime.LockOSThread()
+	var env *C.JNIEnv
+	if C.GetEnv(jVM, &env, C.JNI_VERSION_1_6) != C.JNI_OK {
+		// Couldn't get env; attach the thread.  Note that we never
+		// detach the thread, so the next call to GetEnv on this thread
+		// will succeed. We also don't have to worry about calling
+		// DetachCurrentThread before the thread exits.
+		C.AttachCurrentThreadAsDaemon(jVM, &env, nil)
+	}
+	// GetEnv is called by Go code that wishes to call Java methods. In
+	// this case, JNI cannot automatically free unused local references.
+	// We must do it manually by pushing a new local reference frame. The
+	// frame will be popped in the env's cleanup function below, at which
+	// point JNI will free the unused references.
+	// http://developer.android.com/training/articles/perf-jni.html states
+	// that the JNI implementation is only required to provide a local
+	// reference table with a capacity of 16, so here we provide a table of
+	// that size.
+	localRefCapacity := 16
+	if newCapacity := C.PushLocalFrame(env, C.jint(localRefCapacity)); newCapacity < 0 {
+		panic("PushLocalFrame(" + string(localRefCapacity) + ") returned < 0 (was " + string(newCapacity) + ")")
+	}
+	return env, func() {
+		C.PopLocalFrame(env, nil)
+		runtime.UnlockOSThread()
+	}
+}
diff --git a/services/syncbase/bridge/cgo/jni_wrapper.c b/services/syncbase/bridge/cgo/jni_wrapper.c
index bb00584..c411dcc 100644
--- a/services/syncbase/bridge/cgo/jni_wrapper.c
+++ b/services/syncbase/bridge/cgo/jni_wrapper.c
@@ -6,6 +6,14 @@
 
 #include "jni_wrapper.h"
 
+jint AttachCurrentThread(JavaVM *jvm, JNIEnv **env, void *args) {
+  return (*jvm)->AttachCurrentThread(jvm, (void **)env, args);
+}
+
+jint AttachCurrentThreadAsDaemon(JavaVM *jvm, JNIEnv **env, void *args) {
+  return (*jvm)->AttachCurrentThreadAsDaemon(jvm, (void **)env, args);
+}
+
 jboolean CallBooleanMethodA(JNIEnv *env, jobject obj, jmethodID methodID, jvalue *args) {
   return (*env)->CallBooleanMethodA(env, obj, methodID, args);
 }
@@ -18,6 +26,18 @@
   return (*env)->CallObjectMethodA(env, obj, methodID, args);
 }
 
+void CallVoidMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
+  (*env)->CallVoidMethod(env, obj, methodID);
+}
+
+void CallVoidMethodA(JNIEnv *env, jobject obj, jmethodID methodID, jvalue *args) {
+  (*env)->CallVoidMethodA(env, obj, methodID, args);
+}
+
+void DeleteGlobalRef(JNIEnv *env, jobject globalRef) {
+  (*env)->DeleteGlobalRef(env, globalRef);
+}
+
 void ExceptionClear(JNIEnv *env) {
   (*env)->ExceptionClear(env);
 }
@@ -116,4 +136,16 @@
 
 jint ThrowNew(JNIEnv *env, jclass cls, const char *message) {
   return (*env)->ThrowNew(env, cls, message);
+}
+
+jint PushLocalFrame(JNIEnv *env, jint capacity) {
+  return (*env)->PushLocalFrame(env, capacity);
+}
+
+jobject PopLocalFrame(JNIEnv *env, jobject result) {
+  return (*env)->PopLocalFrame(env, result);
+}
+
+void ExceptionDescribe(JNIEnv *env) {
+  (*env)->ExceptionDescribe(env);
 }
\ No newline at end of file
diff --git a/services/syncbase/bridge/cgo/jni_wrapper.h b/services/syncbase/bridge/cgo/jni_wrapper.h
index 36be6c8..a322045 100644
--- a/services/syncbase/bridge/cgo/jni_wrapper.h
+++ b/services/syncbase/bridge/cgo/jni_wrapper.h
@@ -10,10 +10,15 @@
 
 #include "jni.h"
 
+jint AttachCurrentThread(JavaVM *jvm, JNIEnv **env, void *args);
+jint AttachCurrentThreadAsDaemon(JavaVM* jvm, JNIEnv** env, void* args);
 jboolean CallBooleanMethodA(JNIEnv *env, jobject obj, jmethodID methodID, jvalue *args);
 jobject CallIntMethod(JNIEnv *env, jobject obj, jmethodID methodID);
 jobject CallObjectMethod(JNIEnv *env, jobject obj, jmethodID methodID);
 jobject CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID methodID, jvalue *args);
+void CallVoidMethod(JNIEnv *env, jobject obj, jmethodID methodID);
+void CallVoidMethodA(JNIEnv *env, jobject obj, jmethodID methodID, jvalue *args);
+void DeleteGlobalRef(JNIEnv *env, jobject globalRef);
 void ExceptionClear(JNIEnv *env);
 jthrowable ExceptionOccurred(JNIEnv* env);
 jclass FindClass(JNIEnv* env, const char* name);
@@ -38,4 +43,8 @@
 void SetLongField(JNIEnv *env, jobject obj, jfieldID fieldID, jlong value);
 void SetObjectField(JNIEnv *env, jobject obj, jfieldID fieldID, jobject value);
 jint Throw(JNIEnv *env, jthrowable obj);
-jint ThrowNew(JNIEnv *env, jclass cls, const char *message);
\ No newline at end of file
+jint ThrowNew(JNIEnv *env, jclass cls, const char *message);
+
+jint PushLocalFrame(JNIEnv *env, jint capacity);
+jobject PopLocalFrame(JNIEnv *env, jobject result);
+void ExceptionDescribe(JNIEnv *env);
\ No newline at end of file
diff --git a/services/syncbase/bridge/cgo/lib.h b/services/syncbase/bridge/cgo/lib.h
index 64d849a..d216df6 100644
--- a/services/syncbase/bridge/cgo/lib.h
+++ b/services/syncbase/bridge/cgo/lib.h
@@ -131,25 +131,18 @@
 ////////////////////////////////////////
 // Functions
 
-// Callbacks are represented as struct
-// {v23_syncbase_Handle, f(v23_syncbase_Handle, ...)} to allow for currying
-// RefMap handles to Swift closures.
-// https://forums.developer.apple.com/message/15725#15725
-
-typedef int v23_syncbase_Handle;
+typedef void* v23_syncbase_Handle;
 
 typedef struct {
-  v23_syncbase_Handle hOnChange;
-  v23_syncbase_Handle hOnError;
-  void (*onChange)(v23_syncbase_Handle hOnChange, v23_syncbase_WatchChange);
-  void (*onError)(v23_syncbase_Handle hOnChange, v23_syncbase_Handle hOnError, v23_syncbase_VError);
+  v23_syncbase_Handle handle;
+  void (*onChange)(v23_syncbase_Handle handle, v23_syncbase_WatchChange);
+  void (*onError)(v23_syncbase_Handle handle, v23_syncbase_VError);
 } v23_syncbase_DbWatchPatternsCallbacks;
 
 typedef struct {
-  v23_syncbase_Handle hOnKeyValue;
-  v23_syncbase_Handle hOnDone;
-  void (*onKeyValue)(v23_syncbase_Handle hOnKeyValue, v23_syncbase_KeyValue);
-  void (*onDone)(v23_syncbase_Handle hOnKeyValue, v23_syncbase_Handle hOnDone, v23_syncbase_VError);
+  v23_syncbase_Handle handle;
+  void (*onKeyValue)(v23_syncbase_Handle handle, v23_syncbase_KeyValue);
+  void (*onDone)(v23_syncbase_Handle handle, v23_syncbase_VError);
 } v23_syncbase_CollectionScanCallbacks;
 
 #endif  // V23_SYNCBASE_LIB_H_
diff --git a/services/syncbase/bridge/cgo/refmap/refmap.go b/services/syncbase/bridge/cgo/refmap/refmap.go
new file mode 100644
index 0000000..a3ab087
--- /dev/null
+++ b/services/syncbase/bridge/cgo/refmap/refmap.go
@@ -0,0 +1,49 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package refmap
+
+import (
+	"sync"
+)
+
+func NewRefMap() *refMap {
+	return &refMap{
+		refs: make(map[uint64]interface{}),
+	}
+}
+
+type refMap struct {
+	refs   map[uint64]interface{}
+	lastId uint64
+	lock   sync.Mutex
+}
+
+func (r *refMap) Add(val interface{}) uint64 {
+	r.lock.Lock()
+	defer r.lock.Unlock()
+	id := r.lastId
+	r.lastId++
+	r.refs[id] = val
+	return id
+}
+
+func (r *refMap) Get(id uint64) interface{} {
+	r.lock.Lock()
+	defer r.lock.Unlock()
+	if val, ok := r.refs[id]; ok {
+		return val
+	}
+	return nil
+}
+
+func (r *refMap) Remove(id uint64) interface{} {
+	r.lock.Lock()
+	defer r.lock.Unlock()
+	if val, ok := r.refs[id]; ok {
+		delete(r.refs, id)
+		return val
+	}
+	return nil
+}
diff --git a/services/syncbase/longevity_tests/client/util.go b/services/syncbase/longevity_tests/client/util.go
index 0fc49d1..c4351e8 100644
--- a/services/syncbase/longevity_tests/client/util.go
+++ b/services/syncbase/longevity_tests/client/util.go
@@ -88,7 +88,7 @@
 				// TODO(nlacasse): Parameterize number of retries.  Exponential
 				// backoff?
 				var joinErr error
-				for i := 0; i < 10; i++ {
+				for {
 					_, joinErr = sg.Join(ctx, sgModel.HostDevice.Name, nil, wire.SyncgroupMemberInfo{})
 					if joinErr == nil {
 						syncgroups = append(syncgroups, sg)
diff --git a/services/syncbase/longevity_tests/control/instance.go b/services/syncbase/longevity_tests/control/instance.go
index f88f011..7ce9c82 100644
--- a/services/syncbase/longevity_tests/control/instance.go
+++ b/services/syncbase/longevity_tests/control/instance.go
@@ -79,7 +79,7 @@
 		"--v23.namespace.root="+inst.namespaceRoot,
 		"--v23.credentials="+inst.credsDir,
 		"--v23.permissions.literal="+perms,
-		//"--vmodule=*=2",
+		"--vpath=vsync=5",
 	)
 	inst.cmd.Start()
 	vars := inst.cmd.AwaitVars("ENDPOINT")