Temporary lock the resource until goroutine is finished - rest

I have a method, which sends HTTP status-code 202 Accepted as indicator of successful request, and runs another process in it (goroutine). Something like that:
return func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusAccepted)
go func() {
time.Sleep(2 * time.Second)
}()
}
I want to temporarily lock the resource to prevent multiple execution of goroutine process.
return func(w http.ResponseWriter, r *http.Request) {
c := make(chan bool)
select {
case _, unlocked := <-c:
if unlocked {
break
}
default:
w.WriteHeader(http.StatusLocked)
return
}
w.WriteHeader(http.StatusAccepted)
go func(c chan bool) {
time.Sleep(2 * time.Second)
c <- true
}(c)
}
I'm get 423 Locked status code always. I think, I don't understand channel yet. May be try to use mutexes?

Not the best solution, but it works:
func (h *HookHandler) NewEvents() http.HandlerFunc {
eventsLocker := struct {
v bool
mux *sync.Mutex
}{
v: true,
mux: &sync.Mutex{},
}
return func(w http.ResponseWriter, r *http.Request) {
if !eventsLocker.v {
w.WriteHeader(http.StatusLocked)
return
}
w.WriteHeader(http.StatusAccepted)
go func() {
defer eventsLocker.mux.Unlock()
defer func() { eventsLocker.v = true }()
eventsLocker.mux.Lock()
eventsLocker.v = false
time.Sleep(2 * time.Second)
}()
}
}

Related

How to log only errors like 404 with chi in go?

I'm using chi with our Go webservices.
How to configure the logger (middleware) so it only logs requests that ended up with errors (like 404) but it doesn't log successful requests (with status code 200)?
Here's our current implementation (with no logging at all)
r := chi.NewRouter()
if DEBUG_LOGS {
r.Use(middleware.Logger)
} else {
}
The easiest way is to implement the logging function by yourself using the example from the chi package (for simplicity, I removed the colors).
package main
import (
"bytes"
"fmt"
"log"
"net/http"
"os"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
const DEBUG_LOGS = true
func main() {
api := &http.Server{Addr: ":8000"}
r := chi.NewRouter()
if DEBUG_LOGS {
// create default logger/zerolog/logrus
logger := log.New(os.Stdout, "", log.LstdFlags)
r.Use(middleware.RequestLogger(&StructuredLogger{logger}))
}
r.Get("/tea", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) })
r.Get("/ok", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
api.Handler = r
err := api.ListenAndServe()
if err != nil {
log.Fatal(err)
}
}
// below is the implementation of the custom logger.
type StructuredLogger struct {
Logger middleware.LoggerInterface
}
type LogEntry struct {
*StructuredLogger
request *http.Request
buf *bytes.Buffer
useColor bool
}
func (l *StructuredLogger) NewLogEntry(r *http.Request) middleware.LogEntry {
entry := &LogEntry{
StructuredLogger: l,
request: r,
buf: &bytes.Buffer{},
useColor: false,
}
reqID := middleware.GetReqID(r.Context())
if reqID != "" {
fmt.Fprintf(entry.buf, "[%s] ", reqID)
}
fmt.Fprintf(entry.buf, "\"")
fmt.Fprintf(entry.buf, "%s ", r.Method)
scheme := "http"
if r.TLS != nil {
scheme = "https"
}
fmt.Fprintf(entry.buf, "%s://%s%s %s\" ", scheme, r.Host, r.RequestURI, r.Proto)
entry.buf.WriteString("from ")
entry.buf.WriteString(r.RemoteAddr)
entry.buf.WriteString(" - ")
return entry
}
func (l *LogEntry) Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{}) {
// Do nothing if status code is 200/201/eg
if status < 300 {
return
}
fmt.Fprintf(l.buf, "%03d", status)
fmt.Fprintf(l.buf, " %dB", bytes)
l.buf.WriteString(" in ")
if elapsed < 500*time.Millisecond {
fmt.Fprintf(l.buf, "%s", elapsed)
} else if elapsed < 5*time.Second {
fmt.Fprintf(l.buf, "%s", elapsed)
} else {
fmt.Fprintf(l.buf, "%s", elapsed)
}
l.Logger.Print(l.buf.String())
}
func (l *LogEntry) Panic(v interface{}, stack []byte) {
middleware.PrintPrettyStack(v)
}

