Usa il lavoro con tempo e timeout

Oct 05 2020

Uso il seguente codice di esempio che funziona, ora voglio che ogni lavoro sia in grado di stampare il tempo impiegato per l'esecuzione (è meglio come generico che non ogni lavoro avrà bisogno di usare il codice di

start := time.Now()
took := time.Since(start).Milliseconds()

Fornisci anche un timeout per un lavoro, ad esempio, se sono necessari più di 10 secondi per ucciderlo o interromperlo.

package main

import (
    "encoding/json"
    "fmt"

    "github.com/gammazero/workerpool"
)

var numWorkers = 10

type MyReturnType struct {
    Name string
    Data interface{}
}

func wrapJob(rc chan MyReturnType, f func() MyReturnType) func() {
    return func() {
        rc <- f()
    }
}

func main() {
    // create results chan and worker pool
    // should prob make your results channel typed to what you need
    jobs := []func() MyReturnType {
        func() MyReturnType {
           return job1()
        },
        func() MyReturnType {
           return job2()
        },
    }

    results := make(chan MyReturnType, len(jobs))
    pool := workerpool.New(numWorkers)

    for _, job := range jobs {
        j := job
        pool.Submit(wrapJob(results, j))
    }

    // Wait for all jobs to finish
    pool.StopWait()

    // Close results chan
    close(results)

    // Iterate over results, printing to console
    for res := range results {
        prettyPrint(res)
    }
}

func prettyPrint(i interface{}) {
    prettyJSON, err := json.MarshalIndent(i, "", "    ")
    if err != nil {
        fmt.Printf("Error : %s \n", err.Error())
    }
    fmt.Printf("MyReturnType %s\n", string(prettyJSON))
}

Ecco un esempio di ciò che cerco di evitare e di fornire alcune soluzioni generali per il tempo di stampa per ogni lavoro:

func job1() {
   start := time.Now()
   ...
   // running some code
   took := time.Since(start).Milliseconds()
}

func job2(){
   start := time.Now()
   ...
   // running some code
   took := time.Since(start).Milliseconds()
}

Risposte

2 MattOestreich Oct 05 2020 at 14:33

Aggiornare

Scorri fino alla sezione "Questa è la risposta accettata" per visualizzare la risposta accettata

Sono andato avanti e ho scritto una piccola libreria dal codice nella risposta accettata ...

Puoi trovarlo qui o il codice qui sotto:

// How to use the library
package main

import (
    "fmt"
    "time"

    "github.com/oze4/reactor"
)

func main() {
    timeoutForJobs := time.Duration(time.Second * 10)
    numOfWorkers := 10

    myreactor := reactor.New(numOfWorkers, timeoutForJobs)

    // You can also create a Reactor with a custom Client
    // myreactor := reactor.NewWithClient(numOfWorkers, timeoutForJobs, &reactor.Client{...})

    // Add job(s)
    myreactor.Add(reactor.Job{
        Name: "job1",
        Runner: func(c *reactor.Client) reactor.React {
            // do something with client `c`
            res, _ := c.HTTP.Get("xyz.com")
            return reactor.React{Info: res}
        },
    })

    // All results will be here
    results := myreactor.GetResults()
    
    for _, result := range results {
        fmt.Println(result)
    }
}

Codice per la libreria

// Library code
package reactor

import (
    "context"
    "net/http"
    "time"

    "github.com/gammazero/workerpool"
    "k8s.io/client-go/kubernetes"
)

// New creates a new Reactor
func New(maxWorkers int, jobTimeout time.Duration) Reactor {
    // Do whatever you need to here to create default client
    defaultClient := &Client{
        HTTP:       http.Client{},
        Kubernetes: kubernetes.Clientset{},
    }

    return &reactor{
        workerPool:  workerpool.New(maxWorkers),
        jobTimeout:  jobTimeout,
        transport:   defaultClient,
        resultsChan: make(chan React, 100),
    }
}

