Skip to content

Commit

Permalink
chg: use ticket system to throttle (#263)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucassabreu authored May 17, 2024
1 parent 042b41d commit 79fc6ce
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 14 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- using throttle/ticket providing system to limit requests per second to the clockify's api to prevent the
error message: `Too Many Requests (code: 429)`

## [v0.49.0] - 2024-03-29

### Added
Expand Down
56 changes: 42 additions & 14 deletions api/client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package api

import (
"context"
"encoding/hex"
"fmt"
"net/http"
Expand Down Expand Up @@ -86,13 +87,17 @@ type Client interface {
type client struct {
baseURL *url.URL
http.Client
debugLogger Logger
infoLogger Logger
debugLogger Logger
infoLogger Logger
requestTickets chan struct{}
}

// baseURL is the Clockify API base URL
const baseURL = "https://api.clockify.me/api"

// REQUEST_RATE_LIMIT maximum number of requests per second
const REQUEST_RATE_LIMIT = 50

// ErrorMissingAPIKey returned if X-Api-Key is missing
var ErrorMissingAPIKey = errors.New("api Key must be informed")

Expand Down Expand Up @@ -124,6 +129,7 @@ func NewClientFromUrlAndKey(
next: http.DefaultTransport,
},
},
requestTickets: startRequestTick(REQUEST_RATE_LIMIT),
}, nil
}

Expand All @@ -135,6 +141,39 @@ func NewClient(apiKey string) (Client, error) {
)
}

func startRequestTick(limit int) chan struct{} {
ch := make(chan struct{}, limit)

running := true
release := func() {
i := len(ch)
for i < limit {
if !running {
return
}

i = i + 1
ch <- struct{}{}
}
}

go func() {
release()
for {
select {
case <-time.After(time.Second):
go release()
case <-context.Background().Done():
running = false
defer close(ch)
return
}
}
}()

return ch
}

// GetWorkspaces will be used to filter the workspaces
type GetWorkspaces struct {
Name string
Expand Down Expand Up @@ -445,18 +484,7 @@ func (c *client) GetUsersHydratedTimeEntries(p GetUserTimeEntriesParam) ([]dto.T
return timeEntries, err
}

var user dto.User
tries := 0
for tries < 5 {
tries++
user, err = c.GetUser(GetUser{p.Workspace, p.UserID})
if err == nil || !errors.Is(err, ErrorTooManyRequests) {
break
}

time.Sleep(time.Duration(5))
}

user, err := c.GetUser(GetUser{p.Workspace, p.UserID})
if err != nil {
return timeEntries, err
}
Expand Down
3 changes: 3 additions & 0 deletions api/httpClient.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ func (c *client) NewRequest(method, uri string, body interface{}) (*http.Request
// Do executes a http.Request inside the Clockify's Client
func (c *client) Do(
req *http.Request, v interface{}, name string) (*http.Response, error) {

<-c.requestTickets

r, err := c.Client.Do(req)
if err != nil {
return r, err
Expand Down

0 comments on commit 79fc6ce

Please sign in to comment.