How to run functions concurrently in a Go API instead of sequential? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
In an API, if we need to query multiple tables how can we achieve it concurrently instead of following the sequential way,
i.e
func sampleAPI(w http.ResponseWriter, r *http.Request) {
a, err := getFromATable(); //1
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
b, err := getFromBTable(); //2
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
c, err := getFromCTable(); //3
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
.
.
.
}
I want to call the above functions 1,2,3 concurrently. How can I achieve that
Using an error channel to synchronize
func sampleAPI(w http.ResponseWriter, r *http.Request) {
chErr := make(chan error)
var a correctType
go func() {
var err error
a, err = getFromATable()
chErr <- err
}()
var b correctType
go func() {
var err error
b, err = getFromBTable()
chErr <- err
}()
var c correctType
go func() {
var err error
c, err = getFromCTable()
chErr <- err
}()
var err error
for i := 0; i < 3; i++ {
if r := <-chErr; r != nil {
err = r
}
}
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
// etc.
return
}
// continue to do stuff with a, b, c
}
Some Notes:
Every go function must add a value to chErr. Make sure of it! If there is no error, write nil to chErr (which this example does if there is no error).
The for loop must iterate the same amount as there were go functions started.
The for loop makes sure all functions have completed (with or without error) before continuing.
Using the error to synchronise is convenient as it is the same type for all functions. The return type might be different. If we need to cancel on error, we need to get the error back out of the goroutines anyway.
Using an errgroup
As suggested by #Зелёный in the comments, here an example using the (still) experimental package errgroup:
func sampleAPI(w http.ResponseWriter, r *http.Request) {
g, ctx := errgroup.WithContext(context.TODO())
var a correctType
g.Go(func() (err error) {
a, err = getFromATable(ctx)
return err
})
var b correctType
g.Go(func() (err error) {
b, err = getFromBTable(ctx)
return err
})
var c correctType
g.Go(func() (err error) {
c, err = getFromCTable(ctx)
return err
})
if err := g.Wait(); err != nil {
w.WriteHeader(http.StatusInternalServerError)
// etc.
return
}
// continue to do stuff with a, b, c
}
Some Notes:
This one checks all errors and returns the first one for you.
It also cancels the remaining calls if one errors out (hence the ctx)
It uses a sync.WaitGroup
Downside: it is an extra dependency as it is not part of the standard library (yet).
Using a WaitGroup
You can also use a sync.WaitGroup to wait until all functions have returned their results.
func sampleAPI(w http.ResponseWriter, r *http.Request) {
var wg sync.WaitGroup
wg.Add(3)
var a correctType
var errA error
go func() {
defer wg.Done()
a, errA = getFromATable()
}()
var b correctType
var errB error
go func() {
defer wg.Done()
b, errB = getFromBTable()
}()
var c correctType
var errC error
go func() {
defer wg.Done()
c, errC = getFromCTable()
}()
wg.Wait()
if errA != nil {
w.WriteHeader(http.StatusInternalServerError)
// etc.
return
}
if errB != nil {
w.WriteHeader(http.StatusInternalServerError)
// etc.
return
}
if errC != nil {
w.WriteHeader(http.StatusInternalServerError)
// etc.
return
}
// continue to do stuff with a, b, c
}
Some notes:
You need 3 error variables here.
You need to check all 3 error variables after wg.Wait, making it a bit verbose.
You could also use the wait group in order to wait all the api call are finished before proceed like:
func sampleAPI(w http.ResponseWriter, r *http.Request) {
var res1, res2, res3 myCustomType
var err1, err2, err2 error
var wg sync.WaitGroup
wg.Add(3)
go func() {
defer wg.Done()
res1, err1 = getFromATable(); //1
}()
go func() {
defer wg.Done()
res2, err2 = getFromBTable(); //2
}()
go func() {
defer wg.Done()
res3, err3 = getFromXTable(); //3
}()
wg.Wait()
}
Further reference also https://gobyexample.com/waitgroups and https://tutorialedge.net/golang/go-waitgroup-tutorial/
if a, b and c are different type, you can try the following approach. interface{} type is just to show how to code , you can modify channel type based on your requirement.
aCh := make(chan interface{})
bCh := make(chan interface{})
cCh := make(chan interface{})
defer close(aCh)
defer close(bCh)
defer close(cCh)
go func() {
a, _ := GetFromATable()
aCh <- a
}()
go func() {
b, _ := GetFromBTable()
bCh <- b
}()
go func() {
c, _ := GetFromCTable()
cCh <- c
}()
fmt.Println(<- aCh, <- bCh, <- cCh)
if all three output are in same type, you can only need to use single channel or use forever for loop with select statement to handle streaming requirement.

