Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

syscall: add NewCallbackCDecl definition #6338

Closed
gopherbot opened this issue Sep 5, 2013 · 21 comments
Closed

syscall: add NewCallbackCDecl definition #6338

gopherbot opened this issue Sep 5, 2013 · 21 comments

Comments

@gopherbot
Copy link

by kin.wilson.za:

changeset 9597:27a0abf209ed added to .goc but left out of .go
@alexbrainman
Copy link
Member

Comment 1:

True. Is that a problem? Should we do anything about it? If yes, please, explain the
why? Thank you.
Alex

Status changed to WaitingForReply.

@rsc
Copy link
Contributor

rsc commented Sep 6, 2013

Comment 2:

Adding to the .goc file (which is a C file in disguise) implements a function but does
not make it available for use by Go programs. To do that you have to add a prototype
(without a function body) in a .go file.
I would imagine you need to define NewCallbackCDecl in syscall/syscall_windows.go next
to NewCallback.

Labels changed: added priority-later, go1.2, removed priority-triage.

Status changed to Accepted.

@alexbrainman
Copy link
Member

Comment 3:

Thank you, Russ. I know how to make it available for use by Go programs. But before I
do, I would like to know how people use it. Also I would like to have a test so we can
satisfy ourselves that it actually works, and so we can keep it working in the future.
Alex

Status changed to WaitingForReply.

@gopherbot
Copy link
Author

Comment 4 by kin.wilson.za:

I'll concoct a free-standing test for you in a day or two. As you know, Wndows uses
Stdcall for its api callbacks, so a test that doesn' rely on a foreign dll will by
necessity have to be cgo-ased
Tony Wilson

@gopherbot
Copy link
Author

Comment 5 by kin.wilson.za:

Just realised "C" is  a no-go from "testing" tests.
Do as you must.
I raised the issue and pointed at the solution but cannot seek to mandate its
implementation based on a single need in years.
I am developing swigless generic marshalling using reflect MakeFunc and for the sake of
completeness would like it but can find a way around if it is removed.
Hopefully code review has improved since back then.

@gopherbot
Copy link
Author

Comment 6 by kin.wilson.za:

Just realised "C" is  a no-go from "testing" tests.
Do as you must.
I raised the issue and pointed at the solution but cannot seek to mandate its
implementation based on a single need in years.
I am developing swigless generic marshalling using reflect MakeFunc and for the sake of
completeness would like it but can find a way around if it is removed.
Hopefully code review has improved since back then.

@gopherbot
Copy link
Author

Comment 7 by kin.wilson.za:

