diff options
| author | unwox <me@unwox.com> | 2024-07-23 23:19:46 +0600 |
|---|---|---|
| committer | unwox <me@unwox.com> | 2024-07-23 23:24:14 +0600 |
| commit | cba8ff3896a59598e9f0aa946fd547002ac23aba (patch) | |
| tree | 33fa5edd8f9640bb00e28f75e6bb73910e80c529 | |
| parent | 701d73e4a47964ea40100ab2f329ef1dcc0a6f83 (diff) | |
use luna module instead of returning table for lua->go interaction
| -rw-r--r-- | init.fnl | 2 | ||||
| -rw-r--r-- | lua.go | 57 | ||||
| -rw-r--r-- | main.go | 110 | ||||
| -rw-r--r-- | worker.go | 183 |
4 files changed, 193 insertions, 159 deletions
@@ -14,4 +14,4 @@ (let [headers { :content-type "text/html" }] (values 200 headers (html.render template)))) -{ :routes { :/ root-handler } } +(luna.router.route "/" root-handler) @@ -21,7 +21,7 @@ static inline void luna_push_function(lua_State *l, uintptr_t f) { import "C" import ( "errors" - "log" + "fmt" "runtime/cgo" "unsafe" ) @@ -33,7 +33,6 @@ type Lua struct { //export luna_run_go_func func luna_run_go_func(f C.uintptr_t) C.int { - log.Println(f) fn := cgo.Handle(f).Value().(func() int) return C.int(fn()) } @@ -45,36 +44,36 @@ func (l *Lua) Start() { func (l *Lua) Close() { C.lua_close(l.l) - C.free(unsafe.Pointer(l.l)) } -func (l *Lua) Require(file string) (LuaRef, error) { +func (l *Lua) Require(file string) error { if !l.LoadFile(file) { errMsg := l.ToString(-1) l.Pop(1) - return 0, errors.New("could not open the file:\n" + errMsg) + return errors.New("could not open the file:\n" + errMsg) } - if !l.PCall(0, C.LUA_MULTRET) { - errMsg := l.ToString(-1) - l.Pop(1) - return 0, errors.New("could not execute the file:\n" + errMsg) - } - if !l.IsTable(-1) { - return 0, errors.New("module did not return a table") + err := l.PCall(0, C.LUA_MULTRET) + if err != nil { + return errors.New("could not execute the file:\n" + err.Error()) } - return l.PopToRef(), nil + return nil } -func (l *Lua) PCall(nargs int, nresults int) bool { +func (l *Lua) PCall(nargs int, nresults int) error { res := C.lua_pcallk(l.l, C.int(nargs), C.int(nresults), 0, 0, nil) - return res == C.LUA_OK + if res != C.LUA_OK { + errMsg := l.ToString(-1) + l.Pop(1) + return errors.New(errMsg) + } + return nil } func (l *Lua) LoadFile(file string) bool { - cfile := C.CString(file) - defer C.free(unsafe.Pointer(cfile)) - res := C.luaL_loadfilex(l.l, cfile, nil) + cstr := C.CString(file) + defer C.free(unsafe.Pointer(cstr)) + res := C.luaL_loadfilex(l.l, cstr, nil) return res == C.LUA_OK } @@ -102,6 +101,10 @@ func (l *Lua) PushNil() { C.lua_pushnil(l.l) } +func (l *Lua) PushNumber(num int) { + C.lua_pushnumber(l.l, C.double(num)) +} + func (l *Lua) PushString(str string) { cstr := C.CString(str) defer C.free(unsafe.Pointer(cstr)) @@ -121,11 +124,13 @@ func (l *Lua) PushGoFunction(f func (l *Lua) int) cgo.Handle { func (l *Lua) CreateTable(len int) { C.lua_createtable(l.l, 0, C.int(len)) } + func (l *Lua) SetTableItem(key string) { cstr := C.CString(key) defer C.free(unsafe.Pointer(cstr)) C.lua_setfield(l.l, -2, C.CString(key)) } + func (l *Lua) PushStringTable(table map[string]string) { l.CreateTable(len(table)) for k, v := range table { @@ -173,10 +178,9 @@ func (l *Lua) LoadString(code string) error { l.Pop(1) return errors.New(errMsg) } - if !l.PCall(0, 0) { - errMsg := l.ToString(-1) - l.Pop(1) - return errors.New(errMsg) + err := l.PCall(0, 0) + if err != nil { + return err } l.Pop(l.StackLen()) return nil @@ -190,11 +194,12 @@ func (l *Lua) RestoreStackFunc() func () { if diff == 0 { return } else if diff < 0 { - log.Fatalf( + msg := fmt.Sprintf( "too many stack pops: len before: %d, after: %d\n", before, after, ) + panic(msg) } C.lua_settop(l.l, C.int(before)) } @@ -205,3 +210,9 @@ func (l *Lua) SetGlobal(name string) { defer C.free(unsafe.Pointer(cstr)) C.lua_setglobal(l.l, cstr) } + +func (l *Lua) GetGlobal(name string) { + cstr := C.CString(name) + defer C.free(unsafe.Pointer(cstr)) + C.lua_getglobal(l.l, cstr) +} @@ -2,7 +2,6 @@ package main import ( "bufio" - "flag" "fmt" "log" "net/http" @@ -11,64 +10,89 @@ import ( ) func main() { - debug := flag.Bool("debug", false, "Debug mode!") - flag.Parse() - - readCh := make(chan interface{}, 4096) + msgs := make(chan interface{}, 4096) mux := http.NewServeMux() wrks := []*Worker{} - for i := 0; i < 8; i++ { - log.Printf("worker %d started\n", i) - wrk := NewWorker(readCh) - err := wrk.Start("init.lua") - if err != nil { - log.Fatal(err) - } - if i == 0 { - for _, route := range wrk.ListRoutes() { - // we need to copy route otherwise it's going - // to change with every next iteration - route := route - mux.HandleFunc( - route, - func (w http.ResponseWriter, r *http.Request) { - start := time.Now() - resCh := wrk.Request(route, r, *debug) - res := <- resCh - for k, h := range res.Headers { - w.Header().Set(k, h) - } - w.WriteHeader(int(res.Code)) - fmt.Fprint(w, string(res.Body)) - log.Printf( - "%s %s (%s) -> %d\n", - r.Method, - r.URL.Path, - time.Now().Sub(start), - res.Code, - ) - }, - ) + routes := make(map[string]bool) + + // define luna.router module + routeModule := make(map[string]any) + routeModule["route"] = func (l *Lua) int { + fn := l.PopToRef() + route := l.ToString(-1) + // find corresponding worker for the lua context + var wrk *Worker + for _, wrk = range wrks { + if wrk.HasSameLua(l) { + wrk.SetRoute(route, fn) + break } } + if wrk == nil { + return 0 + } + // check if route is already registered. otherwise + // mux.HandleFunc will panic + _, ok := routes[route] + if ok { + return 0 + } + routes[route] = true + mux.HandleFunc( + route, + func (w http.ResponseWriter, r *http.Request) { + start := time.Now() + resCh := HandleHTTPRequest(msgs, route, r) + res := <- resCh + for k, h := range res.Headers { + w.Header().Set(k, h) + } + w.WriteHeader(int(res.Code)) + fmt.Fprint(w, string(res.Body)) + log.Printf( + "%s %s (%s) -> %d\n", + r.Method, + r.URL.Path, + time.Now().Sub(start), + res.Code, + ) + }, + ) + return 0 + } + module := make(map[string]any) + module["router"] = routeModule + + // start workers + for i := 0; i < 8; i++ { + wrk := NewWorker() wrks = append(wrks, wrk) - go wrk.Listen() + go func () { + err := wrk.Start("init.lua", module) + if err != nil { + log.Fatal(err) + } + wrks = append(wrks, wrk) + wrk.Listen(msgs) + }() defer wrk.Stop() + log.Printf("worker %d started\n", i) } + // listen for evals from stdio go func () { reader := bufio.NewReader(os.Stdin) for { - text, _ := reader.ReadString('\n') - for _, w := range wrks { - err := <- w.Eval(text) - if err != nil { + code, _ := reader.ReadString('\n') + for _, wrk := range wrks { + if err := wrk.Run(code); err != nil { log.Printf("error: %s\n", err) break } } } }() - fmt.Println("luna is running...") + + fmt.Println("luna is running at 127.0.0.1:3002...") log.Fatal(http.ListenAndServe("127.0.0.1:3002", mux)) } @@ -5,6 +5,7 @@ import ( "io" "log" "net/http" + "sync" ) type HTTPRequest struct { @@ -14,11 +15,6 @@ type HTTPRequest struct { result chan *HTTPResponse } -type EvalRequest struct { - code string - result chan error -} - type HTTPResponse struct { Code int Headers map[string]string @@ -26,58 +22,70 @@ type HTTPResponse struct { } type Worker struct { - read chan interface{} lua *Lua - api LuaRef routes map[string]LuaRef started bool + mu sync.Mutex +} + +func HandleHTTPRequest( + queue chan any, + route string, + req *http.Request, +) chan *HTTPResponse { + res := make(chan *HTTPResponse) + queue <- &HTTPRequest{ + request: req, + route: route, + result: res, + } + return res } -func NewWorker(read chan interface{}) *Worker { +func NewWorker() *Worker { return &Worker { - read: read, routes: make(map[string]LuaRef), lua: &Lua{}, } } -func (w *Worker) Start(filename string) error { +func (w *Worker) Start(filename string, module map[string]any) error { + w.mu.Lock() + defer w.mu.Unlock() + if w.started { return errors.New("already started") } - w.lua.Start() - api, err := w.lua.Require(filename) - if err != nil { - return err - } - w.api = api - err = w.initRoutes() + defer w.lua.RestoreStackFunc()() + w.initLunaModule(module) + err := w.lua.Require(filename) if err != nil { return err } - + w.started = true return nil } -func (w *Worker) Listen() { - for { - resStack := w.lua.RestoreStackFunc() - r := <- w.read +func (w *Worker) Listen(queue chan any) { + handle := func() { + defer w.lua.RestoreStackFunc()() + r := <- queue switch r.(type) { case *HTTPRequest: r := r.(*HTTPRequest) - // If in debug mode always use handlers from api table - // instead of cached references. Makes it much easier - // to hot-replace route handlers. - if r.debug { - w.lua.PushFromRef(w.api) - w.lua.PushTableItem("routes") - w.lua.PushTableItem(r.route) - } else { - w.lua.PushFromRef(w.routes[r.route]) + if _, ok := w.routes[r.route]; !ok { + r.result <- &HTTPResponse { + Code: 404, + Headers: make(map[string]string), + Body: "not found", + } + log.Println("no corresponding route") + return } + + w.lua.PushFromRef(w.routes[r.route]) w.lua.PushString(r.request.Method) w.lua.PushString(r.request.URL.Path) @@ -95,12 +103,22 @@ func (w *Worker) Listen() { Body: "server error", } log.Println("could not read a request body") - resStack() - continue + return } w.lua.PushString(string(body)) - w.lua.PCall(4, 3) + err = w.lua.PCall(4, 3) + + if err != nil { + r.result <- &HTTPResponse { + Code: 500, + Headers: make(map[string]string), + Body: "server error", + } + log.Println("could not read a request body") + return + } + code := w.lua.ToInt(-3) rbody := w.lua.ToString(-1) @@ -127,81 +145,62 @@ func (w *Worker) Listen() { Body: rbody, } - case *EvalRequest: - r := r.(*EvalRequest) - err := w.lua.LoadString(r.code) - r.result <- err - default: log.Fatal("unknown request") } - - resStack() } -} -func (w *Worker) Request( - route string, - r *http.Request, - debug bool, -) chan *HTTPResponse { - res := make(chan *HTTPResponse) - w.read <- &HTTPRequest{ - request: r, - route: route, - debug: debug, - result: res, + for { + handle() } - return res } -func (w *Worker) Eval(code string) chan error { - res := make(chan error) - w.read <- &EvalRequest{code: code, result: res} - return res +func (w *Worker) Run(code string) error { + w.mu.Lock() + defer w.mu.Unlock() + defer w.lua.RestoreStackFunc()() + return w.lua.LoadString(code) } -func (w *Worker) ListRoutes() []string { - res := []string{} - for route, _ := range w.routes { - res = append(res, route) - } - return res +func (w *Worker) SetRoute(route string, handler LuaRef) { + w.routes[route] = handler } func (w *Worker) Stop() { + w.mu.Lock() + defer w.mu.Unlock() w.lua.Close() } -func (w *Worker) initLunaModule() { - w.lua.CreateTable(1) - w.lua.PushGoFunction(func (l *Lua) int { - l.PushString("Hello, " + l.ToString(-1)) - return 1 - }) - w.lua.SetTableItem("helloFromGo") +func (w *Worker) initLunaModule(module map[string]any) { + var pushTable func(t map[string]any) + pushTable = func (t map[string]any) { + w.lua.CreateTable(len(t)) + for k, v := range t { + switch v.(type) { + case string: + v, _ := v.(string) + w.lua.PushString(v) + case func (l *Lua) int: + v, _ := v.(func (l *Lua) int) + w.lua.PushGoFunction(v) + case int: + v, _ := v.(int) + w.lua.PushNumber(v) + case map[string]any: + v, _ := v.(map[string]any) + pushTable(v) + default: + // FIXME: more details + log.Fatal("unsupported module value type") + } + w.lua.SetTableItem(k) + } + } + pushTable(module) w.lua.SetGlobal("luna") } -func (w *Worker) initRoutes() error { - defer w.lua.RestoreStackFunc()() - w.lua.PushFromRef(w.api) - w.lua.PushTableItem("routes") - if !w.lua.IsTable(-1) { - return errors.New("\"routes\" must be a table") - } - w.lua.PushNil() - for w.lua.Next() { - if !w.lua.IsString(-2) || !w.lua.IsFunction(-1) { - w.lua.Pop(1) - continue - } - handlerRef := w.lua.PopToRef() - // we must not pop the item key from the stack because - // otherwise C.lua_next won't work properly - route := w.lua.ToString(-1) - w.routes[route] = handlerRef - } - w.lua.Pop(2) - return nil +func (w *Worker) HasSameLua(l *Lua) bool { + return w.lua == l } |
