tentacat icon indicating copy to clipboard operation
tentacat copied to clipboard

Github rate limit

Open mgwidmann opened this issue 8 years ago • 12 comments

Currently theres no handling of the rate limit in this project. I regularly hit the rate limit (usually at the end of the hour) and would like to hear your thoughts on how to handle this properly.

Heres the exception I get when that happens:

[error] Task #PID<0.516.0> started from #PID<0.515.0> terminating
** (FunctionClauseError) no function clause matching in Tentacat.process_stream/1
    lib/tentacat.ex:102: Tentacat.process_stream({403, %{"documentation_url" => "https://developer.github.com/v3/#rate-limiting", "message" => "API rate limit exceeded for mgwidmann."}})
    (elixir) lib/stream.ex:1099: Stream.do_resource/5
    (elixir) lib/enum.ex:1486: Enum.reduce/3
    (elixir) lib/enum.ex:2248: Enum.to_list/1

mgwidmann avatar May 13 '16 18:05 mgwidmann

not sure what you mean by "no handling of the rate limit" What is your usecase? Tentacat is a simple wrapper for the Github API and most likely you can avoid hitting the rate limits by rethinking your use case or its implementation.

one thing I could think of improving on tentacats side is trying to implement https://developer.github.com/v3/#conditional-requests but I'm not sure whether that would be a solution for your usecase.

duksis avatar May 13 '16 21:05 duksis

That would most likely greatly improve my rate limiting... I'd give a 👍 that

What I mean is that I get that exception when I hit the limit. Perhaps instead of returning the data, Tentacat should return a {:ok, data} or {:error, reason} kind of tuples, and then have a corresponding ! method which does what the current implementation now does. This way if I'm unsure if I'll hit the limit i can choose to handle it, right now I have no choice but to rescue the exception.

mgwidmann avatar May 16 '16 14:05 mgwidmann

I implemented a caching layer around Tentacat for this reason.

The code for the cache layer is here:

https://github.com/thinkthroughmath/dev_wizard/blob/master/lib/dev_wizard/github_gateway/cache.ex

Usage is here:

https://github.com/thinkthroughmath/dev_wizard/blob/master/lib/dev_wizard/github_gateway.ex#L34

This solved the problem for us.

joelmccracken avatar Sep 24 '16 18:09 joelmccracken

There's a discussion about adding a cache store support to httpoison https://github.com/edgurgel/httpoison/issues/172 . Maybe this could help us all!

edgurgel avatar Sep 25 '16 21:09 edgurgel

Hmm. The rate limit is not a problem, imho. The lack of exposure of the rate limit headers in the response is a problem - i.e.

X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4996
X-RateLimit-Reset: 1372700873

The ETag and Last-Modified headers are also important when working with Github.

ETag: "644b5b0155e6404a9cc4bd9d8b1ae730"
Last-Modified: Thu, 05 Jul 2012 15:31:30 GMT

I guess this will break the world in terms of case statements with the signature changing from :

@type response :: {integer, any} | :jsx.json_term 

to :

@type response :: {integer, any,  HTTPoison.Response.t  } | 
{:ok, :jsx.json_term, HTTPoison.Base.headers }

There is also a Github Ratelimit API - https://developer.github.com/v3/rate_limit/ but hitting it all the time and dealing with the coordination is gonna be a pita when the info is already being returned in each individual request.

Any thoughts? I mean, in the worst case I can just do raw requests for everything but I'm sure anyone that hits github with a lot of requests is gonna have a similar need (they rate-limit very early, especially when un-authenticated)

Full disclosure, I'm writing a CI server.

bryanhuntesl avatar Nov 24 '17 16:11 bryanhuntesl

Quick experiment, changing the process_response implementation - 116 of 198 tests fail) - looks like it does break the world 😂

  @spec process_response(HTTPoison.Response.t) :: response
  def process_response(%HTTPoison.Response{status_code: 200, body: body, headers: headers}), do: {:ok, body, headers}
  def process_response(%HTTPoison.Response{status_code: status_code, body: body }), do: { status_code, body }

bryanhuntesl avatar Nov 24 '17 16:11 bryanhuntesl

@edgurgel - any suggestions to work around this feature? Tentacat 2.0? Fork it. Submit a massive p/r ?

bryanhuntesl avatar Nov 24 '17 19:11 bryanhuntesl

@bryanhuntesl, well Tentacat is not 1.0.0 yet so we can do breaking changes! I wonder if we should just always return the raw HTTPoison response as the third argument. It will avoid issues in the future and simplify some weird use cases. What do you think?

edgurgel avatar Nov 26 '17 19:11 edgurgel

Sounds good to me. I can't think of a better way to do it

On Sun, 26 Nov 2017 7:26 PM Eduardo Gurgel, [email protected] wrote:

@bryanhuntesl https://github.com/bryanhuntesl, well Tentacat is not 1.0.0 yet so we can do breaking changes! I wonder if we should just always return the raw HTTPoison response as the third argument. It will avoid issues in the future and simplify some weird use cases. What do you think?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/edgurgel/tentacat/issues/84#issuecomment-347031932, or mute the thread https://github.com/notifications/unsubscribe-auth/Aego9tYAC5fHQl9UwORTz-wJFrpZcgXsks5s6bt6gaJpZM4IeSCd .

bryanhuntesl avatar Nov 27 '17 09:11 bryanhuntesl

And then it's kinda of "easy" to build a module to check for the rate limiting based on the HTTPoison.Response? Something like: Tentacat.RateLimit.allowed?(response) and/or Tentacat.RateLimit.remaining(response)

edgurgel avatar Nov 27 '17 19:11 edgurgel

Nice. I'm thinking of a process with an ets table or a process per token. I'm working my way through the unit tests btw. Will create a branch and p/r for review soon.

bryanhuntesl avatar Nov 27 '17 20:11 bryanhuntesl

@mgwidmann - see https://github.com/edgurgel/tentacat/pull/132 - I'll share whatever I build on top of that - most likely a coordinator per token

bryanhuntesl avatar Nov 28 '17 22:11 bryanhuntesl