aegis.cpp
 All Classes Functions Variables Typedefs Enumerations Enumerator Friends Pages
bucket.hpp
1 //
2 // bucket.hpp
3 // **********
4 //
5 // Copyright (c) 2019 Sharon W (sharon at aegis dot gg)
6 //
7 // Distributed under the MIT License. (See accompanying file LICENSE)
8 //
9 
10 #pragma once
11 
12 #include "aegis/config.hpp"
13 #include "aegis/rest/rest_controller.hpp"
14 #include "aegis/snowflake.hpp"
15 #include <mutex>
16 #include <future>
17 #include <chrono>
18 #include <queue>
19 #include <atomic>
20 #include <spdlog/spdlog.h>
21 
22 namespace aegis
23 {
24 
25 using rest_call = std::function<rest::rest_reply(rest::request_params)>;
26 
27 namespace ratelimit
28 {
29 
30 using namespace std::chrono;
31 
36 enum bucket_type
37 {
38  Guild = 0,
39  Channel = 1,
40  Emoji = 2
41 };
42 
44 
49 class bucket
50 {
51 public:
55  bucket(rest_call & call, asio::io_context & _io_context, std::atomic<int64_t> & global_limit)
56  : limit(0)
57  , remaining(1)
58  , reset(0)
59  , _call(call)
60  , _io_context(_io_context)
61  , _global_limit(global_limit)
62  {
63 
64  }
65 
66  std::atomic<int64_t> limit;
67  std::atomic<int64_t> remaining;
68  std::atomic<int64_t> reset;
70 
74  bool is_global() const noexcept
75  {
76  return _global_limit > 0;
77  }
78 
79 
81 
84  bool can_perform() const noexcept
85  {
86  if (ignore_rates)
87  return true;
88  if (limit == 0)
89  return true;
90  if (remaining > 0)
91  return true;
92  int64_t time = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
93  if (time < (reset/* + _time_delay*/))
94  return false;
95  return true;
96  }
97 
99  {
100  std::lock_guard<std::mutex> lock(m);
101  while (!can_perform())
102  {
103  //TODO: find a better solution - wrap asio execution handling, poll ratelimit object to track ordering and execution
104  // not an ideal scenario, but by current design rescheduling a message that would be ratelimited
105  // would cause out of order messages
106  auto waitfor = milliseconds((reset.load(std::memory_order_relaxed)
107  - std::chrono::duration_cast<milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count())/* + _time_delay*/);
108  spdlog::get("aegis")->debug("Ratelimit almost hit: {}({}) - waiting {}ms", rest::rest_controller::get_method(params.method), params.path, waitfor.count());
109  std::this_thread::sleep_for(waitfor);
110  }
111  rest::rest_reply reply(_call(params));
112  auto _now = std::chrono::duration_cast<milliseconds>(std::chrono::system_clock::now().time_since_epoch());
113  if (reply.reply_code == 429)
114  {
115  if (reset_bypass)
116  {
117  spdlog::get("aegis")->warn("Ratelimit hit - retrying in {}ms...", reset_bypass);
118  std::this_thread::sleep_for(milliseconds(reset_bypass));
119  }
120  else
121  {
122  spdlog::get("aegis")->warn("Ratelimit hit - retrying in {}s...", reply.retry / 1000);
123  std::this_thread::sleep_for(milliseconds(reply.retry));
124  }
125  reply = _call(params);
126  if (reply.reply_code == 429)
127  spdlog::get("aegis")->error("Ratelimit hit twice. Giving up.");
128  }
129 
130  limit.store(reply.limit, std::memory_order_relaxed);
131  remaining.store(reply.remaining, std::memory_order_relaxed);
132  auto http_date = std::chrono::duration_cast<milliseconds>(reply.date.time_since_epoch());
133  if (reset_bypass)
134  reset.store((_now + milliseconds(reset_bypass)).count(), std::memory_order_relaxed);
135  else
136  {
137  reset.store(reply.reset*1000, std::memory_order_relaxed);
138  _time_delay = (http_date - _now).count();
139  }
140  return reply;
141  }
142 
143  bool ignore_rates = false;
144  std::mutex m;
145  rest_call & _call;
146  std::queue<std::tuple<std::string, std::string, std::string, std::function<void(rest::rest_reply)>>> _queue;
147  int32_t reset_bypass = 0;
148 
149 private:
150  asio::io_context & _io_context;
151  std::atomic<int64_t> & _global_limit;
152  std::atomic<int64_t> _time_delay;
153 };
154 
155 }
156 
157 }
REST responses with error_code for possible exception throwing.
Definition: rest_reply.hpp:98
Definition: rest_controller.hpp:43
bool can_perform() const noexcept
Check if bucket can send a message without hitting the ratelimit.
Definition: bucket.hpp:84
std::atomic< int64_t > remaining
Definition: bucket.hpp:67
std::atomic< int64_t > reset
Definition: bucket.hpp:68
std::atomic< int64_t > limit
Definition: bucket.hpp:66
bool is_global() const noexcept
Check if globally ratelimited.
Definition: bucket.hpp:74
bucket(rest_call &call, asio::io_context &_io_context, std::atomic< int64_t > &global_limit)
Definition: bucket.hpp:55
Buckets store ratelimit data per major parameter.
Definition: bucket.hpp:49