How to pass a parameter to a MiddlewareFunc?

I'm working on a small demo that tries to explain how a basic HTTP handler works, and I found the following example:
package main
func router() *mux.Router {
router := mux.NewRouter()
auth := router.PathPrefix("/auth").Subrouter()
auth.Use(auth.ValidateToken)
auth.HandleFunc("/api", middleware.ApiHandler).Methods("GET")
return router
}
func main() {
r := router()
http.ListenAndServe(":8080", r)
}
package auth
func ValidateToken(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var header = r.Header.Get("secret-access-token")
json.NewEncoder(w).Encode(r)
header = strings.TrimSpace(header)
if header == "" {
w.WriteHeader(http.StatusForbidden)
json.NewEncoder(w).Encode("Missing auth token")
return
}
if header != "SecretValue" {
w.WriteHeader(http.StatusForbidden)
json.NewEncoder(w).Encode("Auth token is invalid")
return
}
json.NewEncoder(w).Encode(fmt.Sprintf("Token found. Value %s", header))
next.ServeHTTP(w, r)
})
}
package middleware
func ApiHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode("SUCCESS!")
return
}
Everything is understandable, but it pop-out to my mind two questions:
Why it doesn't require to send a parameter in this line: secure.Use(auth.ValidateToken)?
If I wanted to send an extra parameter to this auth.ValidateToken func, (example, a string, for which this header should be compared instead of "SecretValue"), how can this be done?
Thanks in advance, I'm kind of new at using golang and wanted to learn more.
auth.Use takes a function as argument, and auth.ValidateToken is the function you pass. If you want to send an argument to auth.ValidateToken, you can write a function that takes that argument, and returns a middleware function, like this:
func GetValidateTokenFunc(headerName string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var header = r.Header.Get(headerName)
...
}
}
}
Then you can do:
auth.Use(auth.GetValidateTokenFunc("headerName"))

Accessing the underlying socket of a net/http response

