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

[WIP/RFC] Pointer indirection #196

Merged
merged 15 commits into from
May 30, 2015
Merged

[WIP/RFC] Pointer indirection #196

merged 15 commits into from
May 30, 2015

Conversation

pks-t
Copy link
Member

@pks-t pks-t commented Apr 24, 2015

This pull request fixes issues with Go callbacks that are handed over to C functions. As of Go 1.4 it is not possible to hand over Go pointers into C code as the garbage collector is allowed to copy around the stack, causing pointers to become invalid (see golang/go#8310 and golang/go#10303 for more information).

@carlosmn proposed an interface HandleList for pointer indirection which provides a global map of data that is to be tracked. Instead of handing in pointers we now instead hand over an index that points to the corresponding entry in this map.

This PR adds the HandleList implemented by @carlosmn, fixes some issues with it and adjusts code that hands over Go pointers into C code to use handles instead. Still missing is the remote.go code, as I am running into some error there ('runtime: garbage collector found invalid heap pointer *(0xc208063cd0+0x18)=0x1 s=nil') that I've not been able to fix.

This PR also fixes #163.

@pks-t
Copy link
Member Author

pks-t commented Apr 27, 2015

I guess it makes sense to not fix the remotes code but instead wait for libgit2/libgit2#3066 to be merged, as the interface will change anyway. The rest could be merged independetly of this, though.

return callback(C.GoString(cPath), C.GoString(cMatchedPathspec))
} else {
return -1
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The payload wasn't optional before, it shouldn't be now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, if you take a look at index_test.go:TestIndexAddAllNoCallback it seems as if the callback was optional after all.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is optional to pass, but we only tell libgit2 to call indexMatchedPathCallback if the caller did pass in the callback. This function only gets called if there is a callback.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, agreed and fixed.

@pks-t
Copy link
Member Author

pks-t commented May 4, 2015

I've changed the code to panic when no treewalk/submodulevisitor callback could be found. The indexMatchedPathCallback payload may be nil, though, as it is allowed to call AddAll without a callback.

@pks-t
Copy link
Member Author

pks-t commented May 16, 2015

Any comments regarding this PR?

@taylorchu
Copy link
Contributor

I tested "git diff" with go1.4. It solves the panic caused by GC.

@carlosmn
Copy link
Member

There's still callbacks where we check whether the pointer is valid. If we do not find the handle in the list, there is no way we can continue running the program, as the state has been corrupted. We should either assume that the callback is correct, or panic if the retrieval fails.

@taylorchu
Copy link
Contributor

@pks-t

The latest commits broke it again.

runtime: garbage collector found invalid heap pointer *(0xc20806bc88+0x18)=0x1 s=nil
fatal error: invalid heap pointer

runtime stack:
runtime.throw(0x92fb83)
    /usr/lib/go/src/runtime/panic.go:491 +0xad fp=0x7ffd22631088 sp=0x7ffd22631058
scanblock(0xc20806bc88, 0x28, 0x6681c8)
    /usr/lib/go/src/runtime/mgc0.c:381 +0x551 fp=0x7ffd226311c8 sp=0x7ffd22631088
scanframe(0x7ffd226312d0, 0x0, 0x1)
    /usr/lib/go/src/runtime/mgc0.c:743 +0x1c2 fp=0x7ffd22631238 sp=0x7ffd226311c8
runtime.gentraceback(0x44d160, 0xc20806b9d0, 0x0, 0xc208000120, 0x0, 0x0, 0x7fffffff, 0x7ffd22631380, 0x0, 0x0, ...)
    /usr/lib/go/src/runtime/traceback.go:311 +0x7a8 fp=0x7ffd22631328 sp=0x7ffd22631238
scanstack(0xc208000120)
    /usr/lib/go/src/runtime/mgc0.c:780 +0x21c fp=0x7ffd22631398 sp=0x7ffd22631328
markroot(0xc208010000, 0x5)
    /usr/lib/go/src/runtime/mgc0.c:556 +0xe7 fp=0x7ffd226313f8 sp=0x7ffd22631398
runtime.parfordo(0xc208010000)
    /usr/lib/go/src/runtime/parfor.c:76 +0xb2 fp=0x7ffd22631478 sp=0x7ffd226313f8
gc(0x7ffd226315b0)
    /usr/lib/go/src/runtime/mgc0.c:1442 +0x25e fp=0x7ffd22631590 sp=0x7ffd22631478
runtime.gc_m()
    /usr/lib/go/src/runtime/mgc0.c:1371 +0xe0 fp=0x7ffd226315c8 sp=0x7ffd22631590
runtime.onM(0x7ffd22631db8)
    /usr/lib/go/src/runtime/asm_amd64.s:257 +0x68 fp=0x7ffd226315d0 sp=0x7ffd226315c8
runtime.mstart()
    /usr/lib/go/src/runtime/proc.c:818 fp=0x7ffd226315d8 sp=0x7ffd226315d0

goroutine 1 [garbage collection, locked to thread]:
runtime.switchtoM()
    /usr/lib/go/src/runtime/asm_amd64.s:198 fp=0xc20806b9d8 sp=0xc20806b9d0
runtime.gogc(0x7fea00000000)
    /usr/lib/go/src/runtime/malloc.go:469 +0x1cf fp=0xc20806ba10 sp=0xc20806b9d8
runtime.mallocgc(0x30, 0x0, 0x7fea00000003, 0xc20802b4ca)
    /usr/lib/go/src/runtime/malloc.go:341 +0x391 fp=0xc20806bac0 sp=0xc20806ba10
runtime.rawstring(0x2b, 0x0, 0x0, 0x0, 0x0, 0x0)
    /usr/lib/go/src/runtime/string.go:195 +0x93 fp=0xc20806baf0 sp=0xc20806bac0
runtime.gostringn(0x12abd58, 0x2b, 0x0, 0x0)
    /usr/lib/go/src/runtime/string.go:275 +0x57 fp=0xc20806bb50 sp=0xc20806baf0
github.com/taylorchu/git2go._Cfunc_GoStringN(0x12abd58, 0xc20000002b, 0x0, 0x0)
    /home/march/src/github.com/taylorchu/git2go/:321 +0x4b fp=0xc20806bb78 sp=0xc20806bb50
github.com/taylorchu/git2go.diffLineFromC(0x127e0c0, 0x7ffd22631a30, 0x7ffd226316c0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0)
    /home/march/src/github.com/taylorchu/git2go/diff.go:121 +0x42 fp=0xc20806bbd0 sp=0xc20806bb78
github.com/taylorchu/git2go.diffForEachLineCb(0x127e0c0, 0x7ffd22631a30, 0x7ffd226316c0, 0x1, 0x0)
    /home/march/src/github.com/taylorchu/git2go/diff.go:333 +0xf8 fp=0xc20806bc88 sp=0xc20806bbd0
runtime.call64(0x7ffd22631600, 0x7ffd22631680, 0x28)
    /usr/lib/go/src/runtime/asm_amd64.s:403 +0x45 fp=0xc20806bcd0 sp=0xc20806bc88
runtime.cgocallbackg1()
    /usr/lib/go/src/runtime/cgocall.go:239 +0x12a fp=0xc20806bd30 sp=0xc20806bcd0
runtime.cgocallbackg()
    /usr/lib/go/src/runtime/cgocall.go:193 +0x6e fp=0xc20806bd60 sp=0xc20806bd30
runtime.cgocallback_gofunc(0x44e68f, 0x419a9d, 0x40f2f0)
    /usr/lib/go/src/runtime/asm_amd64.s:766 +0x57 fp=0xc20806bd70 sp=0xc20806bd60
asmcgocall()
    /usr/lib/go/src/runtime/asm_amd64.s:649 +0x37 fp=0xc20806bd78 sp=0xc20806bd70
runtime.asmcgocall_errno(0x40f2f0, 0xc20806bdc8)
    /usr/lib/go/src/runtime/asm_amd64.s:626 +0xf fp=0xc20806bd80 sp=0xc20806bd78
runtime.cgocall_errno(0x40f2f0, 0xc20806bdc8, 0x0)
    /usr/lib/go/src/runtime/cgocall.go:131 +0x10d fp=0xc20806bda8 sp=0xc20806bd80
github.com/taylorchu/git2go._Cfunc__go_git_diff_foreach(0x127ddc0, 0x100000001, 0xc200000001, 0x1, 0x0)
    /home/march/src/github.com/taylorchu/git2go/:336 +0x43 fp=0xc20806bdc8 sp=0xc20806bda8
github.com/taylorchu/git2go.(*Diff).ForEach(0xc20803e058, 0x6726b8, 0x2, 0x0, 0x0)
    /home/march/src/github.com/taylorchu/git2go/diff.go:272 +0x17e fp=0xc20806be28 sp=0xc20806bdc8
main.main()
    /home/march/next/main.go:40 +0x4ce fp=0xc20806bf98 sp=0xc20806be28
runtime.main()
    /usr/lib/go/src/runtime/proc.go:63 +0xf3 fp=0xc20806bfe0 sp=0xc20806bf98
runtime.goexit()
    /usr/lib/go/src/runtime/asm_amd64.s:2232 +0x1 fp=0xc20806bfe8 sp=0xc20806bfe0

goroutine 2 [force gc (idle)]:
runtime.gopark(0x445080, 0x934480, 0x61d930, 0xf)
    /usr/lib/go/src/runtime/proc.go:130 +0x105 fp=0xc20801a798 sp=0xc20801a768
runtime.goparkunlock(0x934480, 0x61d930, 0xf)
    /usr/lib/go/src/runtime/proc.go:136 +0x48 fp=0xc20801a7c0 sp=0xc20801a798
runtime.forcegchelper()
    /usr/lib/go/src/runtime/proc.go:99 +0xce fp=0xc20801a7e0 sp=0xc20801a7c0
runtime.goexit()
    /usr/lib/go/src/runtime/asm_amd64.s:2232 +0x1 fp=0xc20801a7e8 sp=0xc20801a7e0
created by runtime.init·4
    /usr/lib/go/src/runtime/proc.go:87 +0x25

goroutine 3 [GC sweep wait]:
runtime.gopark(0x445080, 0x93c400, 0x616d30, 0xd)
    /usr/lib/go/src/runtime/proc.go:130 +0x105 fp=0xc20801df98 sp=0xc20801df68
runtime.goparkunlock(0x93c400, 0x616d30, 0xd)
    /usr/lib/go/src/runtime/proc.go:136 +0x48 fp=0xc20801dfc0 sp=0xc20801df98
runtime.bgsweep()
    /usr/lib/go/src/runtime/mgc0.go:98 +0xbc fp=0xc20801dfe0 sp=0xc20801dfc0
runtime.goexit()
    /usr/lib/go/src/runtime/asm_amd64.s:2232 +0x1 fp=0xc20801dfe8 sp=0xc20801dfe0
created by gc
    /usr/lib/go/src/runtime/mgc0.c:1386

goroutine 4 [finalizer wait]:
runtime.gopark(0x445080, 0x93c3f8, 0x61d350, 0xe)
    /usr/lib/go/src/runtime/proc.go:130 +0x105 fp=0xc208019730 sp=0xc208019700
runtime.goparkunlock(0x93c3f8, 0x61d350, 0xe)
    /usr/lib/go/src/runtime/proc.go:136 +0x48 fp=0xc208019758 sp=0xc208019730
runtime.runfinq()
    /usr/lib/go/src/runtime/malloc.go:727 +0xba fp=0xc2080197e0 sp=0xc208019758
runtime.goexit()
    /usr/lib/go/src/runtime/asm_amd64.s:2232 +0x1 fp=0xc2080197e8 sp=0xc2080197e0
created by runtime.createfing
    /usr/lib/go/src/runtime/malloc.go:707 +0x5e

goroutine 17 [syscall, locked to thread]:
runtime.goexit()
    /usr/lib/go/src/runtime/asm_amd64.s:2232 +0x1 fp=0xc208068fe8 sp=0xc208068fe0

@pks-t
Copy link
Member Author

pks-t commented May 22, 2015

@taylorchu Eh, you're correct. Seems as if I worked on an outdated branch of mine.

carlosmn and others added 15 commits May 22, 2015 09:02
As the Go runtime can move stacks at any point and the C code runs
concurrently with the rest of the system, we cannot assume that the
payloads we give to the C code will stay valid for any particular
duration.

We must therefore give the C code handles which we can then look up in
our own list when the callbacks get called.
Using 0 as the first slot indice leads to not being able to
differentiate between a handle to the first element or a
NULL-handle. As current code may check whether the pointer is
NULL, change the first indice to be 1 instead.
If we store values by uintptrs the GC may try to inspect their
values when it kicks in. As the pointers are most likely invalid,
this will result in an invalid pointer dereference, causing the
program to panic. We can fix this by storing values by an int
index value instead, returning pointers to those indices as
handles instead.
@pks-t
Copy link
Member Author

pks-t commented May 22, 2015

Interestingly enough the problem was not caused by an outdated copy but by a change of the GC behavior. When the GC kicks in it inspects uintptr values, causing invalid dereferences due to us storing callback data by arbitrary uinptrs. I have no idea why the issue did not occur previously, but it's fixed now (at least for me). @taylorchu could you please verify the fix?

@pks-t
Copy link
Member Author

pks-t commented May 29, 2015

Short bump. Any remarks left?

carlosmn added a commit that referenced this pull request May 30, 2015
@carlosmn carlosmn merged commit 35ff0de into libgit2:master May 30, 2015
@dmitshur
Copy link
Contributor

dmitshur commented Jul 7, 2015

Hi,

I believe there is a relatively minor (and fixable) problem with the code change in this PR (I got to this PR from commit 1bd338a, which touches the relevant code). I have found a reproducible test case that causes a panic because the Go garbage collector triggers and removes something that shouldn't be removed (because there are no references to it in the Go world, only in the C world). (/cc @slimsag, thanks for reviewing my findings and suggesting a fix.)

I will create an issue with some steps you can follow to reproduce the issue.

I also have an MVP fix for the issue, that reliably resolves the problem. There's more than one way of doing it, so I welcome review and suggestions to change it, but I believe the general idea of the fix is sound, or at least in the right direction (basically, to keep a reference to the value in Go world memory so that it doesn't get GCed). I will create the PR and reference the issue.

@dmitshur
Copy link
Contributor

dmitshur commented Jul 7, 2015

I've made that issue with a reproducible failing test case in #218, and a PR that fixes it in #219.

@pks-t pks-t deleted the pointer-indirection branch August 13, 2015 10:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

garbage collector found invalid heap pointer: Go 1.4
4 participants