func compare(i1, i2 int) int {
  return i1 - i2
}
...
    c := syscall.NewCallbackCDecl(compare)
    for i := 1; i < 4; i++ {
        r,_,_:=syscall.Syscall(c,2,uintptr(i),2,0)
        if int(r) != i-2 {
            t.Fatal...
...
Tests the mechanism but does not prove stack use and restoration.
Layout courtesy of no gofmt on this kindle
 :)

@alexbrainman
Copy link
Member

Comment 8:

You do not need syscall.NewCallbackCDecl to call Go function from Go function. Just call
them directly.
Alex

Status changed to WontFix.

@gopherbot
Copy link
Author

Comment 9 by kin.wilson.za:

Wow. If that's what you got from my input then I submit. There is no hope for me. My
career that started in 1973 is all to nought.

@alexbrainman
Copy link
Member

Comment 10:

Perhaps we don't understand each other, so lets try again.
You have asked to export runtime.NewCallbackCDecl. No one uses that function yet. So, I
would like to know, how you are planning to use it. So we could devise a test to make
sure we don't break that functionality in the future.
Alternatively, you can enable it yourself as per Russ's instructions. And we don't have
to do anything here.
Alex

@gopherbot
Copy link
Author

Comment 11 by kin.wilson.za:

1. By syscall.Syscall transparently catering for cdecl and stdcall external calls,
perhaps a false sense of security exists with NewCallback. 
2. Human nature being what it is the "don't-read-the-manual" group is probably much
larger than group who do. 
3. The Stdcall vs CDecl issue is arcane to a lot of people.
I give an example of incorrect use of NewCallback for a CDecl callback in [1]. The
program runs successfully and the calls-back do as they should. (The dlls GObject and
Gtk can be found in any windows system set up to use pprof with dot/Graphviz)
There may already be many programs in the wild doing the same thing.
Why the program doesn't fail is quite simple. Go program calls dll which in turn calls
the Go callback. Go callback should cut its arguments from the stack before returning
but doesn't. As most callbacks are a simple give and take operation, the dll and the Go
caller, by virtue of sp restoration don't even notice.
For enumeration type callbacks, where Go calls dll and dll has N>1 conversations with
the Go callback, the intra-dll stack integrity becomes a problem.
I include a demonstration dll [2][3], caller go package [4][5] and output [6.1][6.2][6.3]
[6.1] shows that calls with 2-9 arguments can be called a varying number of times by the
dll without failure. The threshold for each arity is different.
[6.2] shows the threshold of arity-3to9 being broken by 1 in each case.
[6.3] shows the effect of changing the optimization of the dll on the arity-2 callback,
which otherwise didn't fail.
It is my recommendation that you:
    - add NewCallbackCDecl
or  - strongly redocument NewCallback
or  - remove NewCallback
Glad I found out how to call Go from Go! :)
============== [1] example.go ===============
package main
import u "unsafe"
import f "fmt"
import s "syscall"
var (
    do = s.MustLoadDLL("libgobject-2.0-0.dll")
    dg = s.MustLoadDLL("libgtk-win32-2.0-0.dll")
    g_signal_connect_data          = do.MustFindProc("g_signal_connect_data")
    gtk_button_new_with_label      = dg.MustFindProc("gtk_button_new_with_label")
    gtk_container_add              = dg.MustFindProc("gtk_container_add")
    gtk_container_set_border_width = dg.MustFindProc("gtk_container_set_border_width")
    gtk_init                       = dg.MustFindProc("gtk_init")
    gtk_main                       = dg.MustFindProc("gtk_main")
    gtk_main_quit                  = dg.MustFindProc("gtk_main_quit")
    gtk_widget_show                = dg.MustFindProc("gtk_widget_show")
    gtk_window_new                 = dg.MustFindProc("gtk_window_new")
    gtk_widget_destroy = dg.MustFindProc("gtk_widget_destroy").Addr()
)
type void *struct{} // for bogus return values
func deleteEvent(a1, a2, a3 uintptr) uintptr {
    f.Println("delete event occurred with args", a1, a2, a3)
    return 0
}
func destroy(a1, a2 uintptr) (_ void) {
    // actually void return but syscall demands a return !!!
    f.Println("destroy event occurred with args", a1, a2)
    gtk_main_quit.Call()
    return
}
func hello(a1, a2 uintptr) (_ void) {
    // actually void return but syscall demands a return !!!
    f.Println("clicked event occurred with args", a1, a2)
    return
}
func cstring(i string) uintptr {
    r, _ := s.BytePtrFromString(i)
    return (uintptr)(u.Pointer(r))
}
func main() {
    defer do.Release()
    defer dg.Release()
    gtk_init.Call(0, 0)
    ncb := s.NewCallback // Should be s.NewCallbackCDecl
    W, _, _ := gtk_window_new.Call(0)
    //g_signal_connect(A,B,C,D) :-
    //  g_signal_connect_data(A,B,C,D,0,0).
    g_signal_connect_data.Call(
        W, cstring("delete-event"), ncb(deleteEvent), 123, 0, 0)
    g_signal_connect_data.Call(
        W, cstring("destroy"), ncb(destroy), 456, 0, 0)
    gtk_container_set_border_width.Call(W, 30)
    B, _, _ := gtk_button_new_with_label.Call(
        cstring("           Hello World             "))
    clicked := cstring("clicked")
    g_signal_connect_data.Call(
        B, clicked, ncb(hello), 789, 0, 0)
    //g_signal_connect_swapped(A,B,C,D) :-
    //  g_signal_connect_data(A,B,C,D,0,2).
    g_signal_connect_data.Call(
        B, clicked, gtk_widget_destroy, W, 0, 2)
    gtk_container_add.Call(W, B)
    gtk_widget_show.Call(B)
    gtk_widget_show.Call(W)
    gtk_main.Call()
}
============== [2] faux.c ===============
// __cdecl is default
typedef void (*ccb2)(int,int);
typedef void __stdcall (*stdcb2)(int,int);
typedef void (*ccb3)(int,int,int);
typedef void __stdcall (*stdcb3)(int,int,int);
typedef void (*ccb4)(int,int,int,int);
typedef void __stdcall (*stdcb4)(int,int,int,int);
typedef void (*ccb5)(int,int,int,int,int);
typedef void __stdcall (*stdcb5)(int,int,int,int,int);
typedef void (*ccb6)(int,int,int,int,int,int);
typedef void __stdcall (*stdcb6)(int,int,int,int,int,int);
typedef void (*ccb7)(int,int,int,int,int,int,int);
typedef void __stdcall (*stdcb7)(int,int,int,int,int,int,int);
typedef void (*ccb8)(int,int,int,int,int,int,int,int);
typedef void __stdcall (*stdcb8)(int,int,int,int,int,int,int,int);
typedef void (*ccb9)(int,int,int,int,int,int,int,int,int);
typedef void __stdcall (*stdcb9)(int,int,int,int,int,int,int,int,int);
void c2(ccb2 f, int n) {
    int i;
    for(i=0;i<n;i++){
        f(1,2);
    }
}
void std2(stdcb2 f, int n) {
    int i;
    for(i=0;i<n;i++){
        f(1,2);
    }
}
void c3(ccb3 f, int n) {
    int i;
    for(i=0;i<n;i++){
        f(1,2,3);
    }
}
void std3(stdcb3 f, int n) {
    int i;
    for(i=0;i<n;i++){
        f(1,2,3);
    }
}
void c4(ccb4 f, int n) {
    int i;
    for(i=0;i<n;i++){
        f(1,2,3,4);
    }
}
void std4(stdcb4 f, int n) {
    int i;
    for(i=0;i<n;i++){
        f(1,2,3,4);
    }
}
void c5(ccb5 f, int n) {
    int i;
    for(i=0;i<n;i++){
        f(1,2,3,4,5);
    }
}
void std5(stdcb5 f, int n) {
    int i;
    for(i=0;i<n;i++){
        f(1,2,3,4,5);
    }
}
void c6(ccb6 f, int n) {
    int i;
    for(i=0;i<n;i++){
        f(1,2,3,4,5,6);
    }
}
void std6(stdcb6 f, int n) {
    int i;
    for(i=0;i<n;i++){
        f(1,2,3,4,5,6);
    }
}
void c7(ccb7 f, int n) {
    int i;
    for(i=0;i<n;i++){
        f(1,2,3,4,5,6,7);
    }
}
void std7(stdcb7 f, int n) {
    int i;
    for(i=0;i<n;i++){
        f(1,2,3,4,5,6,7);
    }
}
void c8(ccb8 f, int n) {
    int i;
    for(i=0;i<n;i++){
        f(1,2,3,4,5,6,7,8);
    }
}
void std8(stdcb8 f, int n) {
    int i;
    for(i=0;i<n;i++){
        f(1,2,3,4,5,6,7,8);
    }
}
void c9(ccb9 f, int n) {
    int i;
    for(i=0;i<n;i++){
        f(1,2,3,4,5,6,7,8,9);
    }
}
void std9(stdcb9 f, int n) {
    int i;
    for(i=0;i<n;i++){
        f(1,2,3,4,5,6,7,8,9);
    }
}
============== [3] makedlls.bat ===============
gcc -shared -s -o faux.dll faux.c
gcc -shared -s -o fauxO2.dll -O2 faux.c
============== [4] test.go ===============
package test
============== [5] test_test.go ===============
package test
import (
    . "fmt"
    . "syscall"
    "testing"
)
/*
//bug?
//you can have:
func a() (_ int) { return }
//or:
var b = func() (_ int) { return }
//but not:
func c() {
    var d = func() (_ int) { return }
}
*/
//Callbacks must have a return!!! Unfortunate but true.
type void *struct{}
const dll = "faux.dll"
func test(n uintptr, extc, extstd string, ca, stda uintptr) {
    d := MustLoadDLL(dll)
    defer d.Release()
    d.MustFindProc(extstd).Call(stda, n)
    Println("Stdcall done")
    d.MustFindProc(extc).Call(ca, n)
    Println("CDecl done")
}
func TestCallbackIsUsed(t *testing.T) {
    n := uintptr(1)
    c := func(i1, i2 int) void {
        Println("in callback")
        if i1+i2 != 3 || i1 != i2-1 {
            t.Error("bad input")
        }
        return nil
    }
    ca := NewCallbackCDecl(c)
    s := func(i1, i2 int) void {
        Println("in callback")
        if i1+i2 != 3 || i1 != i2-1 {
            t.Error("bad input")
        }
        return nil
    }
    stda := NewCallback(s)
    Println(dll, n, "x 2-arg correctly using NewCallbackCDecl")
    test(n, "c2", "std2", ca, stda)
}
func TestNx2(t *testing.T) {
    n := uintptr(10000)
    c := func(i1, i2 int) void {
        if i1+i2 != 3 {
            t.Error("bad input")
        }
        return nil
    }
    ca := NewCallback(c)
    s := func(i1, i2 int) void {
        if i1+i2 != 3 {
            t.Error("bad input")
        }
        return nil
    }
    stda := NewCallback(s)
    Println(dll, n, "x 2-arg incorrectly using NewCallback")
    test(n, "c2", "std2", ca, stda)
}
func TestNx3(t *testing.T) {
    n := uintptr(3)
    c := func(i1, i2, i3 int) void {
        if i1+i2+i3 != 6 {
            t.Error("bad input")
        }
        return nil
    }
    ca := NewCallback(c)
    s := func(i1, i2, i3 int) void {
        if i1+i2+i3 != 6 {
            t.Error("bad input")
        }
        return nil
    }
    stda := NewCallback(s)
    Println(dll, n, "x 3-arg CDecl incorrectly using NewCallback")
    test(n, "c3", "std3", ca, stda)
}
func TestNx4(t *testing.T) {
    n := uintptr(5)
    c := func(i1, i2, i3, i4 int) void {
        if i1+i2+i3+i4 != 10 {
            t.Error("bad input")
        }
        return nil
    }
    ca := NewCallback(c)
    s := func(i1, i2, i3, i4 int) void {
        if i1+i2+i3+i4 != 10 {
            t.Error("bad input")
        }
        return nil
    }
    stda := NewCallback(s)
    Println(dll, n, "x 4-arg CDecl incorrectly using NewCallback")
    test(n, "c4", "std4", ca, stda)
}
func TestNx5(t *testing.T) {
    n := uintptr(2)
    c := func(i1, i2, i3, i4, i5 int) void {
        if i1+i2+i3+i4+i5 != 15 {
            t.Error("bad input")
        }
        return nil
    }
    ca := NewCallback(c)
    s := func(i1, i2, i3, i4, i5 int) void {
        if i1+i2+i3+i4+i5 != 15 {
            t.Error("bad input")
        }
        return nil
    }
    stda := NewCallback(s)
    Println(dll, n, "x 5-arg CDecl incorrectly using NewCallback")
    test(n, "c5", "std5", ca, stda)
}
func TestNx6(t *testing.T) {
    n := uintptr(7)
    c := func(i1, i2, i3, i4, i5, i6 int) void {
        if i1+i2+i3+i4+i5+i6 != 21 {
            t.Error("bad input")
        }
        return nil
    }
    ca := NewCallback(c)
    s := func(i1, i2, i3, i4, i5, i6 int) void {
        if i1+i2+i3+i4+i5+i6 != 21 {
            t.Error("bad input")
        }
        return nil
    }
    stda := NewCallback(s)
    Println(dll, n, "x 6-arg CDecl incorrectly using NewCallback")
    test(n, "c6", "std6", ca, stda)
}
func TestNx7(t *testing.T) {
    n := uintptr(6)
    c := func(i1, i2, i3, i4, i5, i6, i7 int) void {
        if i1+i2+i3+i4+i5+i6+i7 != 28 {
            t.Error("bad input")
        }
        return nil
    }
    ca := NewCallback(c)
    s := func(i1, i2, i3, i4, i5, i6, i7 int) void {
        if i1+i2+i3+i4+i5+i6+i7 != 28 {
            t.Error("bad input")
        }
        return nil
    }
    stda := NewCallback(s)
    Println(dll, n, "x 7-arg CDecl incorrectly using NewCallback")
    test(n, "c7", "std7", ca, stda)
}
func TestNx8(t *testing.T) {
    n := uintptr(1)
    c := func(i1, i2, i3, i4, i5, i6, i7, i8 int) void {
        if i1+i2+i3+i4+i5+i6+i7+i8 != 36 {
            t.Error("bad input")
        }
        return nil
    }
    ca := NewCallback(c)
    s := func(i1, i2, i3, i4, i5, i6, i7, i8 int) void {
        if i1+i2+i3+i4+i5+i6+i7+i8 != 36 {
            t.Error("bad input")
        }
        return nil
    }
    stda := NewCallback(s)
    Println(dll, n, "x 8-arg CDecl incorrectly using NewCallback")
    test(n, "c8", "std8", ca, stda)
}
func TestNx9(t *testing.T) {
    n := uintptr(8)
    c := func(i1, i2, i3, i4, i5, i6, i7, i8, i9 int) void {
        if i1+i2+i3+i4+i5+i6+i7+i8+i9 != 45 {
            t.Error("bad input")
        }
        return nil
    }
    ca := NewCallback(c)
    s := func(i1, i2, i3, i4, i5, i6, i7, i8, i9 int) void {
        if i1+i2+i3+i4+i5+i6+i7+i8+i9 != 45 {
            t.Error("bad input")
        }
        return nil
    }
    stda := NewCallback(s)
    Println(dll, n, "x 9-arg CDecl incorrectly using NewCallback")
    test(n, "c9", "std9", ca, stda)
}
func TestLooped1x9(t *testing.T) {
    d := MustLoadDLL(dll)
    defer d.Release()
    dc := d.MustFindProc("c9").Addr()
    c := func(i1, i2, i3, i4, i5, i6, i7, i8, i9 int) void {
        if i1+i2+i3+i4+i5+i6+i7+i8+i9 != 45 {
            t.Error("bad input")
        }
        return nil
    }
    ca := NewCallback(c)
    n := 10000
    Println(dll, n, "iterations of 1 x 9-arg CDecl incorrectly using NewCallback")
    for i := 0; i < n; i++ {
        Syscall(dc, 2, ca, 1, 0)
    }
}
============== [6.1] test output faux.dll ===============
=== RUN TestCallbackIsUsed
../faux.dll 1 x 2-arg correctly using NewCallbackCDecl
in callback
Stdcall done
in callback
CDecl done
--- PASS: TestCallbackIsUsed (0.17 seconds)
=== RUN TestNx2
../faux.dll 10000 x 2-arg incorrectly using NewCallback
Stdcall done
CDecl done
--- PASS: TestNx2 (0.02 seconds)
=== RUN TestNx3
../faux.dll 3 x 3-arg CDecl incorrectly using NewCallback
Stdcall done
CDecl done
--- PASS: TestNx3 (0.00 seconds)
=== RUN TestNx4
../faux.dll 5 x 4-arg CDecl incorrectly using NewCallback
Stdcall done
CDecl done
--- PASS: TestNx4 (0.00 seconds)
=== RUN TestNx5
../faux.dll 2 x 5-arg CDecl incorrectly using NewCallback
Stdcall done
CDecl done
--- PASS: TestNx5 (0.00 seconds)
=== RUN TestNx6
../faux.dll 7 x 6-arg CDecl incorrectly using NewCallback
Stdcall done
CDecl done
--- PASS: TestNx6 (0.00 seconds)
=== RUN TestNx7
../faux.dll 6 x 7-arg CDecl incorrectly using NewCallback
Stdcall done
CDecl done
--- PASS: TestNx7 (0.00 seconds)
=== RUN TestNx8
../faux.dll 1 x 8-arg CDecl incorrectly using NewCallback
Stdcall done
CDecl done
--- PASS: TestNx8 (0.00 seconds)
=== RUN TestNx9
../faux.dll 8 x 9-arg CDecl incorrectly using NewCallback
Stdcall done
CDecl done
--- PASS: TestNx9 (0.00 seconds)
=== RUN TestLooped1x9
../faux.dll 10000 iterations of 1 x 9-arg CDecl incorrectly using NewCallback
--- PASS: TestLooped1x9 (0.00 seconds)
PASS
ok      github.com/tHinqa/goIssue6338/test  0.312s
============== [6.2] test output faux.dll failures ===============
=== RUN TestNx3
../faux.dll 4 x 3-arg CDecl incorrectly using NewCallback
Stdcall done
fatal error: malloc/free - deadlock
[signal 0xc0000005 code=0x1 addr=0x2f0 pc=0x41ab28]
=== RUN TestNx4
../faux.dll 6 x 4-arg CDecl incorrectly using NewCallback
Stdcall done
fatal error: malloc/free - deadlock
[signal 0xc0000005 code=0x1 addr=0x2f0 pc=0x41ab28]
=== RUN TestNx5
../faux.dll 3 x 5-arg CDecl incorrectly using NewCallback
Stdcall done
!!!!!!!!!!!! No failure occurs !!!!!!!!!!!!! Program simply exited
=== RUN TestNx6
../faux.dll 8 x 6-arg CDecl incorrectly using NewCallback
Stdcall done
fatal error: malloc/free - deadlock
[signal 0xc0000005 code=0x1 addr=0x2f0 pc=0x41ab28]
=== RUN TestNx7
../faux.dll 7 x 7-arg CDecl incorrectly using NewCallback
Stdcall done
fatal error: malloc/free - deadlock
[signal 0xc0000005 code=0x1 addr=0x2f0 pc=0x41ab28]
=== RUN TestNx8
../faux.dll 2 x 8-arg CDecl incorrectly using NewCallback
Stdcall done
fatal error: malloc/free - deadlock
[signal 0xc0000005 code=0x1 addr=0x2f0 pc=0x41ab28]
=== RUN TestNx9
../faux.dll 9 x 9-arg CDecl incorrectly using NewCallback
Stdcall done
fatal error: malloc/free - deadlock
[signal 0xc0000005 code=0x1 addr=0x2f0 pc=0x41ab28]
============== [6.3] test output fauxO2.dll ===============
=== RUN TestNx2
../fauxO2.dll 10000 x 2-arg incorrectly using NewCallback
Stdcall done
exit status -1073741819
FAIL    github.com/tHinqa/goIssue6338/test  0.359s

