java: Implement the Database.WatchPatterns and Collection.Scan
I wasn't able to see that the onChange is called (which matches what
zinman@ also observed).
This change also contains two fixes to the ScanCallbacks interface:
- the onValue method is renamed to onKeyValue
- the onDone is updated to take a VError argument.
Due to some new "inconsistent definitions" I also had to move the
jFindClass from jni_util to jni.go. The bottom of the jni.go and the
jni_lib.go are also sorted.
MultiPart: 1/2
Change-Id: Ie3a07a22cadb88fe3c888da5453fbc3ff0e293f1
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/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
+}