I'm new to Go and evaluating it for a project.
I'm trying to write a custom handler to serve files with net/http.
I can't use the default http.FileServer() handler because I need to have access to the underlying socket (the internal net.Conn) so I can perform some informational platform specific "syscall" calls on it (mainly TCP_INFO).
More precisly: I need to access the underlying socket of the http.ResponseWriter in the handler function:
func myHandler(w http.ResponseWriter, r *http.Request) {
...
// I need the net.Conn of w
...
}
used in
http.HandleFunc("/", myHandler)
Is there a way to this. I looked at how websocket.Upgrade does this but it uses Hijack() which is 'too much' because then I have to code 'speaking http' over the raw tcp socket I get. I just want a reference to the socket and not taking over completely.
After Issue #30694 is completed, it looks like Go 1.13 will probably support storing the net.Conn in the Request Context, which makes this fairly clean and simple:
package main
import (
"net/http"
"context"
"net"
"log"
)
type contextKey struct {
key string
}
var ConnContextKey = &contextKey{"http-conn"}
func SaveConnInContext(ctx context.Context, c net.Conn) (context.Context) {
return context.WithValue(ctx, ConnContextKey, c)
}
func GetConn(r *http.Request) (net.Conn) {
return r.Context().Value(ConnContextKey).(net.Conn)
}
func main() {
http.HandleFunc("/", myHandler)
server := http.Server{
Addr: ":8080",
ConnContext: SaveConnInContext,
}
server.ListenAndServe()
}
func myHandler(w http.ResponseWriter, r *http.Request) {
conn := GetConn(r)
...
}
Until then ... For a server listening on a TCP port, net.Conn.RemoteAddr().String() is unique for each connection and is available to the http.Handler as r.RemoteAddr, so it can be used as a key to a global map of Conns:
package main
import (
"net/http"
"net"
"fmt"
"log"
)
var conns = make(map[string]net.Conn)
func ConnStateEvent(conn net.Conn, event http.ConnState) {
if event == http.StateActive {
conns[conn.RemoteAddr().String()] = conn
} else if event == http.StateHijacked || event == http.StateClosed {
delete(conns, conn.RemoteAddr().String())
}
}
func GetConn(r *http.Request) (net.Conn) {
return conns[r.RemoteAddr]
}
func main() {
http.HandleFunc("/", myHandler)
server := http.Server{
Addr: ":8080",
ConnState: ConnStateEvent,
}
server.ListenAndServe()
}
func myHandler(w http.ResponseWriter, r *http.Request) {
conn := GetConn(r)
...
}
For a server listening on a UNIX socket, net.Conn.RemoteAddr().String() is always "#", so the above doesn't work. To make this work, we can override net.Listener.Accept(), and use that to override net.Conn.RemoteAddr().String() so that it returns a unique string for each connection:
package main
import (
"net/http"
"net"
"os"
"golang.org/x/sys/unix"
"fmt"
"log"
)
func main() {
http.HandleFunc("/", myHandler)
listenPath := "/var/run/go_server.sock"
l, err := NewUnixListener(listenPath)
if err != nil {
log.Fatal(err)
}
defer os.Remove(listenPath)
server := http.Server{
ConnState: ConnStateEvent,
}
server.Serve(NewConnSaveListener(l))
}
func myHandler(w http.ResponseWriter, r *http.Request) {
conn := GetConn(r)
if unixConn, isUnix := conn.(*net.UnixConn); isUnix {
f, _ := unixConn.File()
pcred, _ := unix.GetsockoptUcred(int(f.Fd()), unix.SOL_SOCKET, unix.SO_PEERCRED)
f.Close()
log.Printf("Remote UID: %d", pcred.Uid)
}
}
var conns = make(map[string]net.Conn)
type connSaveListener struct {
net.Listener
}
func NewConnSaveListener(wrap net.Listener) (net.Listener) {
return connSaveListener{wrap}
}
func (self connSaveListener) Accept() (net.Conn, error) {
conn, err := self.Listener.Accept()
ptrStr := fmt.Sprintf("%d", &conn)
conns[ptrStr] = conn
return remoteAddrPtrConn{conn, ptrStr}, err
}
func GetConn(r *http.Request) (net.Conn) {
return conns[r.RemoteAddr]
}
func ConnStateEvent(conn net.Conn, event http.ConnState) {
if event == http.StateHijacked || event == http.StateClosed {
delete(conns, conn.RemoteAddr().String())
}
}
type remoteAddrPtrConn struct {
net.Conn
ptrStr string
}
func (self remoteAddrPtrConn) RemoteAddr() (net.Addr) {
return remoteAddrPtr{self.ptrStr}
}
type remoteAddrPtr struct {
ptrStr string
}
func (remoteAddrPtr) Network() (string) {
return ""
}
func (self remoteAddrPtr) String() (string) {
return self.ptrStr
}
func NewUnixListener(path string) (net.Listener, error) {
if err := unix.Unlink(path); err != nil && !os.IsNotExist(err) {
return nil, err
}
mask := unix.Umask(0777)
defer unix.Umask(mask)
l, err := net.Listen("unix", path)
if err != nil {
return nil, err
}
if err := os.Chmod(path, 0660); err != nil {
l.Close()
return nil, err
}
return l, nil
}
Note that although in current implementation http.ResponseWriter is a *http.response (note the lowercase!) which holds the connection, the field is unexported and you can't access it.
Instead take a look at the Server.ConnState hook: you can "register" a function which will be called when the connection state changes, see http.ConnState for details. For example you will get the net.Conn even before the request enters the handler (http.StateNew and http.StateActive states).
You can install a connection state listener by creating a custom Server like this:
func main() {
http.HandleFunc("/", myHandler)
s := &http.Server{
Addr: ":8081",
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
ConnState: ConnStateListener,
}
panic(s.ListenAndServe())
}
func ConnStateListener(c net.Conn, cs http.ConnState) {
fmt.Printf("CONN STATE: %v, %v\n", cs, c)
}
This way you will have exactly the desired net.Conn even before (and also during and after) invoking the handler. The downside is that it is not "paired" with the ResponseWriter, you have to do that manually if you need that.
You can use an HttpHijacker to take over the TCP connection from the ResponseWriter. Once you've done that you're free to use the socket to do whatever you want.
See http://golang.org/pkg/net/http/#Hijacker, which also contains a good example.
This can be done with reflection. it's a bit "dirty" but it works:
package main
import "net/http"
import "fmt"
import "runtime"
import "reflect"
func myHandler(w http.ResponseWriter, r *http.Request) {
ptrVal := reflect.ValueOf(w)
val := reflect.Indirect(ptrVal)
// w is a "http.response" struct from which we get the 'conn' field
valconn := val.FieldByName("conn")
val1 := reflect.Indirect(valconn)
// which is a http.conn from which we get the 'rwc' field
ptrRwc := val1.FieldByName("rwc").Elem()
rwc := reflect.Indirect(ptrRwc)
// which is net.TCPConn from which we get the embedded conn
val1conn := rwc.FieldByName("conn")
val2 := reflect.Indirect(val1conn)
// which is a net.conn from which we get the 'fd' field
fdmember := val2.FieldByName("fd")
val3 := reflect.Indirect(fdmember)
// which is a netFD from which we get the 'sysfd' field
netFdPtr := val3.FieldByName("sysfd")
fmt.Printf("netFDPtr= %v\n", netFdPtr)
// which is the system socket (type is plateform specific - Int for linux)
if runtime.GOOS == "linux" {
fd := int(netFdPtr.Int())
fmt.Printf("fd = %v\n", fd)
// fd is the socket - we can call unix.Syscall6(unix.SYS_GETSOCKOPT, uintptr(fd),....) on it for instance
}
fmt.Fprintf(w, "Hello World")
}
func main() {
http.HandleFunc("/", myHandler)
err := http.ListenAndServe(":8081", nil)
fmt.Println(err.Error())
}
Ideally the library should be augmented with a method to get the underlying net.Conn
Expanding on KGJV's answer, a working solution using reflection to maintain a map of connections indexed by net.Conn instance memory addresses.
Instances of net.Conn can be looked up by pointer, and pointers derived using reflection against http.Response.
It's a bit nasty, but given you can't access unpublished fields with reflection it's the only way I could see of doing it.
// Connection array indexed by connection address
var conns = make(map[uintptr]net.Conn)
var connMutex = sync.Mutex{}
// writerToConnPrt converts an http.ResponseWriter to a pointer for indexing
func writerToConnPtr(w http.ResponseWriter) uintptr {
ptrVal := reflect.ValueOf(w)
val := reflect.Indirect(ptrVal)
// http.conn
valconn := val.FieldByName("conn")
val1 := reflect.Indirect(valconn)
// net.TCPConn
ptrRwc := val1.FieldByName("rwc").Elem()
rwc := reflect.Indirect(ptrRwc)
// net.Conn
val1conn := rwc.FieldByName("conn")
val2 := reflect.Indirect(val1conn)
return val2.Addr().Pointer()
}
// connToPtr converts a net.Conn into a pointer for indexing
func connToPtr(c net.Conn) uintptr {
ptrVal := reflect.ValueOf(c)
return ptrVal.Pointer()
}
// ConnStateListener bound to server and maintains a list of connections by pointer
func ConnStateListener(c net.Conn, cs http.ConnState) {
connPtr := connToPtr(c)
connMutex.Lock()
defer connMutex.Unlock()
switch cs {
case http.StateNew:
log.Printf("CONN Opened: 0x%x\n", connPtr)
conns[connPtr] = c
case http.StateClosed:
log.Printf("CONN Closed: 0x%x\n", connPtr)
delete(conns, connPtr)
}
}
func HandleRequest(w http.ResponseWriter, r *http.Request) {
connPtr := writerToConnPtr(w)
connMutex.Lock()
defer connMutex.Unlock()
// Requests can access connections by pointer from the responseWriter object
conn, ok := conns[connPtr]
if !ok {
log.Printf("error: no matching connection found")
return
}
// Do something with connection here...
}
// Bind with http.Server.ConnState = ConnStateListener
It looks like you cannot "pair" a socket (or net.Conn) to either http.Request or http.ResponseWriter.
But you can implement your own Listener:
package main
import (
"fmt"
"net"
"net/http"
"time"
"log"
)
func main() {
// init http server
m := &MyHandler{}
s := &http.Server{
Handler: m,
}
// create custom listener
nl, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal(err)
}
l := &MyListener{nl}
// serve through custom listener
err = s.Serve(l)
if err != nil {
log.Fatal(err)
}
}
// net.Conn
type MyConn struct {
nc net.Conn
}
func (c MyConn) Read(b []byte) (n int, err error) {
return c.nc.Read(b)
}
func (c MyConn) Write(b []byte) (n int, err error) {
return c.nc.Write(b)
}
func (c MyConn) Close() error {
return c.nc.Close()
}
func (c MyConn) LocalAddr() net.Addr {
return c.nc.LocalAddr()
}
func (c MyConn) RemoteAddr() net.Addr {
return c.nc.RemoteAddr()
}
func (c MyConn) SetDeadline(t time.Time) error {
return c.nc.SetDeadline(t)
}
func (c MyConn) SetReadDeadline(t time.Time) error {
return c.nc.SetReadDeadline(t)
}
func (c MyConn) SetWriteDeadline(t time.Time) error {
return c.nc.SetWriteDeadline(t)
}
// net.Listener
type MyListener struct {
nl net.Listener
}
func (l MyListener) Accept() (c net.Conn, err error) {
nc, err := l.nl.Accept()
if err != nil {
return nil, err
}
return MyConn{nc}, nil
}
func (l MyListener) Close() error {
return l.nl.Close()
}
func (l MyListener) Addr() net.Addr {
return l.nl.Addr()
}
// http.Handler
type MyHandler struct {
// ...
}
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World")
}

