Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chg: use ticket system to throttle #263

Merged
merged 1 commit into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 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 @@
next: http.DefaultTransport,
},
},
requestTickets: startRequestTick(REQUEST_RATE_LIMIT),
}, nil
}

Expand All @@ -135,6 +141,39 @@
)
}

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

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

Check warning on line 152 in api/client.go

View check run for this annotation

Codecov / codecov/patch

api/client.go#L152

Added line #L152 was not covered by tests
}

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

Check warning on line 169 in api/client.go

View check run for this annotation

Codecov / codecov/patch

api/client.go#L164-L169

Added lines #L164 - L169 were not covered by tests
}
}
}()

return ch
}

// GetWorkspaces will be used to filter the workspaces
type GetWorkspaces struct {
Name string
Expand Down Expand Up @@ -445,18 +484,7 @@
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})

Check warning on line 487 in api/client.go

View check run for this annotation

Codecov / codecov/patch

api/client.go#L487

Added line #L487 was not covered by tests
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
Loading