1

I'm writing a small application to perform automatic postings on social networks.

My intention is that the user can via the web interface create the post at a specific time and the bot can be checking for new posts scheduled and execute them.

I'm having trouble working with routines and channels on Go.

I will leave below an example that reflects the reality of my code. It contains some comments to make it easier to understand.

What is the best way to implement a routine that checks for new posts at any time? Remembering:

  1. User can enter new posts at any time.
  2. The bot can manage hundreds/thousands of accounts at the same time. It would be essential to consume as little processing as possible.

play.golang.org (here)


    package main

    import (
        "fmt"
        "sync"
        "time"
    )

    var botRunning = true
    var wg = &sync.WaitGroup{}

    func main() {
        // I start the routine of checking for and posting scheduled appointments.
        wg.Add(1)
        go postingScheduled()

        // Later the user passes the command to stop the post.
        // At that moment I would like to stop the routine immediately without getting stuck in a loop.
        // What is the best way to do this?
        time.Sleep(3 * time.Second)
        botRunning = false

        // ignore down
        time.Sleep(2 * time.Second)
        panic("")

        wg.Wait()
    }

    // Function that keeps checking whether the routine should continue or not.
    // Check every 2 seconds.
    // I think this is very wrong because it consumes unnecessary resources.
    // -> Is there another way to do this?
    func checkRunning() {
        for {
            fmt.Println("Pause/Running? - ", botRunning)
            if botRunning {
                break
            }
            time.Sleep(2 * time.Second)
        }
    }

    // Routine that looks for the scheduled posts in the database.
    // It inserts the date of the posts in the Ticker and when the time comes the posting takes place.
    // This application will have hundreds of social network accounts and each will have its own function running in parallel.
    // -> What better way to check constantly if there are scheduled items in the database consuming the least resources on the machine?
    // -> Another important question. User can schedule posts to the database at any time. How do I check for new posts schedule while the Ticker is waiting for the time the last posting loaded?
    func postingScheduled() {
        fmt.Println("Init bot posting routine")
        defer wg.Done()
        for {
            checkRunning()
            <-time.NewTicker(2 * time.Second).C
            fmt.Println("posted success")
        }

    }

