I'm trying to make middleware for the Gin server to handle events during a single request because I need things like IP address from context and to not pass the whole context I'd rather pass my listener which makes my method not dependent on gin.Context.
I set up a server
func main() {
router := gin.New()
router.Use(gin.Recovery())
api := router.Group("api", middleware())
{
api.GET("test/:id", endpoint)
}
router.Run("localhost:8080")
}
and made a middleware
func middleware() gin.HandlerFunc {
return func(c *gin.Context) {
listener := make(chan int)
c.Set("EventListener", listener)
go func() {
select {
case <-listener:
fmt.Println("event called")
default:
fmt.Println("default")
}
}()
c.Next()
}
}
then I got an endpoint where I get my listener and then pass it to any function I want to
func endpoint(c *gin.Context) {
listener := c.MustGet("EventListener").(chan int)
id := c.Param("id")
idInt, err := strconv.ParseInt(id, 10, 64)
if err != nil {
fmt.Println(err)
return
}
doSomething(listener, int(idInt))
}
and doSomething function to show that no context is used later on
func doSomething(listener chan int, id int) {
if id == 1 {
fmt.Println("ERROR: cause event and exit")
listener <- int(id)
return
}
if id == 2 {
fmt.Println("WARN: cause event but continue")
listener <- int(id)
}
fmt.Println("OK: everything is fine")
}
And the way it works now is:
when you call GET http://localhost:8080/api/test/1 it will trigger event and exit
when you call GET http://localhost:8080/api/test/2 it will trigger event and keep working
when you call GET http://localhost:8080/api/test/3 it will not trigger event because all is ok.
So everything is working but only for one event along request. You can not call another one, because select already passed so my question is how to fix it and allow to trigger event multiple times.
I know I can make it like
for {
select {
case <-listener:
fmt.Println("event called")
}
}
but what would be stop condition of this loop?
I know there is something like c.Done() which is chan but have no idea how to use it in my case because I can make it like
for {
select {
case <-listener:
fmt.Println("event called")
case <-c.Done():
return
}
}
but how to pass that c.Done()? This goroutine does not stop.
I found out that there is also c.Request.Context().Done() which works now, stopping goroutine and allowing to handle multiple events
go func() {
for {
select {
case <-listener:
fmt.Println("event called")
case <-c.Request.Context().Done():
return
}
}
}()
Related
Go newbie here. I am trying to create a http handler that drops the main routine if the client drops the connection by using the request context. Inspiration is from here: https://go.dev/blog/context/google/google.go
The general structure of my code is to kick off a subroutine to retrieve a slice of struct pointers and pass them back to the main routine via a channel. The main routine has a select on client request context and the data-retrieval subroutine.
Go Playground: https://go.dev/play/p/JRjoFNw5Gko
Code:
package handlers
import (
"context"
"encoding/json"
"log"
"net/http"
// "gitlab.com/mbsloth14/learn-go/monorepo/products/data"
)
type Product struct {
ID int `json:"id"`
}
type Products []*Product
var productList = []*Product{
{
ID: 1,
},
{
ID: 2,
},
}
type ListProducts struct {
log *log.Logger
}
func NewListProducts(log *log.Logger) *ListProducts {
return &ListProducts{log: log}
}
func (lp *ListProducts) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
p, retrieveProductsErr := lp.listProducts(req.Context())
if retrieveProductsErr != nil {
http.Error(rw, retrieveProductsErr.Error(), http.StatusInternalServerError)
}
lp.log.Printf("ListProductsHandler: retrieved %d products\n", len(p))
encoder := json.NewEncoder(rw)
err := encoder.Encode(p)
if err != nil {
http.Error(rw, "Server failed to encode results. Returning empty list.", http.StatusInternalServerError)
lp.log.Println("ListProductsHandler: failed to encode results", err)
}
}
func (lp *ListProducts) listProducts(ctx context.Context) (Products, error) {
resultsChan := make(chan Products)
defer close(resultsChan)
go func() {
lp.log.Println("ListProductsHandler: retrieving products")
// time.Sleep(5 * time.Second) // <------- PROBLEMATIC LINE!
resultsChan <- getProducts()
}()
select {
case <-ctx.Done():
lp.log.Println("ListProductsHandler: client canceled request")
<-resultsChan
return Products{}, ctx.Err()
case p := <-resultsChan:
return p, nil
}
}
// Mock DB/API call
func getProducts() Products {
return productList
}
I am testing if the select statement is effective by adding a sleep statement in the subroutine to simulate database delay. Regardless of this delay, the handler always shows that 2 products are retrieved. However, when the subroutine sleeps, encoder writes an empty string back to the client whereas the entire slice gets properly marshaled back to the client when there is no sleep.
Any idea would be super appreciated. Thank you
I have the following code for a module I'm developing and I'm not sure why the provider.Shutdown() function is never called when I called .Stop()
The main process does stop but I'm confused why this doesn't work?
package pluto
import (
"context"
"fmt"
"log"
"sync"
)
type Client struct {
name string
providers []Provider
cancelCtxFunc context.CancelFunc
}
func NewClient(name string) *Client {
return &Client{name: name}
}
func (c *Client) Start(blocking bool) {
log.Println(fmt.Sprintf("Starting the %s service", c.name))
ctx, cancel := context.WithCancel(context.Background())
c.cancelCtxFunc = cancel // assign for later use
var wg sync.WaitGroup
for _, p := range c.providers {
wg.Add(1)
provider := p
go func() {
provider.Setup()
select {
case <-ctx.Done():
// THIS IS NEVER CALLED?!??!
provider.Shutdown()
return
default:
provider.Run(ctx)
}
}()
}
if blocking {
wg.Wait()
}
}
func (c *Client) RegisterProvider(p Provider) {
c.providers = append(c.providers, p)
}
func (c *Client) Stop() {
log.Println("Attempting to stop service")
c.cancelCtxFunc()
}
Client code
package main
import (
"pluto/pkgs/pluto"
"time"
)
func main() {
client := pluto.NewClient("test-client")
testProvider := pluto.NewTestProvider()
client.RegisterProvider(testProvider)
client.Start(false)
time.Sleep(time.Second * 3)
client.Stop()
}
Because it's already chosen the other case before the context is cancelled. Here is your code, annotated:
// Start a new goroutine
go func() {
provider.Setup()
// Select the first available case
select {
// Is the context cancelled right now?
case <-ctx.Done():
// THIS IS NEVER CALLED?!??!
provider.Shutdown()
return
// No? Then call provider.Run()
default:
provider.Run(ctx)
// Run returned, nothing more to do, we're not in a loop, so our goroutine returns
}
}()
Once provider.Run is called, cancelling the context isn't going to do anything in the code shown. provider.Run also gets the context though, so it is free to handle cancellation as it sees fit. If you want your routine to also see cancellation, you could wrap this in a loop:
go func() {
provider.Setup()
for {
select {
case <-ctx.Done():
// THIS IS NEVER CALLED?!??!
provider.Shutdown()
return
default:
provider.Run(ctx)
}
}
}()
This way, once provider.Run returns, it will go through the select again, and if the context has been cancelled, that case will be called. However, if the context hasn't been cancelled, it'll call provider.Run again, which may or may not be what you want.
EDIT:
More typically, you'd have one of a couple scenarios, depending on how provider.Run and provider.Shutdown work, which hasn't been made clear in the question, so here are your options:
Shutdown must be called when the context is cancelled, and Run must only be called once:
go func() {
provider.Setup()
go provider.Run(ctx)
go func() {
<- ctx.Done()
provider.Shutdown()
}()
}
Or Run, which already receives the context, already does the same thing as Shutdown when the context is cancelled, and therefore calling Shutdown when the context is cancelled is wholly unnecessary:
go provider.Run(ctx)
I have the following code in Go using the semaphore library just as an example:
package main
import (
"fmt"
"context"
"time"
"golang.org/x/sync/semaphore"
)
// This protects the lockedVar variable
var lock *semaphore.Weighted
// Only one go routine should be able to access this at once
var lockedVar string
func acquireLock() {
err := lock.Acquire(context.TODO(), 1)
if err != nil {
panic(err)
}
}
func releaseLock() {
lock.Release(1)
}
func useLockedVar() {
acquireLock()
fmt.Printf("lockedVar used: %s\n", lockedVar)
releaseLock()
}
func causeDeadLock() {
acquireLock()
// calling this from a function that's already
// locked the lockedVar should cause a deadlock.
useLockedVar()
releaseLock()
}
func main() {
lock = semaphore.NewWeighted(1)
lockedVar = "this is the locked var"
// this is only on a separate goroutine so that the standard
// go "deadlock" message doesn't print out.
go causeDeadLock()
// Keep the primary goroutine active.
for true {
time.Sleep(time.Second)
}
}
Is there a way to get the acquireLock() function call to print a message after a timeout indicating that there is a potential deadlock but without unblocking the call? I would want the deadlock to persist, but a log message to be written in the event that a timeout is reached. So a TryAcquire isn't exactly what I want.
An example of what I want in psuedo code:
afterFiveSeconds := func() {
fmt.Printf("there is a potential deadlock\n")
}
lock.Acquire(context.TODO(), 1, afterFiveSeconds)
The lock.Acquire call in this example would call the afterFiveSeconds callback if the Acquire call blocked for more than 5 seconds, but it would not unblock the caller. It would continue to block.
I think I've found a solution to my problem.
func acquireLock() {
timeoutChan := make(chan bool)
go func() {
select {
case <-time.After(time.Second * time.Duration(5)):
fmt.Printf("potential deadlock while acquiring semaphore\n")
case <-timeoutChan:
break
}
}()
err := lock.Acquire(context.TODO(), 1)
close(timeoutChan)
if err != nil {
panic(err)
}
}
I have a func that receives context and does some cpubound operations like bellow.
func DoSomeOperation(ctx context.Context){
CPUBoundWork1()
CPUBoundWork2()
CPUBoundWork3()
CPUBoundWork4()
}
What I want to do is to check if Context has been cancelled or not before making each CPUBound func call. If cancelled I want to return immidiately without making next func call. Is there any way to do this?
Use ctx.Err()
if ctx.Err() == context.Canceled {
return
}
You also can use select statement with slice of functions.
For Example:
ctx := ...
executors := []func(){...}
Loop:
for _,executor := range executors{
select {
case <-ctx.Done():
if ctx.Err() == context.Canceled {
break Loop
}
if ctx.Err() == context.DeadlineExceeded {
//do something else
}
default:
executor()
}
}
PLAYGROUND
I am attempting to create a poller in Go that spins up and every 24 hours executes a function.
I want to also be able to stop the polling, I'm attempting to do this by having a done channel and passing down an empty struct to stop the for loop.
In my tests, the for just loops infinitely and I can't seem to stop it, am I using the done channel incorrectly? The ticker case works as expected.
Poller struct {
HandlerFunc HandlerFunc
interval *time.Ticker
done chan struct{}
}
func (p *Poller) Start() error {
for {
select {
case <-p.interval.C:
err := p.HandlerFunc()
if err != nil {
return err
}
case <-p.done:
return nil
}
}
}
func (p *Poller) Stop() {
p.done <- struct{}{}
}
Here is the test that's exeuting the code and causing the infinite loop.
poller := poller.NewPoller(
testHandlerFunc,
time.NewTicker(1*time.Millisecond),
)
err := poller.Start()
assert.Error(t, err)
poller.Stop()
Seems like problem is in your use case, you calling poller.Start() in blocking maner, so poller.Stop() is never called. It's common, in go projects to call goroutine inside of Start/Run methods, so, in poller.Start(), i would do something like that:
func (p *Poller) Start() <-chan error {
errc := make(chan error, 1 )
go func() {
defer close(errc)
for {
select {
case <-p.interval.C:
err := p.HandlerFunc()
if err != nil {
errc <- err
return
}
case <-p.done:
return
}
}
}
return errc
}
Also, there's no need to send empty struct to done channel. Closing channel like close(p.done) is more idiomatic for go.
There is no explicit way in Go to broadcast an event to go routines for something like cancellation. Instead its idiomatic to create a channel that when closed signifies a message such as cancelling any work it has to do. Something like this is a viable pattern:
var done = make(chan struct{})
func cancelled() bool {
select {
case <-done:
return true
default:
return false
}
}
Go-routines can call cancelled to poll for a cancellation.
Then your main loop can respond to such an event but make sure you drain any channels that might cause go-routines to block.
for {
select {
case <-done:
// Drain whatever channels you need to.
for range someChannel { }
return
//.. Other cases
}
}