// NewWithClient creates a new Reactor with a custom client
func NewWithClient(client *Client, maxWorkers int, jobTimeout time.Duration) Reactor {
    return &reactor{
        workerPool:  workerpool.New(maxWorkers),
        jobTimeout:  jobTimeout,
        transport:   client,
        resultsChan: make(chan React, 100),
    }
}

// Reactor knows how to handle jobs
type Reactor interface {
    Add(j Job)                          // Add puts a job on the queue
    Client() *Client                    // I dont know if you want the consumer to have access to this or not
    GetResults() []React                // Get results
    Timeout() time.Duration             // I dont know if you want the consumer to have access to this or not
    WorkerPool() *workerpool.WorkerPool // I dont know if you want the consumer to have access to this or not
}

type reactor struct {
    jobTimeout  time.Duration
    workerPool  *workerpool.WorkerPool
    resultsChan chan React
    transport   *Client
}

// Add submits a job
func (r *reactor) Add(j Job) {
    r.workerPool.Submit(r.wrapper(j))
}

// I dont know if you want the consumer to have access to this or not
func (r *reactor) Client() *Client {
    return r.transport
}

// Get results gets results
func (r *reactor) GetResults() []React {
    return r.getResults()
}

func (r *reactor) getResults() []React {
    r.workerPool.StopWait()
    close(r.resultsChan)

    var allReacts []React
    for jobreact := range r.resultsChan {
        allReacts = append(allReacts, jobreact)
    }

    return allReacts
}

func (r *reactor) Timeout() time.Duration {
    return r.jobTimeout
}

// I dont know if you want the consumer to have access to this or not
func (r *reactor) WorkerPool() *workerpool.WorkerPool {
    return r.workerPool
}

// worker should be private
func (r *reactor) worker(ctx context.Context, done context.CancelFunc, job Job, start time.Time) {
    runner := job.Runner(r.transport)
    runner.duration = time.Since(start)
    runner.name = job.Name

    if ctx.Err() == nil {
        r.resultsChan <- runner
    }

    done()
}

// wrapper should be private
func (r *reactor) wrapper(job Job) func() {
    ctx, cancel := context.WithTimeout(context.Background(), r.jobTimeout)

    return func() {
        start := time.Now()
        go r.worker(ctx, cancel, job, start)

        select {
        case <-ctx.Done():
            switch ctx.Err() {
            case context.DeadlineExceeded:
                r.resultsChan <- React{
                    Error:    context.DeadlineExceeded,
                    name:     job.Name,
                    duration: time.Since(start),
                }
            }
        }
    }
}

// React holds response data
type React struct {
    // This should be public so the consumer can set it
    Info  interface{}
    Error error

    // These fields should be private and handled via public methods
    duration time.Duration
    name     string
}

// Duration returns duration
func (r *React) Duration() time.Duration {
    return r.duration
}

// Name returns the job name
func (r *React) Name() string {
    return r.name
}

// Client holds http and kubernetes clients
type Client struct {
    HTTP       http.Client
    Kubernetes kubernetes.Clientset
}

// Job holds job data
type Job struct {
    Name   string
    Runner func(*Client) React
}

Questa è la risposta accettata

L'esempio seguente mostra come raccogliere il tempo di esecuzione e impostare un timeout.

package main

import (
    "context"
    "fmt"
    "time"

    "github.com/gammazero/workerpool"
)

var (
    //
    // Set timeout for all jobs here
    //
    jobTimeout = time.Duration(time.Second * 1)
)

// MyReturnType could be anything you want it to be
type MyReturnType struct {
    name              string
    Data              interface{}
    Error             error
    ExecutionDuration time.Duration
}

// Name returns name. It is written like this so the consumer
// cannot change the name outside of supplying one via the Job
func (m *MyReturnType) Name() string {
    return m.name
}

// Job holds job data
type Job struct {
    Name string
    Task func() MyReturnType
}

