Skip to content

Commit 35fd837

Browse files
committed
Add naked Block
for blocking for an arbitrary timespan
1 parent f900938 commit 35fd837

File tree

6 files changed

+72
-12
lines changed

6 files changed

+72
-12
lines changed

CHANGELOG.md

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
1-
## [0.5.0] - 2024-02-11
1+
## 0.6.0
2+
3+
- Add `Pecorino::Block` for setting blocks directly. These are available both to `Throttle` with the same key and on their own. This can be used to set arbitrary blocks without having to configure a `Throttle` first.
4+
5+
## 0.5.0
26

37
- Add `CachedThrottle` for caching the throttle blocks. This allows protection to the database when the throttle is in a blocked state.
48
- Add `Throttle#throttled` for silencing alerts
59
- **BREAKING CHANGE** Remove `Throttle::State#retry_after`, because there is no reasonable value for that member if the throttle is not in the "blocked" state
610
- Allow accessing `Throttle::State` from the `Throttled` exception so that the blocked throttle state can be cached downstream (in Rails cache, for example)
711
- Make `Throttle#request!` return the new state if there was no exception raised
812

9-
## [0.4.1] - 2024-02-11
13+
## 0.4.1
1014

1115
- Make sure Pecorino works on Ruby 2.7 as well by removing 3.x-exclusive syntax
1216

13-
## [0.4.0] - 2024-01-22
17+
## 0.4.0
1418

1519
- Use Bucket#connditional_fillup inside Throttle and throttle only when the capacity _would_ be exceeded, as opposed
1620
to throttling when capacity has already been exceeded. This allows for finer-grained throttles such as
@@ -21,17 +25,17 @@
2125
- Allow "conditional fillup" - only add tokens to the leaky bucket if the bucket has enough space.
2226
- Fix `over_time` leading to incorrect `leak_rate`. The divider/divisor were swapped, leading to the inverse leak rate getting computed.
2327

24-
## [0.3.0] - 2024-01-18
28+
## 0.3.0
2529

2630
- Allow `over_time` in addition to `leak_rate`, which is a more intuitive parameter to tweak
2731
- Set default `block_for` to the time it takes the bucket to leak out completely instead of 30 seconds
2832

29-
## [0.2.0] - 2024-01-09
33+
## 0.2.0
3034

3135
- [Add support for SQLite](https://github.com/cheddar-me/pecorino/pull/9)
3236
- [Use comparisons in SQL to determine whether the leaky bucket did overflow](https://github.com/cheddar-me/pecorino/pull/8)
3337
- [Change the way Structs are defined to appease Tapioca/Sorbet](https://github.com/cheddar-me/pecorino/pull/6)
3438

35-
## [0.1.0] - 2023-10-30
39+
## 0.1.0
3640

3741
- Initial release

lib/pecorino.rb

+4-3
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44
require "active_record/sanitization"
55

66
require_relative "pecorino/version"
7-
require_relative "pecorino/leaky_bucket"
8-
require_relative "pecorino/throttle"
97
require_relative "pecorino/railtie" if defined?(Rails::Railtie)
10-
require_relative "pecorino/cached_throttle"
118

129
module Pecorino
1310
autoload :Postgres, "pecorino/postgres"
1411
autoload :Sqlite, "pecorino/sqlite"
12+
autoload :LeakyBucket, "pecorino/leaky_bucket"
13+
autoload :Block, "pecorino/block"
14+
autoload :Throttle, "pecorino/throttle"
15+
autoload :CachedThrottle, "pecorino/cached_throttle"
1516

1617
# Deletes stale leaky buckets and blocks which have expired. Run this method regularly to
1718
# avoid accumulating too many unused rows in your tables.

lib/pecorino/block.rb

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# frozen_string_literal: true
2+
3+
# Provides access to Pecorino blocks - same blocks which get set when a throttle triggers. The blocks
4+
# are just keys in the data store which have an expiry value. This can be useful if you want to restrict
5+
# access to a resource for an arbitrary timespan.
6+
class Pecorino::Block
7+
# Sets a block for the given key. The block will also be seen by the Pecorino::Throttle with the same key
8+
#
9+
# @param key[String] the key to set the block for
10+
# @param block_for[Float] the number of seconds or a time interval to block for
11+
# @return [Time] the time when the block will be released
12+
def self.set!(key:, block_for:)
13+
Pecorino.adapter.set_block(key: key, block_for: block_for)
14+
Time.now + block_for
15+
end
16+
17+
# Returns the time until a certain block is in effect
18+
#
19+
# @return [Time,nil] the time when the block will be released
20+
def self.blocked_until(key:)
21+
t = Pecorino.adapter.blocked_until(key: key)
22+
(t && t > Time.now) ? t : nil
23+
end
24+
end

lib/pecorino/throttle.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ def request!(n = 1)
156156
#
157157
# @return [State] the state of the throttle after filling up the leaky bucket / trying to pass the block
158158
def request(n = 1)
159-
existing_blocked_until = Pecorino.adapter.blocked_until(key: @key)
159+
existing_blocked_until = Pecorino::Block.blocked_until(key: @key)
160160
return State.new(existing_blocked_until.utc) if existing_blocked_until
161161

162162
# Topup the leaky bucket, and if the topup gets rejected - block the caller
@@ -165,7 +165,7 @@ def request(n = 1)
165165
State.new(nil)
166166
else
167167
# and set the block if the fillup was rejected
168-
fresh_blocked_until = Pecorino.adapter.set_block(key: @key, block_for: @block_for)
168+
fresh_blocked_until = Pecorino::Block.set!(key: @key, block_for: @block_for)
169169
State.new(fresh_blocked_until.utc)
170170
end
171171
end

lib/pecorino/version.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# frozen_string_literal: true
22

33
module Pecorino
4-
VERSION = "0.5.0"
4+
VERSION = "0.6.0"
55
end

test/block_test.rb

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
5+
class BlockTest < ActiveSupport::TestCase
6+
def setup
7+
create_postgres_database
8+
end
9+
10+
def teardown
11+
drop_postgres_database
12+
end
13+
14+
test "sets a block" do
15+
k = Base64.strict_encode64(Random.bytes(4))
16+
assert_nil Pecorino::Block.blocked_until(key: k)
17+
assert Pecorino::Block.set!(key: k, block_for: 30.minutes)
18+
19+
blocked_until = Pecorino::Block.blocked_until(key: k)
20+
assert_in_delta Time.now + 30.minutes, blocked_until, 10
21+
end
22+
23+
test "does not return a block which has lapsed" do
24+
k = Base64.strict_encode64(Random.bytes(4))
25+
assert_nil Pecorino::Block.blocked_until(key: k)
26+
assert Pecorino::Block.set!(key: k, block_for: -30.minutes)
27+
28+
blocked_until = Pecorino::Block.blocked_until(key: k)
29+
assert_nil blocked_until
30+
end
31+
end

0 commit comments

Comments
 (0)