@gopherbot
Copy link
Author

Comment 12 by kin.wilson.za:

Some personal notes which could be interpreted as being snarky were left in the code by
mistake.
Apologies.

@rsc
Copy link
Contributor

rsc commented Sep 23, 2013

Comment 13:

i don't understand what's going on here. if we're not going to export
NewCallbackCDecl we should delete the new code. if it can't be called, why
do we have it?

@alexbrainman
Copy link
Member

Comment 14:

> i don't understand what's going on here. ...
I am trying to decide if NewCallbackCDecl is that useful. I think the fact that it is
used by some products -  "... (The dlls GObject and Gtk can be found in any windows
system set up to use pprof with dot/Graphviz) ..." (as per
https://golang.org/issue/6338?c=11), means we should export it for
use. I will try to use some of kin.wilson.za code to build a test for NewCallbackCDecl,
so we don't break it in the future.
> ... if we're not going to export
> NewCallbackCDecl we should delete the new code. if it can't be called, why
> do we have it?
Looks like hector created the code https://golang.org/cl/4058046. And then
webmaster asked me to "export" the function https://golang.org/cl/4969047. Goes
to show how much he used it, as it still doesn't work. As to the code itself, I don't
think, there is much there to "remove" - callback code is pretty general now.
Alex

Status changed to Thinking.

@rsc
Copy link
Contributor

rsc commented Oct 3, 2013

Comment 15:

This issue was closed by revision 4216203.

Status changed to Fixed.

@alexbrainman
Copy link
Member

Comment 16:

kin.wilson.za
I will fix it after Go1.2 is released.
Alex

Labels changed: removed go1.2.

Status changed to Accepted.

@rsc
Copy link
Contributor

rsc commented Nov 27, 2013

Comment 17:

Labels changed: added go1.3maybe.

@rsc
Copy link
Contributor

rsc commented Dec 4, 2013

Comment 18:

Labels changed: added release-none, removed go1.3maybe.

@rsc
Copy link
Contributor

rsc commented Dec 4, 2013

Comment 19:

Labels changed: added repo-main.

@alexbrainman
Copy link
Member

Comment 20:

kin.wilson.za,
Here https://golang.org/cl/36180044/ is the change with your test. Please,
review. Thank you.
Alex

Status changed to Started.

@alexbrainman
Copy link
Member

Comment 21:

This issue was closed by revision 7f8a505.

Status changed to Fixed.

@golang golang locked and limited conversation to collaborators Jun 25, 2016
This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants