diff --git a/_example/threads.go b/_example/threads.go new file mode 100644 index 0000000..acff181 --- /dev/null +++ b/_example/threads.go @@ -0,0 +1,41 @@ +package main + +import ( + "strconv" + "sync" + + "github.com/aarzilli/golua/lua" +) + +func test(L *lua.State) int { + println("hello from thread: " + strconv.Itoa(L.ToInteger(-1))) + return 0 +} + +var wg sync.WaitGroup + +func main() { + L := lua.NewState() + defer L.Close() + L.OpenLibs() + + L.PushGoFunction(test) + L.SetGlobal("test") + + for i := 0; i < 20; i++ { + wg.Add(1) + go func() { + defer wg.Done() + thread := L.NewThread() + + thread.GetGlobal("test") + thread.PushInteger(int64(i)) + if err := thread.Call(1, 0); err != nil { + println("pain") + println(err) + } + }() + } + + wg.Wait() +} diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/lua/golua.go b/lua/golua.go index 09ad902..adda147 100644 --- a/lua/golua.go +++ b/lua/golua.go @@ -31,6 +31,12 @@ type HookFunction func(L *State) // The errorstring used by State.SetExecutionLimit const ExecutionQuantumExceeded = "Lua execution quantum exceeded" +type goRegistry struct { + mu sync.Mutex + items []interface{} + freeIndices []uint +} + // Wrapper to keep cgo from complaining about incomplete ptr type //export State type State struct { @@ -40,11 +46,7 @@ type State struct { // index of this object inside the goStates array Index uintptr - // Registry of go object that have been pushed to Lua VM - registry []interface{} - - // Freelist for funcs indices, to allow for freeing - freeIndices []uint + registry *goRegistry // User self defined memory alloc func for the lua State allocfn *Alloc @@ -84,10 +86,13 @@ func getGoState(gostateindex uintptr) *State { //export golua_callgofunction func golua_callgofunction(gostateindex uintptr, fid uint) int { L1 := getGoState(gostateindex) + L1.registry.mu.Lock() + defer L1.registry.mu.Unlock() + if fid < 0 { panic(&LuaError{0, "Requested execution of an unknown function", L1.StackTrace()}) } - f := L1.registry[fid].(LuaGoFunction) + f := L1.registry.items[fid].(LuaGoFunction) return f(L1) } @@ -104,11 +109,13 @@ var typeOfBytes = reflect.TypeOf([]byte(nil)) //export golua_interface_newindex_callback func golua_interface_newindex_callback(gostateindex uintptr, iid uint, field_name_cstr *C.char) int { L := getGoState(gostateindex) - iface := L.registry[iid] - ifacevalue := reflect.ValueOf(iface).Elem() - field_name := C.GoString(field_name_cstr) + L.registry.mu.Lock() + iface := L.registry.items[iid] + L.registry.mu.Unlock() + ifacevalue := reflect.ValueOf(iface).Elem() + field_name := C.GoString(field_name_cstr) fval := ifacevalue.FieldByName(field_name) if fval.Kind() == reflect.Ptr { @@ -199,9 +206,12 @@ func golua_interface_newindex_callback(gostateindex uintptr, iid uint, field_nam //export golua_interface_index_callback func golua_interface_index_callback(gostateindex uintptr, iid uint, field_name *C.char) int { L := getGoState(gostateindex) - iface := L.registry[iid] - ifacevalue := reflect.ValueOf(iface).Elem() + + L.registry.mu.Lock() + iface := L.registry.items[iid] + L.registry.mu.Unlock() + ifacevalue := reflect.ValueOf(iface).Elem() fval := ifacevalue.FieldByName(C.GoString(field_name)) if fval.Kind() == reflect.Ptr { @@ -267,7 +277,9 @@ func golua_gchook(gostateindex uintptr, id uint) int { //export golua_callpanicfunction func golua_callpanicfunction(gostateindex uintptr, id uint) int { L1 := getGoState(gostateindex) - f := L1.registry[id].(LuaGoFunction) + L1.registry.mu.Lock() + f := L1.registry.items[id].(LuaGoFunction) + L1.registry.mu.Unlock() return f(L1) } diff --git a/lua/lua.go b/lua/lua.go index 15d365b..4b8dc85 100644 --- a/lua/lua.go +++ b/lua/lua.go @@ -48,6 +48,7 @@ package lua import "C" import ( "fmt" + "sync" "unsafe" ) @@ -59,7 +60,8 @@ type LuaStackEntry struct { } func newState(L *C.lua_State) *State { - newstate := &State{L, 0, make([]interface{}, 0, 8), make([]uint, 0, 8), nil, nil, nil} + registry := &goRegistry{sync.Mutex{}, make([]interface{}, 0, 8), make([]uint, 0, 8)} + newstate := &State{L, 0, registry, nil, nil, nil} registerGoState(newstate) C.clua_setgostate(L, C.size_t(newstate.Index)) C.clua_initstate(L) @@ -67,25 +69,25 @@ func newState(L *C.lua_State) *State { } func (L *State) addFreeIndex(i uint) { - freelen := len(L.freeIndices) + freelen := len(L.registry.freeIndices) //reallocate if necessary - if freelen+1 > cap(L.freeIndices) { - newSlice := make([]uint, freelen, cap(L.freeIndices)*2) - copy(newSlice, L.freeIndices) - L.freeIndices = newSlice + if freelen+1 > cap(L.registry.freeIndices) { + newSlice := make([]uint, freelen, cap(L.registry.freeIndices)*2) + copy(newSlice, L.registry.freeIndices) + L.registry.freeIndices = newSlice } //reslice - L.freeIndices = L.freeIndices[0 : freelen+1] - L.freeIndices[freelen] = i + L.registry.freeIndices = L.registry.freeIndices[0 : freelen+1] + L.registry.freeIndices[freelen] = i } func (L *State) getFreeIndex() (index uint, ok bool) { - freelen := len(L.freeIndices) + freelen := len(L.registry.freeIndices) //if there exist entries in the freelist if freelen > 0 { - i := L.freeIndices[freelen-1] //get index + i := L.registry.freeIndices[freelen-1] //get index //fmt.Printf("Free indices before: %v\n", L.freeIndices) - L.freeIndices = L.freeIndices[0 : freelen-1] //'pop' index from list + L.registry.freeIndices = L.registry.freeIndices[0 : freelen-1] //'pop' index from list //fmt.Printf("Free indices after: %v\n", L.freeIndices) return i, true } @@ -94,34 +96,40 @@ func (L *State) getFreeIndex() (index uint, ok bool) { //returns the registered function id func (L *State) register(f interface{}) uint { + L.registry.mu.Lock() + defer L.registry.mu.Unlock() + //fmt.Printf("Registering %v\n") index, ok := L.getFreeIndex() //fmt.Printf("\tfreeindex: index = %v, ok = %v\n", index, ok) //if not ok, then we need to add new index by extending the slice if !ok { - index = uint(len(L.registry)) + index = uint(len(L.registry.items)) //reallocate backing array if necessary - if index+1 > uint(cap(L.registry)) { - newcap := cap(L.registry) * 2 + if index+1 > uint(cap(L.registry.items)) { + newcap := cap(L.registry.items) * 2 if index+1 > uint(newcap) { newcap = int(index + 1) } newSlice := make([]interface{}, index, newcap) - copy(newSlice, L.registry) - L.registry = newSlice + copy(newSlice, L.registry.items) + L.registry.items = newSlice } //reslice - L.registry = L.registry[0 : index+1] + L.registry.items = L.registry.items[0 : index+1] } //fmt.Printf("\tregistering %d %v\n", index, f) - L.registry[index] = f + L.registry.items[index] = f return index } func (L *State) unregister(fid uint) { + L.registry.mu.Lock() + defer L.registry.mu.Unlock() + //fmt.Printf("Unregistering %d (len: %d, value: %v)\n", fid, len(L.registry), L.registry[fid]) - if (fid < uint(len(L.registry))) && (L.registry[fid] != nil) { - L.registry[fid] = nil + if fid < uint(len(L.registry.items)) && L.registry.items[fid] != nil { + L.registry.items[fid] = nil L.addFreeIndex(fid) } } @@ -192,7 +200,9 @@ func (L *State) AtPanic(panicf LuaGoFunction) (oldpanicf LuaGoFunction) { oldres := interface{}(C.clua_atpanic(L.s, C.uint(fid))) switch i := oldres.(type) { case C.uint: - f := L.registry[uint(i)].(LuaGoFunction) + L.registry.mu.Lock() + f := L.registry.items[uint(i)].(LuaGoFunction) + L.registry.mu.Unlock() //free registry entry L.unregister(uint(i)) return f @@ -350,11 +360,18 @@ func (L *State) NewTable() { // lua_newthread func (L *State) NewThread() *State { + L.registry.mu.Lock() + defer L.registry.mu.Unlock() + //TODO: call newState with result from C.lua_newthread and return it //TODO: should have same lists as parent // but may complicate gc - s := C.lua_newthread(L.s) - return &State{s, 0, nil, nil, nil, nil, nil} + L2 := newState(C.lua_newthread(L.s)) + L2.registry = L.registry + L2.allocfn = L.allocfn + L2.hookFn = L.hookFn + L2.ctx = L.ctx + return L2 } // lua_next @@ -508,7 +525,11 @@ func (L *State) ToGoFunction(index int) (f LuaGoFunction) { if fid < 0 { return nil } - return L.registry[fid].(LuaGoFunction) + + L.registry.mu.Lock() + function := L.registry.items[fid].(LuaGoFunction) + L.registry.mu.Unlock() + return function } // Returns the value at index as a Go Struct (it must be something pushed with PushGoStruct) @@ -520,7 +541,11 @@ func (L *State) ToGoStruct(index int) (f interface{}) { if fid < 0 { return nil } - return L.registry[fid] + + L.registry.mu.Lock() + idx := L.registry.items[fid] + L.registry.mu.Unlock() + return idx } // lua_tostring