How to wait for threads with low latency in go?

I've been trying to create a simple event loop wrapper in Go. But I got stumped, how was I supposed to keep track of operations in the current thread?
I wanted CurrentTick to run a function, and even if the calling function quits, not start the next tick until all functions run by CurrentTick quit. I thought I might use a mutex to monitor the number of threads, but I realized if I kept checking that over and over it would throttle the CPU. If I used time.Sleep it would be latent. How would you solve the problem?
package eventloop
import (
"reflect"
)
type eventLoop *struct{
functions []reflect.Value
addFunc chan<-/*3*/ reflect.Value
mutex chan/*1*/ bool
threads int
}
func NewEventLoop() eventLoop {
var funcs chan reflect.Value
loop := eventLoop{
[]Reflect.Value{},
funcs = make(chan reflect.Value, 3),
make(chan bool, 1),
0,
}
go func(){
for {
this.mutex <- 1
if threads == 0 {
}
}
}
}
func (this eventLoop) NextTick(f func()) {
this.addFunc <- reflect.ValueOf(f)
}
func (this eventLoop) CurrentTick(f func()) {
this.mutex <- 1
threads += 1
<-this.mutex
go func() {
f()
this.mutex <- 1
threads -= 1
<-this.mutex
}()
}
If I understand your intent, I think you're overcomplicating things. I'd do it like this:
package eventloop
type EventLoop struct {
nextFunc chan func()
curFunc chan func()
}
func NewEventLoop() *EventLoop {
el := &EventLoop{
// Adjust the capacities to taste
make(chan func(), 3),
make(chan func(), 3),
}
go eventLoop(el)
return el
}
func (el *EventLoop) NextTick(f func()) {
el.nextFunc <- f
}
func (el *EventLoop) CurrentTick(f func()) {
el.curFunc <- f
}
func (el *EventLoop) Quit() {
close(el.nextFunc)
}
func eventLoop(el *EventLoop) {
for {
f, ok := <-el.nextFunc
if !ok {
return
}
f()
drain: for {
select {
case f := <-el.curFunc:
f()
default:
break drain
}
}
}
}
Depending on your use, you may need to add some synchronization to make sure all tasks in the loop finish before your program exits.
I figured it out myself, after a lot of problems and random issues including using 15 as length instead of capacity... Seems you just have a thread send a message after you decrement the counter. (the loop.tick part could be inlined, but I'm not worried about that)
package eventloop
type eventLoop struct{
functions []func()
addFunc chan/*3*/ func()
mutex chan/*1*/ bool
threads int
waitChannel chan bool
pauseState chan bool
}
func (this *eventLoop) NextTick (f func()) {
this.addFunc <- f
}
func (this *eventLoop) tick () {
this.mutex <- true
for this.threads != 0 {
<-this.mutex
<-this.waitChannel
this.mutex <- true
}
<-this.mutex
L1: for {
select {
case f := <-this.addFunc:
this.functions = append(this.functions,f)
default: break L1
}
}
if len(this.functions) != 0 {
this.functions[0]()
if len(this.functions) >= 2 {
this.functions = this.functions[1:]
} else {
this.functions = []func(){}
}
} else {
(<-this.addFunc)()
}
}
func (this *eventLoop) CurrentTick (f func()) {
this.mutex <- true
this.threads += 1
<-this.mutex
go func() {
f()
this.mutex <- true
this.threads -= 1
<-this.mutex
this.waitChannel <- true
}()
}
func NewEventLoop () *eventLoop {
funcs := make(chan func(),3)
loop := &eventLoop{
make([]func(),0,15), /*functions*/
funcs, /*addFunc*/
make(chan bool, 1), /*mutex for threads*/
0, /*Number of threads*/
make(chan bool,0), /*The "wait" channel*/
make(chan bool,1),
}
go func(){
for { loop.tick() }
}()
return loop
}
Note: this still has lots of other problems.