github.com/F4RD1N/gomobile@v1.0.1/bind/java/Seq.java (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package go; 6 7 import android.content.Context; 8 9 import java.lang.ref.PhantomReference; 10 import java.lang.ref.Reference; 11 import java.lang.ref.ReferenceQueue; 12 import java.util.Arrays; 13 import java.util.Collection; 14 import java.util.Collections; 15 import java.util.IdentityHashMap; 16 import java.util.HashSet; 17 import java.util.Set; 18 import java.util.logging.Logger; 19 20 import go.Universe; 21 22 // Seq is a sequence of machine-dependent encoded values. 23 // Used by automatically generated language bindings to talk to Go. 24 public class Seq { 25 private static Logger log = Logger.getLogger("GoSeq"); 26 27 // also known to bind/seq/ref.go and bind/objc/seq_darwin.m 28 private static final int NULL_REFNUM = 41; 29 30 // use single Ref for null Object 31 public static final Ref nullRef = new Ref(NULL_REFNUM, null); 32 33 // The singleton GoRefQueue 34 private static final GoRefQueue goRefQueue = new GoRefQueue(); 35 36 static { 37 System.loadLibrary("gojni"); 38 init(); 39 Universe.touch(); 40 } 41 42 // setContext sets the context in the go-library to be used in RunOnJvm. 43 public static void setContext(Context context) { 44 setContext((java.lang.Object)context); 45 } 46 47 private static native void init(); 48 49 // Empty method to run class initializer 50 public static void touch() {} 51 52 private Seq() { 53 } 54 55 // ctx is an android.context.Context. 56 static native void setContext(java.lang.Object ctx); 57 58 public static void incRefnum(int refnum) { 59 tracker.incRefnum(refnum); 60 } 61 62 // incRef increments the reference count of Java objects. 63 // For proxies for Go objects, it calls into the Proxy method 64 // incRefnum() to make sure the Go reference count is positive 65 // even if the Proxy is garbage collected and its Ref is finalized. 66 public static int incRef(Object o) { 67 return tracker.inc(o); 68 } 69 70 public static int incGoObjectRef(GoObject o) { 71 return o.incRefnum(); 72 } 73 74 // trackGoRef tracks a Go reference and decrements its refcount 75 // when the given GoObject wrapper is garbage collected. 76 // 77 // TODO(crawshaw): We could cut down allocations for frequently 78 // sent Go objects by maintaining a map to weak references. This 79 // however, would require allocating two objects per reference 80 // instead of one. It also introduces weak references, the bane 81 // of any Java debugging session. 82 // 83 // When we have real code, examine the tradeoffs. 84 public static void trackGoRef(int refnum, GoObject obj) { 85 if (refnum > 0) { 86 throw new RuntimeException("trackGoRef called with Java refnum " + refnum); 87 } 88 goRefQueue.track(refnum, obj); 89 } 90 91 public static Ref getRef(int refnum) { 92 return tracker.get(refnum); 93 } 94 95 // Increment the Go reference count before sending over a refnum. 96 // The ref parameter is only used to make sure the referenced 97 // object is not garbage collected before Go increments the 98 // count. It's the equivalent of Go's runtime.KeepAlive. 99 public static native void incGoRef(int refnum, GoObject ref); 100 101 // Informs the Go ref tracker that Java is done with this refnum. 102 static native void destroyRef(int refnum); 103 104 // decRef is called from seq.FinalizeRef 105 static void decRef(int refnum) { 106 tracker.dec(refnum); 107 } 108 109 // A GoObject is a Java class implemented in Go. When a GoObject 110 // is passed to Go, it is wrapped in a Go proxy, to make it behave 111 // the same as passing a regular Java class. 112 public interface GoObject { 113 // Increment refcount and return the refnum of the proxy. 114 // 115 // The Go reference count need to be bumped while the 116 // refnum is passed to Go, to avoid finalizing and 117 // invalidating it before being translated on the Go side. 118 int incRefnum(); 119 } 120 // A Proxy is a Java object that proxies a Go object. Proxies, unlike 121 // GoObjects, are unwrapped to their Go counterpart when deserialized 122 // in Go. 123 public interface Proxy extends GoObject {} 124 125 // A Ref represents an instance of a Java object passed back and forth 126 // across the language boundary. 127 public static final class Ref { 128 public final int refnum; 129 130 private int refcnt; // Track how many times sent to Go. 131 132 public final Object obj; // The referenced Java obj. 133 134 Ref(int refnum, Object o) { 135 if (refnum < 0) { 136 throw new RuntimeException("Ref instantiated with a Go refnum " + refnum); 137 } 138 this.refnum = refnum; 139 this.refcnt = 0; 140 this.obj = o; 141 } 142 143 void inc() { 144 // Count how many times this ref's Java object is passed to Go. 145 if (refcnt == Integer.MAX_VALUE) { 146 throw new RuntimeException("refnum " + refnum + " overflow"); 147 } 148 refcnt++; 149 } 150 } 151 152 static final RefTracker tracker = new RefTracker(); 153 154 static final class RefTracker { 155 private static final int REF_OFFSET = 42; 156 157 // Next Java object reference number. 158 // 159 // Reference numbers are positive for Java objects, 160 // and start, arbitrarily at a different offset to Go 161 // to make debugging by reading Seq hex a little easier. 162 private int next = REF_OFFSET; // next Java object ref 163 164 // Java objects that have been passed to Go. refnum -> Ref 165 // The Ref obj field is non-null. 166 // This map pins Java objects so they don't get GCed while the 167 // only reference to them is held by Go code. 168 private final RefMap javaObjs = new RefMap(); 169 170 // Java objects to refnum 171 private final IdentityHashMap<Object, Integer> javaRefs = new IdentityHashMap<>(); 172 173 // inc increments the reference count of a Java object when it 174 // is sent to Go. inc returns the refnum for the object. 175 synchronized int inc(Object o) { 176 if (o == null) { 177 return NULL_REFNUM; 178 } 179 if (o instanceof Proxy) { 180 return ((Proxy)o).incRefnum(); 181 } 182 Integer refnumObj = javaRefs.get(o); 183 if (refnumObj == null) { 184 if (next == Integer.MAX_VALUE) { 185 throw new RuntimeException("createRef overflow for " + o); 186 } 187 refnumObj = next++; 188 javaRefs.put(o, refnumObj); 189 } 190 int refnum = refnumObj; 191 Ref ref = javaObjs.get(refnum); 192 if (ref == null) { 193 ref = new Ref(refnum, o); 194 javaObjs.put(refnum, ref); 195 } 196 ref.inc(); 197 return refnum; 198 } 199 200 synchronized void incRefnum(int refnum) { 201 Ref ref = javaObjs.get(refnum); 202 if (ref == null) { 203 throw new RuntimeException("referenced Java object is not found: refnum="+refnum); 204 } 205 ref.inc(); 206 } 207 208 // dec decrements the reference count of a Java object when 209 // Go signals a corresponding proxy object is finalized. 210 // If the count reaches zero, the Java object is removed 211 // from the javaObjs map. 212 synchronized void dec(int refnum) { 213 if (refnum <= 0) { 214 // We don't keep track of the Go object. 215 // This must not happen. 216 log.severe("dec request for Go object "+ refnum); 217 return; 218 } 219 if (refnum == Seq.nullRef.refnum) { 220 return; 221 } 222 // Java objects are removed on request of Go. 223 Ref obj = javaObjs.get(refnum); 224 if (obj == null) { 225 throw new RuntimeException("referenced Java object is not found: refnum="+refnum); 226 } 227 obj.refcnt--; 228 if (obj.refcnt <= 0) { 229 javaObjs.remove(refnum); 230 javaRefs.remove(obj.obj); 231 } 232 } 233 234 // get returns an existing Ref to a Java object. 235 synchronized Ref get(int refnum) { 236 if (refnum < 0) { 237 throw new RuntimeException("ref called with Go refnum " + refnum); 238 } 239 if (refnum == NULL_REFNUM) { 240 return nullRef; 241 } 242 Ref ref = javaObjs.get(refnum); 243 if (ref == null) { 244 throw new RuntimeException("unknown java Ref: "+refnum); 245 } 246 return ref; 247 } 248 } 249 250 // GoRefQueue is a queue of GoRefs that are no longer live. An internal thread 251 // processes the queue and decrement the reference count on the Go side. 252 static class GoRefQueue extends ReferenceQueue<GoObject> { 253 // The set of tracked GoRefs. If we don't hold on to the GoRef instances, the Java GC 254 // will not add them to the queue when their referents are reclaimed. 255 private final Collection<GoRef> refs = Collections.synchronizedCollection(new HashSet<GoRef>()); 256 257 void track(int refnum, GoObject obj) { 258 refs.add(new GoRef(refnum, obj, this)); 259 } 260 261 GoRefQueue() { 262 Thread daemon = new Thread(new Runnable() { 263 @Override public void run() { 264 while (true) { 265 try { 266 GoRef ref = (GoRef)remove(); 267 refs.remove(ref); 268 destroyRef(ref.refnum); 269 ref.clear(); 270 } catch (InterruptedException e) { 271 // Ignore 272 } 273 } 274 } 275 }); 276 daemon.setDaemon(true); 277 daemon.setName("GoRefQueue Finalizer Thread"); 278 daemon.start(); 279 } 280 } 281 282 // A GoRef is a PhantomReference to a Java proxy for a Go object. 283 // GoRefs are enqueued to the singleton GoRefQueue when no longer live, 284 // so the corresponding reference count can be decremented. 285 static class GoRef extends PhantomReference<GoObject> { 286 final int refnum; 287 288 GoRef(int refnum, GoObject obj, GoRefQueue q) { 289 super(obj, q); 290 if (refnum > 0) { 291 throw new RuntimeException("GoRef instantiated with a Java refnum " + refnum); 292 } 293 this.refnum = refnum; 294 } 295 } 296 297 // RefMap is a mapping of integers to Ref objects. 298 // 299 // The integers can be sparse. In Go this would be a map[int]*Ref. 300 static final class RefMap { 301 private int next = 0; 302 private int live = 0; 303 private int[] keys = new int[16]; 304 private Ref[] objs = new Ref[16]; 305 306 RefMap() {} 307 308 Ref get(int key) { 309 int i = Arrays.binarySearch(keys, 0, next, key); 310 if (i >= 0) { 311 return objs[i]; 312 } 313 return null; 314 } 315 316 void remove(int key) { 317 int i = Arrays.binarySearch(keys, 0, next, key); 318 if (i >= 0) { 319 if (objs[i] != null) { 320 objs[i] = null; 321 live--; 322 } 323 } 324 } 325 326 void put(int key, Ref obj) { 327 if (obj == null) { 328 throw new RuntimeException("put a null ref (with key "+key+")"); 329 } 330 int i = Arrays.binarySearch(keys, 0, next, key); 331 if (i >= 0) { 332 if (objs[i] == null) { 333 objs[i] = obj; 334 live++; 335 } 336 if (objs[i] != obj) { 337 throw new RuntimeException("replacing an existing ref (with key "+key+")"); 338 } 339 return; 340 } 341 if (next >= keys.length) { 342 grow(); 343 i = Arrays.binarySearch(keys, 0, next, key); 344 } 345 i = ~i; 346 if (i < next) { 347 // Insert, shift everything afterwards down. 348 System.arraycopy(keys, i, keys, i+1, next-i); 349 System.arraycopy(objs, i, objs, i+1, next-i); 350 } 351 keys[i] = key; 352 objs[i] = obj; 353 live++; 354 next++; 355 } 356 357 private void grow() { 358 // Compact and (if necessary) grow backing store. 359 int[] newKeys; 360 Ref[] newObjs; 361 int len = 2*roundPow2(live); 362 if (len > keys.length) { 363 newKeys = new int[keys.length*2]; 364 newObjs = new Ref[objs.length*2]; 365 } else { 366 newKeys = keys; 367 newObjs = objs; 368 } 369 370 int j = 0; 371 for (int i = 0; i < keys.length; i++) { 372 if (objs[i] != null) { 373 newKeys[j] = keys[i]; 374 newObjs[j] = objs[i]; 375 j++; 376 } 377 } 378 for (int i = j; i < newKeys.length; i++) { 379 newKeys[i] = 0; 380 newObjs[i] = null; 381 } 382 383 keys = newKeys; 384 objs = newObjs; 385 next = j; 386 387 if (live != next) { 388 throw new RuntimeException("bad state: live="+live+", next="+next); 389 } 390 } 391 392 private static int roundPow2(int x) { 393 int p = 1; 394 while (p < x) { 395 p *= 2; 396 } 397 return p; 398 } 399 } 400 }