Skip to content

[WIP/RFC] Pointer indirection #196

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

Merged
merged 15 commits into from
May 30, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,13 @@ type BlobCallbackData struct {
}

//export blobChunkCb
func blobChunkCb(buffer *C.char, maxLen C.size_t, payload unsafe.Pointer) int {
data := (*BlobCallbackData)(payload)
func blobChunkCb(buffer *C.char, maxLen C.size_t, handle unsafe.Pointer) int {
payload := pointerHandles.Get(handle)
data, ok := payload.(*BlobCallbackData)
if !ok {
panic("could not retrieve blob callback data")
}

goBuf, err := data.Callback(int(maxLen))
if err == io.EOF {
return 0
Expand All @@ -83,8 +88,12 @@ func (repo *Repository) CreateBlobFromChunks(hintPath string, callback BlobChunk
defer C.free(unsafe.Pointer(chintPath))
}
oid := C.git_oid{}

payload := &BlobCallbackData{Callback: callback}
ecode := C._go_git_blob_create_fromchunks(&oid, repo.ptr, chintPath, unsafe.Pointer(payload))
handle := pointerHandles.Track(payload)
defer pointerHandles.Untrack(handle)

ecode := C._go_git_blob_create_fromchunks(&oid, repo.ptr, chintPath, handle)
if payload.Error != nil {
return nil, payload.Error
}
Expand Down
47 changes: 36 additions & 11 deletions diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,16 +265,24 @@ func (diff *Diff) ForEach(cbFile DiffForEachFileCallback, detail DiffDetail) err
data := &diffForEachData{
FileCallback: cbFile,
}
ecode := C._go_git_diff_foreach(diff.ptr, 1, intHunks, intLines, unsafe.Pointer(data))

handle := pointerHandles.Track(data)
defer pointerHandles.Untrack(handle)

ecode := C._go_git_diff_foreach(diff.ptr, 1, intHunks, intLines, handle)
if ecode < 0 {
return data.Error
}
return nil
}

//export diffForEachFileCb
func diffForEachFileCb(delta *C.git_diff_delta, progress C.float, payload unsafe.Pointer) int {
data := (*diffForEachData)(payload)
func diffForEachFileCb(delta *C.git_diff_delta, progress C.float, handle unsafe.Pointer) int {
payload := pointerHandles.Get(handle)
data, ok := payload.(*diffForEachData)
if !ok {
panic("could not retrieve data for handle")
}

data.HunkCallback = nil
if data.FileCallback != nil {
Expand All @@ -292,8 +300,12 @@ func diffForEachFileCb(delta *C.git_diff_delta, progress C.float, payload unsafe
type DiffForEachHunkCallback func(DiffHunk) (DiffForEachLineCallback, error)

//export diffForEachHunkCb
func diffForEachHunkCb(delta *C.git_diff_delta, hunk *C.git_diff_hunk, payload unsafe.Pointer) int {
data := (*diffForEachData)(payload)
func diffForEachHunkCb(delta *C.git_diff_delta, hunk *C.git_diff_hunk, handle unsafe.Pointer) int {
payload := pointerHandles.Get(handle)
data, ok := payload.(*diffForEachData)
if !ok {
panic("could not retrieve data for handle")
}

data.LineCallback = nil
if data.HunkCallback != nil {
Expand All @@ -311,9 +323,12 @@ func diffForEachHunkCb(delta *C.git_diff_delta, hunk *C.git_diff_hunk, payload u
type DiffForEachLineCallback func(DiffLine) error

//export diffForEachLineCb
func diffForEachLineCb(delta *C.git_diff_delta, hunk *C.git_diff_hunk, line *C.git_diff_line, payload unsafe.Pointer) int {

data := (*diffForEachData)(payload)
func diffForEachLineCb(delta *C.git_diff_delta, hunk *C.git_diff_hunk, line *C.git_diff_line, handle unsafe.Pointer) int {
payload := pointerHandles.Get(handle)
data, ok := payload.(*diffForEachData)
if !ok {
panic("could not retrieve data for handle")
}

err := data.LineCallback(diffLineFromC(delta, hunk, line))
if err != nil {
Expand Down Expand Up @@ -479,9 +494,15 @@ type diffNotifyData struct {
}

//export diffNotifyCb
func diffNotifyCb(_diff_so_far unsafe.Pointer, delta_to_add *C.git_diff_delta, matched_pathspec *C.char, payload unsafe.Pointer) int {
func diffNotifyCb(_diff_so_far unsafe.Pointer, delta_to_add *C.git_diff_delta, matched_pathspec *C.char, handle unsafe.Pointer) int {
diff_so_far := (*C.git_diff)(_diff_so_far)
data := (*diffNotifyData)(payload)

payload := pointerHandles.Get(handle)
data, ok := payload.(*diffNotifyData)
if !ok {
panic("could not retrieve data for handle")
}

if data != nil {
if data.Diff == nil {
data.Diff = newDiffFromC(diff_so_far)
Expand All @@ -507,6 +528,7 @@ func diffOptionsToC(opts *DiffOptions) (copts *C.git_diff_options, notifyData *d
notifyData = &diffNotifyData{
Callback: opts.NotifyCallback,
}

if opts.Pathspec != nil {
cpathspec.count = C.size_t(len(opts.Pathspec))
cpathspec.strings = makeCStringsFromStrings(opts.Pathspec)
Expand All @@ -527,7 +549,7 @@ func diffOptionsToC(opts *DiffOptions) (copts *C.git_diff_options, notifyData *d

if opts.NotifyCallback != nil {
C._go_git_setup_diff_notify_callbacks(copts)
copts.notify_payload = unsafe.Pointer(notifyData)
copts.notify_payload = pointerHandles.Track(notifyData)
}
}
return
Expand All @@ -539,6 +561,9 @@ func freeDiffOptions(copts *C.git_diff_options) {
freeStrarray(&cpathspec)
C.free(unsafe.Pointer(copts.old_prefix))
C.free(unsafe.Pointer(copts.new_prefix))
if copts.notify_payload != nil {
pointerHandles.Untrack(copts.notify_payload)
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions git.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,11 @@ var (
ErrInvalid = errors.New("Invalid state for operation")
)

var pointerHandles *HandleList

func init() {
pointerHandles = NewHandleList()

C.git_libgit2_init()

// This is not something we should be doing, as we may be
Expand Down
84 changes: 84 additions & 0 deletions handles.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package git

import (
"fmt"
"sync"
"unsafe"
)

type HandleList struct {
sync.RWMutex
// stores the Go pointers
handles []interface{}
// indicates which indices are in use
set map[int]bool
}

func NewHandleList() *HandleList {
return &HandleList{
handles: make([]interface{}, 5),
set: make(map[int]bool),
}
}

// findUnusedSlot finds the smallest-index empty space in our
// list. You must only run this function while holding a write lock.
func (v *HandleList) findUnusedSlot() int {
for i := 1; i < len(v.handles); i++ {
isUsed := v.set[i]
if !isUsed {
return i
}
}

// reaching here means we've run out of entries so append and
// return the new index, which is equal to the old length.
slot := len(v.handles)
v.handles = append(v.handles, nil)

return slot
}

// Track adds the given pointer to the list of pointers to track and
// returns a pointer value which can be passed to C as an opaque
// pointer.
func (v *HandleList) Track(pointer interface{}) unsafe.Pointer {
v.Lock()

slot := v.findUnusedSlot()
v.handles[slot] = pointer
v.set[slot] = true

v.Unlock()

return unsafe.Pointer(&slot)
}

// Untrack stops tracking the pointer given by the handle
func (v *HandleList) Untrack(handle unsafe.Pointer) {
slot := *(*int)(handle)

v.Lock()

v.handles[slot] = nil
delete(v.set, slot)

v.Unlock()
}

// Get retrieves the pointer from the given handle
func (v *HandleList) Get(handle unsafe.Pointer) interface{} {
slot := *(*int)(handle)

v.RLock()

if _, ok := v.set[slot]; !ok {
panic(fmt.Sprintf("invalid pointer handle: %p", handle))
}

ptr := v.handles[slot]

v.RUnlock()

return ptr
}
28 changes: 17 additions & 11 deletions index.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,16 +162,17 @@ func (v *Index) AddAll(pathspecs []string, flags IndexAddOpts, callback IndexMat
runtime.LockOSThread()
defer runtime.UnlockOSThread()

var cb *IndexMatchedPathCallback
var handle unsafe.Pointer
if callback != nil {
cb = &callback
handle = pointerHandles.Track(callback)
defer pointerHandles.Untrack(handle)
}

ret := C._go_git_index_add_all(
v.ptr,
&cpathspecs,
C.uint(flags),
unsafe.Pointer(cb),
handle,
)
if ret < 0 {
return MakeGitError(ret)
Expand All @@ -188,15 +189,16 @@ func (v *Index) UpdateAll(pathspecs []string, callback IndexMatchedPathCallback)
runtime.LockOSThread()
defer runtime.UnlockOSThread()

var cb *IndexMatchedPathCallback
var handle unsafe.Pointer
if callback != nil {
cb = &callback
handle = pointerHandles.Track(callback)
defer pointerHandles.Untrack(handle)
}

ret := C._go_git_index_update_all(
v.ptr,
&cpathspecs,
unsafe.Pointer(cb),
handle,
)
if ret < 0 {
return MakeGitError(ret)
Expand All @@ -213,15 +215,16 @@ func (v *Index) RemoveAll(pathspecs []string, callback IndexMatchedPathCallback)
runtime.LockOSThread()
defer runtime.UnlockOSThread()

var cb *IndexMatchedPathCallback
var handle unsafe.Pointer
if callback != nil {
cb = &callback
handle = pointerHandles.Track(callback)
defer pointerHandles.Untrack(handle)
}

ret := C._go_git_index_remove_all(
v.ptr,
&cpathspecs,
unsafe.Pointer(cb),
handle,
)
if ret < 0 {
return MakeGitError(ret)
Expand All @@ -231,8 +234,11 @@ func (v *Index) RemoveAll(pathspecs []string, callback IndexMatchedPathCallback)

//export indexMatchedPathCallback
func indexMatchedPathCallback(cPath, cMatchedPathspec *C.char, payload unsafe.Pointer) int {
callback := (*IndexMatchedPathCallback)(payload)
return (*callback)(C.GoString(cPath), C.GoString(cMatchedPathspec))
if callback, ok := pointerHandles.Get(payload).(IndexMatchedPathCallback); ok {
return callback(C.GoString(cPath), C.GoString(cMatchedPathspec))
} else {
panic("invalid matched path callback")
}
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.

}

func (v *Index) RemoveByPath(path string) error {
Expand Down
13 changes: 10 additions & 3 deletions odb.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,12 @@ type foreachData struct {
}

//export odbForEachCb
func odbForEachCb(id *C.git_oid, payload unsafe.Pointer) int {
data := (*foreachData)(payload)
func odbForEachCb(id *C.git_oid, handle unsafe.Pointer) int {
data, ok := pointerHandles.Get(handle).(*foreachData)

if !ok {
panic("could not retrieve handle")
}

err := data.callback(newOidFromC(id))
if err != nil {
Expand All @@ -119,7 +123,10 @@ func (v *Odb) ForEach(callback OdbForEachCallback) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()

ret := C._go_git_odb_foreach(v.ptr, unsafe.Pointer(&data))
handle := pointerHandles.Track(&data)
defer pointerHandles.Untrack(handle)

ret := C._go_git_odb_foreach(v.ptr, handle)
if ret == C.GIT_EUSER {
return data.err
} else if ret < 0 {
Expand Down
2 changes: 1 addition & 1 deletion odb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func TestOdbForeach(t *testing.T) {

checkFatal(t, err)
if count != expect {
t.Fatalf("Expected %v objects, got %v")
t.Fatalf("Expected %v objects, got %v", expect, count)
}

expect = 1
Expand Down
13 changes: 10 additions & 3 deletions packbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,13 @@ type packbuilderCbData struct {
}

//export packbuilderForEachCb
func packbuilderForEachCb(buf unsafe.Pointer, size C.size_t, payload unsafe.Pointer) int {
data := (*packbuilderCbData)(payload)
func packbuilderForEachCb(buf unsafe.Pointer, size C.size_t, handle unsafe.Pointer) int {
payload := pointerHandles.Get(handle)
data, ok := payload.(*packbuilderCbData)
if !ok {
panic("could not get packbuilder CB data")
}

slice := C.GoBytes(buf, C.int(size))

err := data.callback(slice)
Expand All @@ -130,11 +135,13 @@ func (pb *Packbuilder) ForEach(callback PackbuilderForeachCallback) error {
callback: callback,
err: nil,
}
handle := pointerHandles.Track(&data)
defer pointerHandles.Untrack(handle)

runtime.LockOSThread()
defer runtime.UnlockOSThread()

err := C._go_git_packbuilder_foreach(pb.ptr, unsafe.Pointer(&data))
err := C._go_git_packbuilder_foreach(pb.ptr, handle)
if err == C.GIT_EUSER {
return data.err
}
Expand Down
Loading