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