Make it possible to reference Go objects from C. This patch is a variant on sample code written by Nick Wellnhofer.
Project: http://git-wip-us.apache.org/repos/asf/lucy/repo Commit: http://git-wip-us.apache.org/repos/asf/lucy/commit/dab9a88d Tree: http://git-wip-us.apache.org/repos/asf/lucy/tree/dab9a88d Diff: http://git-wip-us.apache.org/repos/asf/lucy/diff/dab9a88d Branch: refs/heads/master Commit: dab9a88d5c654a2539e4a81db4d74f88cc976cf3 Parents: fed6ca7 Author: Marvin Humphrey <[email protected]> Authored: Thu Jul 16 20:32:45 2015 -0700 Committer: Marvin Humphrey <[email protected]> Committed: Fri Jul 31 16:48:03 2015 -0700 ---------------------------------------------------------------------- go/lucy/registry.go | 135 ++++++++++++++++++++++++++++++++++++++++++ go/lucy/registry_test.go | 84 ++++++++++++++++++++++++++ 2 files changed, 219 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/lucy/blob/dab9a88d/go/lucy/registry.go ---------------------------------------------------------------------- diff --git a/go/lucy/registry.go b/go/lucy/registry.go new file mode 100644 index 0000000..8671931 --- /dev/null +++ b/go/lucy/registry.go @@ -0,0 +1,135 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package lucy + +import "sync" + +type indexInt uintptr + +type objRegistry struct { + // Use pointer to array to guarantee atomic update for lock-free reads. + // Assume that loads and stores of the pointer are atomic. + entries *[]interface{} + freeListHead indexInt + mutex sync.Mutex +} + +func newObjRegistry(size uintptr) *objRegistry { + entries := make([]interface{}, size) + + // Each empty entry points to the index of the next empty entry. Index 0 + // is unused. The last slot is seet to a terminating sentry value of 0. + entries[0] = indexInt(0) // unused + for i := uintptr(1); i < size - 1; i++ { + entries[i] = indexInt(i + 1) + } + entries[size-1] = indexInt(0) + + reg := &objRegistry{} + reg.entries = &entries + reg.freeListHead = indexInt(1) + + return reg +} + +func (reg *objRegistry) store(obj interface{}) uintptr { + reg.mutex.Lock() + + // Find the index of the next empty slot. + index := uintptr(reg.freeListHead) + + entries := reg.entries + + if (index != 0) { + // A slot is available. It contains the index of the next available + // slot; put that index into the freeListHead. + reg.freeListHead = (*entries)[index].(indexInt) + } else { + // The sentinel value was encountered, indicating that we are out of + // space and must grow the entries array. + + // The list head was 0, a slot we don't want to use. Figure out what + // slot we're going to use instead. If the current size of the + // entries array is 8, and will soon be 16, use slot 8. + index = uintptr(len(*entries)) + + // Duplicate the array and copy in the existing entries data. + newSize := index * 2 + newEntries := make([]interface{}, newSize) + copy(newEntries, *entries) + + // Set up each new empty slot to point at another new empty slot, up + // to the final slot which will get the sentinel value 0. + for i := index + 1; i < newSize - 1; i++ { + newEntries[i] = indexInt(i + 1) + } + newEntries[newSize - 1] = indexInt(0) + entries = &newEntries + reg.entries = entries + + // Set the freeListHead to one greater than the slot we're using this + // time -- i.e. if the current size is 8, the new size is 16, and the + // slot we use for the supplied value is 8, then the new list head + // will be 9. + reg.freeListHead = indexInt(index + 1) + } + + // Store the supplied value in the slot. + (*entries)[index] = obj + + reg.mutex.Unlock() + + return index +} + +func (reg *objRegistry) fetch(index uintptr) interface{} { + + // Ignore an out of range request. + if index >= uintptr(len(*reg.entries)) { + return nil + } + entry := (*reg.entries)[index] + if _, ok := entry.(indexInt); ok { + // Return nil if the slot is empty. + return nil + } + return entry +} + +func (reg *objRegistry) delete(index uintptr) { + reg.mutex.Lock() + + // Overwrite the value at the supplied index with the freeListHead. For + // example, if you are storing strings and the entries array consists of + // {0, "A", "B", C", 5, 6, 7, 0}, with freeListHead at 4, then deleting + // index 2 (string value "B") will result in the following state: + // {0, "A", 4, "C", 5, 6, 7, 0} and freeListHead at 2. + // + // Some potential errors are ignored: + // * Index is greater than the size of the array. + // * Slot is empty. + if index < uintptr(len(*reg.entries)) { + _, isIndexInt := (*reg.entries)[index].(indexInt) + if !isIndexInt { + (*reg.entries)[index] = reg.freeListHead + reg.freeListHead = indexInt(index) + } + } + + reg.mutex.Unlock() +} + http://git-wip-us.apache.org/repos/asf/lucy/blob/dab9a88d/go/lucy/registry_test.go ---------------------------------------------------------------------- diff --git a/go/lucy/registry_test.go b/go/lucy/registry_test.go new file mode 100644 index 0000000..ea4f9da --- /dev/null +++ b/go/lucy/registry_test.go @@ -0,0 +1,84 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package lucy + +import "testing" +import "math/rand" + +func TestRegistrySingle(t *testing.T) { + reg := newObjRegistry(4) + index := reg.store(42) + if intVal, ok := reg.fetch(index).(int); !ok || intVal != 42 { + t.Error("Failed to store/fetch int") + } + reg.delete(index) + if reg.fetch(index) != nil { + t.Error("Failed to delete int") + } +} + +func TestRegistryMany(t *testing.T) { + reg := newObjRegistry(4) + stored := make(map[int]uintptr) + deleted := make(map[int]uintptr) + for i := 0; i < 1000; i++ { + if rand.Intn(10) == 0 { + // Randomly delete an element 10% of the time. + goner := rand.Intn(i - 1) + if index, ok := stored[goner]; ok { + reg.delete(index) + delete(stored, goner) + deleted[goner] = index + } + } + stored[i] = reg.store(i) + } + for expected, index := range stored { + got, ok := reg.fetch(index).(int) + if !ok { + t.Errorf("Failed to fetch stored value %d at index %d", expected, index) + } else if got != expected { + t.Errorf("Expected %d got %d", expected, got) + } + } + for i := 0; i < len(*reg.entries) - 1; i++ { + got, ok := reg.fetch(uintptr(i)).(int) + if ok { + if _, wasDeleted := deleted[got]; wasDeleted { + t.Errorf("Deleted item %d still present at index %d", got, i) + } + } + } +} + +func TestRegistryStringSlice(t *testing.T) { + reg := newObjRegistry(4) + s := make([]int, 2) + index := reg.store(&s) + s2 := reg.fetch(index).(*[]int) + (*s2)[1] = 1000 + if s[1] != 1000 { + t.Error("Not the same slice") + } +} + +func TestRegistryRange(t *testing.T) { + reg := newObjRegistry(4) + if reg.fetch(uintptr(10)) != nil { + t.Error("Out of range index should return nil") + } +}