func wrapJob(timeout time.Duration, resultsChan chan MyReturnType, job Job) func() {
    timeoutContext, timeoutCancel := context.WithTimeout(context.Background(), timeout)

    return func() {
        timerStart := time.Now()
        go func(ctx context.Context, done context.CancelFunc, resChan chan MyReturnType, todo Job, startTime time.Time) {
            result := todo.Task()
            result.ExecutionDuration = time.Since(startTime)
            result.name = todo.Name
            if timeoutContext.Err() == nil {
                resChan <- result
            }
            done()
        }(timeoutContext, timeoutCancel, resultsChan, job, timerStart)

        select {
        case <-timeoutContext.Done():
            switch timeoutContext.Err() {
            case context.DeadlineExceeded:
                resultsChan <- MyReturnType{
                    name:              job.Name,
                    Error:             context.DeadlineExceeded,
                    ExecutionDuration: time.Since(timerStart),
                }
            }
        }
    }
}

func main() {
    jobs := []Job{
        {
            Name: "job1",
            Task: func() MyReturnType {
                // This will surpass our timeout and should get cancelled
                time.Sleep(time.Second * 3)
                // Don't have to set the name here
                return MyReturnType{Data: map[string]string{"Whatever": "You want"}}
            },
        },
        {
            Name: "job2",
            Task: func() MyReturnType {
                // This job will succeed
                time.Sleep(time.Millisecond * 300)
                resultFromCurl := "i am a result"
                return MyReturnType{Data: resultFromCurl}
            },
        },
    }

    jobResultsChannel := make(chan MyReturnType, len(jobs))
    pool := workerpool.New(10)

    for _, job := range jobs {
        pool.Submit(wrapJob(jobTimeout, jobResultsChannel, job))
    }

    pool.StopWait()
    close(jobResultsChannel)

    // Do whatever you want with results
    for jobResult := range jobResultsChannel {
        if jobResult.Error != nil {
            fmt.Printf("[took '%d' ms] '%s' : JobError : %s\n", jobResult.ExecutionDuration, jobResult.Name(), jobResult.Error)
        } else {
            fmt.Printf("[took '%d' ms] '%s' : JobSuccess : %s\n", jobResult.ExecutionDuration, jobResult.Name(), jobResult.Data)
        }
    }
}

Che ritorna:

// [took '305182398' ms] 'job2' : JobSuccess : i am a result
// [took '1001045539' ms] 'job1' : JobError : context deadline exceeded

Risposta originale

Dovresti essere in grado di utilizzare il contesto per timeout / cancellazione ( come menzionato da Peter ).

Per quanto riguarda il tempo di esecuzione della registrazione, potresti fare quello che hai dichiarato nel tuo commento , o qualcosa del genere:

package main

import (
    "fmt"
    "time"

    "github.com/gammazero/workerpool"
)

type MyReturnType struct {
    Name string
    Data interface{}
    Time time.Duration
}

func wrapJob(rc chan MyReturnType, f func() MyReturnType) func() {
    return func() {
        start := time.Now()
        result := f()
        result.Time = time.Since(start)
        rc <- result
    }
}

func main() {
    jobs := []func() MyReturnType{
        func() MyReturnType {
            time.Sleep(time.Millisecond*400)
            return MyReturnType{Name: "job1", Data: map[string]string{"Whatever": "You want"}}
        },
        func() MyReturnType {
            resultFromCurl := "i am a result"
            return MyReturnType{Name: "job2", Data: resultFromCurl}
        },
    }

    results := make(chan MyReturnType, len(jobs))
    pool := workerpool.New(10)

    for _, job := range jobs {
        j := job
        pool.Submit(wrapJob(results, j))
    }

    pool.StopWait()
    close(results)

    for res := range results {
        fmt.Printf("[took '%d' ms] ", res.Time)
        fmt.Println(res)
    }
}