How to implement a function using channels - go

There is a function that is running in goroutine:
func (c *controlUC) WebhookPool() {
for {
if len(c.webhookPool) == 0 {
continue
}
for i := 0; i < len(c.webhookPool); i++ {
if !c.webhookPool[i].LastSentTime.IsZero() && time.Now().Before(c.webhookPool[i].LastSentTime.Add(GetDelayBySentCount(c.webhookPool[i].SendCount))) {
continue
}
var headers = make(map[string]string)
headers["Content-type"] = "application/json"
_, statusCode, err := c.fhttpClient.Request("POST", c.webhookPool[i].Path, c.webhookPool[i].Body, nil, headers)
if err != nil {
c.logger.Error(err)
return
}
if statusCode != 200 {
if c.webhookPool[i].SendCount >= 2 {
c.webhookPool = append(c.webhookPool[:i], c.webhookPool[i+1:]...)
i--
continue
}
c.webhookPool[i].SendCount++
} else {
c.webhookPool = append(c.webhookPool[:i], c.webhookPool[i+1:]...)
i--
continue
}
c.webhookPool[i].LastSentTime = time.Now()
}
}
}
// webhookPool []models.WebhookPoolElem
type WebhookPoolElem struct {
SendCount int
LastSentTime time.Time
Path string
Body []byte
}
The webhookPoolElem element is added to c.webhookpool, after which a request is sent to the server (the path is taken from WebhookPoolElem.path). If the server returned a non - 200 200, then I need to send the request again, after X seconds (taken from GetDelayBySentCount(), depending on SendCount returns different times). The number of attempts is limited (c.webhookpool[i].SendCount >= 2)
But maybe this function needs to be done through channels? If so, how?