Wilson Tamarozzi
  • 606
  • 1
  • 8
  • 10
  • 1
    Possible duplicate of [I have to send out thousands of reminders, any way to avoid a ticker every minute?](https://stackoverflow.com/questions/53198618/i-have-to-send-out-thousands-of-reminders-any-way-to-avoid-a-ticker-every-minut) – Peter Jan 09 '19 at 22:04
  • is very similar, it even gave me several new ideas, but there were still some doubts. 1. How do I make a control to stop and re-run (on / off) the postage system? 2. In my case, I create the ticker initially with the time of the post. If I turn the ticker into a global variable and reset it when adding or removing post, will it stop and start? My concern is to get new posts that should be posted first but that does not get to the function because the ticker is waiting for the time of the first post. – Wilson Tamarozzi Jan 09 '19 at 23:34
  • 3. Finally, will not I have trouble keeping the checkRunning function the way it is? I say because I will have 100+ routines running in parallel and I am afraid to leave them in infinite loop for hours. – Wilson Tamarozzi Jan 09 '19 at 23:35
  • Can you consider editing the title of your post? It should describe the problem, not the particulars in which it arose. – kostix Jan 10 '19 at 10:16
  • Of course, what do you suggest? My problem was design architecture, but I do not think it fits. – Wilson Tamarozzi Jan 10 '19 at 17:31

1 Answers1

1

With Peter's response I was able to adapt all the needs to put together a sketch.

I do not know if this would be the best way to do it, maybe some function will consume processing resources unnecessarily. If anyone has better ideas for refactoring, I'll be very grateful to hear.


    package main

    import (
        "fmt"
        "log"
        "net/http"
        "sort"
        "time"
    )

    type posting struct {
        caption string
        scheduledTo time.Time
    }

    const dateLayoutFormat  = "02-01-2006 15:04:05"

    var botStatus = true
    var indexPosting int
    var tickerSchedule = time.NewTicker(1)
    var posts = []posting{
        {caption: "item 1", scheduledTo: time.Now().Add(5 * time.Second)},
        {caption: "item 2", scheduledTo: time.Now().Add(25 * time.Second)},
    }

    func init() {
        indexPosting = len(posts)
    }

    func main() {
        http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) {
            fmt.Fprint(w, "Bem vindo ao bot")
        })

        http.HandleFunc("/stop", func (w http.ResponseWriter, r *http.Request) {
            fmt.Fprint(w, "Parando o bot!")
            stopBot()
        })

        http.HandleFunc("/start", func (w http.ResponseWriter, r *http.Request) {
            fmt.Fprint(w, "Iniciando o bot!")
            startBot()
        })

        http.HandleFunc("/add", func (w http.ResponseWriter, r *http.Request) {
            t := time.Now().Add(5 * time.Second)
            indexPosting++

            addItemDB(posting{
                caption: fmt.Sprint("item ", indexPosting),
                scheduledTo: t,
            })

            fmt.Fprint(w, "Adicionando nova postagem \nPróximo post será: ", t.Format(dateLayoutFormat))
        })

        if botStatus {
            go workerScheduled()
        }

        log.Print("Inicnando server...")
        if err := http.ListenAndServe(":9090", nil); err != nil {
            log.Print("erro ao iniciar servidor => ", err)
        }

    }

    func workerScheduled() {
        for {
            log.Print("listando as próximas postagens")

            pts := getNextPostsDB()
            if len(pts) == 0 {
                log.Print("sem postagem agendada")
                botStatus = false
                return
            }

            p1 := pts[0]

            log.Printf("Próxima postagem será: %s \n\n", string(p1.scheduledTo.Format(dateLayoutFormat)))
            <- updateTimer(p1.scheduledTo).C

            if !botStatus {
                log.Print("postagem cancelado, bot status = parado")
                return
            }

            if time.Until(p1.scheduledTo) > 1 * time.Second {
                updateTimer(p1.scheduledTo)
                log.Print("timer resetado")
                continue
            }

            post(p1)
            if len(pts) > 1 {
                p2 := pts[1]
                updateTimer(p2.scheduledTo)
            }
            updatePostedDB()
        }
    }

    func updateTimer(t time.Time) *time.Ticker {
        tickerSchedule = time.NewTicker(t.Sub(time.Now()))
        return tickerSchedule
    }

    func post(p posting) {
        log.Printf("'%s' postado com sucesso", p.caption)
    }

    func addItemDB(p posting) {
        posts = append(posts, p)
        if botStatus {
            next := getNextPostDB()
            updateTimer(next.scheduledTo)
        } else {
            botStatus = true
            go workerScheduled()
        }
    }

    func getNextPostDB() posting {
        return getNextPostsDB()[0]
    }

    func getNextPostsDB() []posting {
        orderPostsList()
        removePostExpired()
        return posts
    }

    func removePostExpired() {
        for _, p := range posts {
            if p.scheduledTo.Before(time.Now()) {
                log.Printf("removendo postagem expirada")
                removePostByIndex(getIndexOf(p))
            }
        }
    }

    func removePostByIndex(i int) {
        copy(posts[i:], posts[i+1:])
        posts = posts[:len(posts)-1]
    }

    func getIndexOf(post posting) int {
        for i, p := range posts {
            if p.caption == post.caption {
                return i
            }
        }
        return -1
    }

    func updatePostedDB() {
        removePostByIndex(0)
    }

    func orderPostsList() {
        sort.Slice(posts, func(i, j int) bool {
            return posts[i].scheduledTo.Before(posts[j].scheduledTo)
        })
    }

    func startBot() {
        if !botStatus {
            log.Printf("comando 'iniciar bot'")
            botStatus = true
            go workerScheduled()
        } else {
            log.Printf("comando 'iniciar bot' (já iniciado)")
        }
    }

    func stopBot() {
        if botStatus {
            log.Printf("comando 'pausar bot'")
            botStatus = false
            tickerSchedule.Stop()
        } else {
            log.Printf("comando 'pausar bot' (já pausado)")
        }
    }

Wilson Tamarozzi
  • 606
  • 1
  • 8
  • 10