Introduce an indirection layer for pointers
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.
diff --git a/git.go b/git.go
index 9496d2d..4f1a65e 100644
--- a/git.go
+++ b/git.go
@@ -93,7 +93,11 @@
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
diff --git a/handles.go b/handles.go
new file mode 100644
index 0000000..c0d1889
--- /dev/null
+++ b/handles.go
@@ -0,0 +1,82 @@
+package git
+
+import (
+ "sync"
+ "unsafe"
+)
+
+type HandleList struct {
+ sync.RWMutex
+ // stores the Go pointers
+ handles []interface{}
+ // indicates which indices are in use
+ set map[uintptr]bool
+}
+
+func NewHandleList() *HandleList {
+ return &HandleList{
+ handles: make([]interface{}, 5),
+ }
+}
+
+// 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() uintptr {
+ for i := 0; i < len(v.handles); i++ {
+ isUsed := v.set[uintptr(i)]
+ if !isUsed {
+ return uintptr(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 uintptr(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 := uintptr(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 := uintptr(handle)
+
+ v.RLock()
+
+ if _, ok := v.set[slot]; !ok {
+ panic("invalid pointer handle")
+ }
+
+ ptr := v.handles[slot]
+
+ v.RUnlock()
+
+ return ptr
+}