Lets say controlUC receiver has a field webhookPool chan WebhookPoolElem and init as webhookPool: make(chan WebhookPoolElem, n) with n as buffer.
You can receive elements and more or less replace c.webhookPool[i] to elem. Rewrite like this:
func (c *controlUC) WebhookPool() {
for {
elem, open := <-c.webhookPool
if !open {
return
}
if !elem.LastSentTime.IsZero() && time.Now().Before(elem.LastSentTime.Add(GetDelayBySentCount(elem.SendCount))) {
continue
}
// I omit http request
if statusCode != 200 {
if elem.SendCount >= 2 {
// drop message from channel, no need to do anything
continue
}
elem.SendCount++
elem.LastSentTime = time.Now()
c.webhookPool <- elem // enqueue again
}
}
I suggest buffered channel so the last send c.webhookPool <- elem does not block, but it's best if you place the send in a select so if the send can not proceed regardless of the buffer, the goroutine doesn't block:
select {
case c.webhookPool <- elem:
// success
default:
// can not send
}

Related

Handle user answer

The task is that user input a sum of deposit and I could handle it in this context, but isn't like a simple command. Example:
My code:
func main () {
testing()
NewBot, BotError = tgBotApi.NewBotAPI(configuration.BOT_TOKEN)
if BotError != nil {
fmt.Println(BotError.Error())
}
NewBot.Debug = true
fmt.Println("OK", time.Now().Unix(), time.Now(), time.Now().Weekday())
setWebhook(NewBot)
updates := NewBot.ListenForWebhook("/" + configuration.BOT_TOKEN)
//go successfulPaymentListen()
go http.ListenAndServeTLS(fmt.Sprintf("%s:%s", configuration.BOT_HOST, configuration.BOT_PORT), configuration.CERT_FILE, configuration.CERT_KEY, nil)
for update := range updates {
if update.Message != nil {
recognizeCommand(update)
} else if update.CallbackQuery != nil {
if update.CallbackQuery.Data == "/addFunds crypto" {
get_data.AddFundsChooseCurrencyCrypto(update, NewBot)
} else if update.CallbackQuery.Data == "/addFunds qiwi" {
get_data.AddFundsChooseCurrencyQiwi(update, NewBot)
} else if strings.Split(update.CallbackQuery.Data, " ")[2] != "" {
get_data.AddFundsChooseCurrencyCurrentCrypto(update, NewBot, strings.Split(update.CallbackQuery.Data, " ")[2])
//This function is below
}
}
}
}
get_data.AddFundsChooseCurrencyCurrentCrypto:
func AddFundsChooseCurrencyCurrentCrypto(update tgBotApi.Update, NewBot *tgBotApi.BotAPI, currency string) {
chatUser := int64(update.CallbackQuery.From.ID)
msg := tgBotApi.NewMessage(chatUser, "Input a sum of deposit:")
NewBot.Send(msg)
//There is I have to handle user answer, but I can't override ListenWebHook
}
The problem is that I need ListenWebHook localy( in the function AddFundsChooseCurrencyCurrentCrypto) instead of main function
------------------------ UPDATE ------------------------
I have tried this code:
func AddFundsChooseCurrencyCurrentCrypto(update tgBotApi.Update, NewBot *tgBotApi.BotAPI, currency string) {
chatUser := int64(update.CallbackQuery.From.ID)
msg := tgBotApi.NewMessage(chatUser, "Input a sum of deposit:")
NewBot.Send(msg)
NewBotContext, BotError := tgBotApi.NewBotAPI(configuration.BOT_TOKEN)
if BotError != nil {
log.Panic(BotError.Error())
}
updates := NewBotContext.ListenForWebhook("/" + configuration.BOT_TOKEN)
for update := range updates {
fmt.Println(update)
}
}
But error:
panic: http: multiple registrations for /mytokenbot
goroutine 1 [running]:
net/http.(*ServeMux).Handle(0xe38620, 0xc25304, 0x2f, 0xc7dbe0, 0xc00018bec0)
You've tried to register the same url '/mytokenbot' twice with your router. You can find the error in net/http:
https://golang.org/src/net/http/server.go#L2433
In the mux Handle function.
So just look through your code for the register function with servemux, and check how you might be calling it twice.

Understand this code (golang), double parantheses ()()

I was wondering if someone can explain this syntax to me. In the google maps go api, they have
type Client struct {
httpClient *http.Client
apiKey string
baseURL string
clientID string
signature []byte
requestsPerSecond int
rateLimiter chan int
}
// NewClient constructs a new Client which can make requests to the Google Maps WebService APIs.
func NewClient(options ...ClientOption) (*Client, error) {
c := &Client{requestsPerSecond: defaultRequestsPerSecond}
WithHTTPClient(&http.Client{})(c) //???????????
for _, option := range options {
err := option(c)
if err != nil {
return nil, err
}
}
if c.apiKey == "" && (c.clientID == "" || len(c.signature) == 0) {
return nil, errors.New("maps: API Key or Maps for Work credentials missing")
}
// Implement a bursty rate limiter.
// Allow up to 1 second worth of requests to be made at once.
c.rateLimiter = make(chan int, c.requestsPerSecond)
// Prefill rateLimiter with 1 seconds worth of requests.
for i := 0; i < c.requestsPerSecond; i++ {
c.rateLimiter <- 1
}
go func() {
// Wait a second for pre-filled quota to drain
time.Sleep(time.Second)
// Then, refill rateLimiter continuously
for _ = range time.Tick(time.Second / time.Duration(c.requestsPerSecond)) {
c.rateLimiter <- 1
}
}()
return c, nil
}
// WithHTTPClient configures a Maps API client with a http.Client to make requests over.
func WithHTTPClient(c *http.Client) ClientOption {
return func(client *Client) error {
if _, ok := c.Transport.(*transport); !ok {
t := c.Transport
if t != nil {
c.Transport = &transport{Base: t}
} else {
c.Transport = &transport{Base: http.DefaultTransport}
}
}
client.httpClient = c
return nil
}
}
And this is the line I don't understand in NewClient
WithHTTPClient(&http.Client{})(c)
Why are there two ()()?
I see that WithHTTPClient takes in a *http.Client which that line does, but then it also passes in a pointer to the client struct declared above it?
WithHTTPClient returns a function, ie:
func WithHTTPClient(c *http.Client) ClientOption {
return func(client *Client) error {
....
return nil
}
}
WithHTTPClient(&http.Client{})(c) is just calling that function with c (a pointer to a Client) as parameter. It could be written as:
f := WithHTTPClient(&http.Client{})
f(c)

Value sent to channel by method in go, cannot be received on the outside

I was trying to implement sort-of a reactive golang implementation. I have an array of observers. They are just a bunch of channels. Everything is encapsulated in a package where other code could subscribe and unsubscribe. When ever an order is created, the change will be pushed. But I have failed to register channel receive with in a method.
package rxOrder
import (
"fmt"
"time"
"errors"
"gopkg.in/mgo.v2/bson"
)
// Order This is the sample data structure
type Order struct {
id bson.ObjectId
moldID bson.ObjectId
bomID bson.ObjectId
deviceID bson.ObjectId
userIds []bson.ObjectId
name string
orderType string // withOrder, noOrder, makeUp, test
startTime time.Time
deadline time.Time
volume int32
}
// OrderMutation This is the struct for sending
// mutations to observers
type OrderMutation struct {
order Order
action string
}
// RxOrder This is the node for reactive Order
// management
type RxOrder struct {
orders []Order
observers map[string]chan OrderMutation
}
// init This method initialize RxOrder, including
// orders slice and subscriber map, user cannot
// initialize a RxOrder object more than once
func (rx *RxOrder) init() error {
if len(rx.orders) == 0 && len(rx.observers) == 0 {
rx.orders = make([]Order, 1)
rx.observers = make(map[string]chan OrderMutation)
return nil
}
return errors.New("Cannot reinitialize orders")
}
// subscribe, add observer to list
func (rx *RxOrder) subscribe(key string, ch chan OrderMutation) error {
if _, ok := rx.observers[key]; ok {
return errors.New("Observer already existed")
}
rx.observers[key] = ch
return nil
}
// unsubscribe, delete observer from list
func (rx *RxOrder) unsubscribe(key string) error {
if _, ok := rx.observers[key]; !ok {
return errors.New("Observer does not exist")
}
delete(rx.observers, key)
return nil
}
// createOrder The method for creating an order
func (rx *RxOrder) createOrder(order Order) error {
if !order.id.Valid() {
return errors.New("Invalid order id")
}
if !order.bomID.Valid() {
return errors.New("Invalid bom id")
}
if !order.deviceID.Valid() {
return errors.New("Invalid device id")
}
if !order.moldID.Valid() {
return errors.New("Invalid mold id")
}
if len(order.userIds) < 1 {
return errors.New("Empty users list")
}
for index, userID := range order.userIds {
if !userID.Valid() {
return errors.New(fmt.Sprint("Invalid user id at index: ", index))
}
}
if len(order.name) < 1 {
return errors.New("Empty order name")
}
if order.orderType != "withOrder" && order.orderType != "noOrder" && order.orderType != "makeUp" && order.orderType != "test" {
return errors.New("Wrong order type")
}
if order.startTime.After(order.deadline) {
return errors.New("Deadline cannot come before start time")
}
if order.volume < 1 {
return errors.New("Empty order is not accepted")
}
rx.orders = append(rx.orders, order)
for _, ch := range rx.observers {
ch <- OrderMutation{order, "create"}
}
return nil
}
func TestCreateOrder(t *testing.T) {
orderManagement := RxOrder{}
orderManagement.init()
orderManagement.subscribe("123", make(chan OrderMutation))
orderManagement.subscribe("345", make(chan OrderMutation))
orderManagement.subscribe("768", make(chan OrderMutation))
order := Order{}
order.id = bson.NewObjectId()
order.bomID = bson.NewObjectId()
order.deviceID = bson.NewObjectId()
order.moldID = bson.NewObjectId()
order.name = "iPhone 8+"
order.orderType = "withOrder"
order.volume = 5
order.startTime = time.Now()
order.deadline = order.startTime.AddDate(0, 1, 1)
order.userIds = make([]bson.ObjectId, 1)
order.userIds = append(order.userIds, bson.NewObjectId())
go func(t *testing.T) {
fmt.Println(<-orderManagement.observers["123"])
}(t)
orderManagement.createOrder(order)
//orderManagement.observers["123"] <- OrderMutation{order, "w"}
t.Fail()
}
when I do test, the above code prints nothing, but if I uncomment line:
orderManagement.observers["123"] <- OrderMutation{order, "w"}
Everything works. It seems I cannot operate on channel within a method. How can I encapsulate channel operation with in package?
The situation depends on the following outputs:
Does you createOrder return an error?
If createOrder has any error,then it will not send any message on the channel, so by commenting that line your main test function will exit without waiting.
You have no output, if you add the mentioned line, your main test function will wait on the channel operation until your coroutine receives the message and prints the output.
If your createOrder has no error, you will face a race condition because the message sent on channel except "123" will block your main test function forever.
After I did this, everything works.
go func() {
for _, ch: = range rx.observers {
ch <-OrderMutation {
order, "create"
}
}
}()
// create a goroutine to send message
func() {
orderManagement.createOrder(order)
}()
select {
case val := <-orderManagement.observers["123"]:
fmt.Println(val)
}
// then receive on the outside like this
Even without resolve method everything works.

Idiomatic go way to compare 2 slices to see which element has been removed

I am using RethinkDB in my application and I have a lobby that has users.
RethinkDB has the ability to watch for changes to a table and when changes happen it automagically emits the changes so you can do whatever you want with the data, right now I'm trying to make it so when a user leaves the lobby I can send a websocket out to remove the user. The only thing is I'm trying to find out where the difference is in the before / after data which is a slice of members, this is the data:
type change struct {
NewVal *fields `gorethink:"new_val,omitempty"`
OldVal *fields `gorethink:"old_val,omitempty"`
}
type fields struct {
ID string `gorethink:"id"`
Owner string `gorethink:"owner"`
Inqueue bool `gorethink:"inqueue"`
Members []struct {
SteamID string `gorethink:"steamid"`
Username string `gorethink:"username"`
} `gorethink:"members"`
Messages []struct {
Username string `gorethink:"username"`
Message string `gorethink:"message"`
CreatedAt time.Time `gorethink:"createdAt"`
} `gorethink:"messages"`
}
Right now I'm doing
func (l *lobby) watchChanges() {
db := common.DB()
query := gorethink.Table("Lobbys").Get(l.ID).Changes()
res, err := query.Run(db)
if err != nil {
log.Println(err)
}
go func(res *gorethink.Cursor, l *lobby) {
defer res.Close()
changes := new(change)
for res.Next(&changes) {
if changes.NewVal != nil && changes.OldVal != nil {
switch {
case len(changes.NewVal.Members) > len(changes.OldVal.Members):
// Member has joined so announce who it was.
case len(changes.NewVal.Members) < len(changes.OldVal.Members):
// Member has left so announce who it was.
-------->
case len(changes.NewVal.Messages) > len(changes.OldVal.Messages):
// New Message was recieved so announce the message.
}
}
}
}(res, l)
select {
case <-l.KillMe:
res.Close()
break
}
}
The new entries are easy enough I'll just take the end off the slice and send that, but when it comes to the user leaving, how can I compare the changes.NewVal.Members and changes.OldVal.Members to see which index was removed so I can send the right member to remove through the websocket. Hope my question is clear let me know if it isn't.
This is how I'm currently doing it
removedIndex := 0
for i, oldMember := range changes.OldVal.Members {
foundMissing := true
for _, newMember := range changes.NewVal.Members {
if reflect.DeepEqual(oldMember, newMember) {
foundMissing = false
}
}
if foundMissing {
removedIndex = i
break
}
}
but it feels a bit hacky, is there a better way?
Sort the old and new members by a unique and sortable key. It looks like SteamID might be suitable for this purpose. Iterate through both slices checking for added and deleted elements by comparing the keys.
func diff(old []*member, new []*member) {
sort.Sort(bySteamID(old))
sort.Sort(bySteamID(new))
i, j := 0, 0
for i < len(old) && j < len(new) {
switch {
case old[i].SteamID < new[j].SteamID:
fmt.Println(" delete", old[i].SteamID)
i++
case old[i].SteamID > new[j].SteamID:
fmt.Println(" add", new[j].SteamID)
j++
default:
i++
j++
}
}
for i < len(old) {
fmt.Println(" delete", old[i].SteamID)
i++
}
for j < len(new) {
fmt.Println(" add", new[j].SteamID)
j++
}
}
playground example

How can I get rid of this data race

I have these 2 functions:
// PartyHub struct contains all data for the party
type PartyHub struct {
FullPartys map[string]Party
PartialPartys map[string]Party
Enter chan Member
Leave chan Member
sync.Mutex
}
// RemoveFromQueue will remove the member from party
func (p *PartyHub) RemoveFromQueue(memberLeaving Member, inQueue bool) {
if !inQueue {
return
}
for _, party := range p.PartialPartys {
go func(party Party) {
if _, ok := party.Members[memberLeaving.Identifier]; ok {
p.Lock()
->>>>>>>> delete(party.Members, memberLeaving.Identifier)
p.Unlock()
}
}(party)
}
log.Println("Removing")
}
// SortIntoParty will sort the member into party
func (p *PartyHub) SortIntoParty(newMember Member, inQueue bool) {
log.Println(inQueue)
if inQueue {
return
}
log.Println("Adding")
foundParty := false
->> for partyid, party := range p.PartialPartys {
if !party.Accepting {
continue
}
goodFitForParty := true
for _, partyMember := range party.Members {
if newMember.Type == partyMember.Type && newMember.Rank >= partyMember.Rank-partyMember.RankTol && newMember.Rank <= partyMember.Rank+partyMember.RankTol {
goodFitForParty = true
continue
} else {
goodFitForParty = false
break
}
}
if !goodFitForParty {
continue
} else {
foundParty = true
newMember.Conn.CurrentParty = partyid
p.Lock()
p.PartialPartys[partyid].Members[newMember.Conn.Identifier] = newMember
p.Unlock()
if len(party.Members) == 2 {
p.Lock()
party.Accepting = false
p.Unlock()
// Start Go Routine
}
break
}
}
if !foundParty {
uuid := feeds.NewUUID().String()
newMember.Conn.CurrentParty = uuid
p.Lock()
p.PartialPartys[uuid] = Party{Accepting: true, Members: make(map[string]Member), Ready: make(chan *Connection), Decline: make(chan *Connection)}
p.PartialPartys[uuid].Members[newMember.Conn.Identifier] = newMember
p.Unlock()
}
}
I put ->>>>>> next to where the 2 pieces of code are being accessed, I'm not sure how I can keep these 2 up to date without being in a data race, fairly new to go and wondering how I should be reading and writing this variable without a data-race.
You've got a lot of code in your question, but it looks like you're trying to delete elements from a map (party.Members) in one goroutine, while looping over it in another. This sounds like an unmaintainable, error-ridden disaster in the making, but it's possible to do without memory races.
You need a mutex to protect access (both read and write) to the map, and the hard part is to make sure the lock is held during the for/range iteration. Here's one way to do it, by having the lock held before the for loop starts, and unlocking it inside the body of the loop.
var mut sync.Mutex
var m = map[string]int{}
func f(key string) {
mut.Lock()
defer mut.Unlock()
delete(m, key)
}
func g() {
mut.Lock()
defer mut.Unlock()
for k, v := range m {
mut.Unlock()
fmt.Println(k, v)
mut.Lock()
}
}
Here, any combination of fs and gs can be called concurrently without memory races.
Drastically simpler to understand would be to not Unlock/Lock the mutex inside the loop, which would mean a deletion in f would wait for any running loop in g to complete (or vice-versa).

Resources