golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
123.85k stars 17.65k forks source link

x/mobile/bind/seq: Seq can throw "java.lang.RuntimeException: unknown java Ref" when a legitimate Java object is passed to Go #10933

Closed rod-hynes closed 9 years ago

rod-hynes commented 9 years ago

Summary

When a Java stub object instance is passed into Go, and then Go's garbage collection runs, and then the same Java object is passed into Go again, an exception is thrown in go.Seq.java and the Android app crashes.

Test setup

As best as I can tell, the problem may be a lifetime mismatch in the go.Seq.Ref reference counting. The constructor of the Stub creates a reference. When the Java object reference is passed to Go, the reference is scheduled for deletion using runtime.SetFinalizer here. The SetFinalizer handler signals the Java reference manager to delete the reference. But since the reference is only added in the Java Stub object constructor, there's no valid reference when the Java object is used again after the SetFinalizer signal and the exception will be thrown.

The docs on passing foreign language objects to Go don't indicate that this reuse of a Java Stub object is not supported.

One workaround is to create a new, proxy Java object each time you wish to pass the same long-lived Java object into Go.

Exception

05-21 15:52:16.957    2280-2313/ca.psiphon.androidgolanghello E/RECEIVER-HELLO﹕ Hello, a!
05-21 15:52:16.958    2280-2313/ca.psiphon.androidgolanghello E/RECEIVER-HELLO﹕ Hello, b!
05-21 15:52:16.961    2280-2313/ca.psiphon.androidgolanghello E/AndroidRuntime﹕ FATAL EXCEPTION: pool-1-thread-1
    Process: ca.psiphon.androidgolanghello, PID: 2280
    java.lang.RuntimeException: unknown java Ref: 42
            at go.Seq$RefTracker.get(Seq.java:252)
            at go.Seq$1.run(Seq.java:126)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
            at java.lang.Thread.run(Thread.java:818)

Sample Go package

package hirecv

import (
    "fmt"
    "runtime"
)

type Receiver interface {
    Hello(message string)
}

func Hello(r Receiver, name string) {
    r.Hello(fmt.Sprintf("Hello, %s!\n", name))
}

func GarbageCollect() {
    runtime.GC()
}

Sample Android app using the Go package

public class MainActivity extends ActionBarActivity {

    static class Receiver extends Hirecv.Receiver.Stub {
        @Override
        public void Hello(String s) {
            android.util.Log.e("RECEIVER-HELLO", s);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Go.init(this);

        Receiver receiver = new Receiver();
        Hirecv.Hello(receiver, "a");
        Hirecv.Hello(receiver, "b");
        Hirecv.GarbageCollect();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
        }
        Hirecv.Hello(receiver, "c");
    }
...
ianlancetaylor commented 9 years ago

CC @crawshaw @hyangah

gopherbot commented 9 years ago

CL https://golang.org/cl/10638 mentions this issue.