falcon-limiter 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/context/getting-started.md +116 -0
- data/context/index.yaml +16 -0
- data/context/long-tasks.md +160 -0
- data/lib/falcon/limiter/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +4 -4
- metadata.gz.sig +0 -0
- data/examples/load/config.ru +0 -25
- data/examples/load/falcon.rb +0 -19
- data/examples/load/readme.md +0 -87
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 19aed9c67b7fe7b68716eb8b4db0ffaa0424733a2e3e18abda65e367293fda17
|
4
|
+
data.tar.gz: 1eabdea4a1110e92a86a5d30bc1dba115cc10f7d33fb8ef545d3f5f6806eaac0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be434c0ba702339891fbf77bf78ce76610e5b833abf304ac7c65913aceac28a04a0ac4560d75e29c5866d8fa13598482b62aa949d9bef6d326eb62b2ccfcd1f7
|
7
|
+
data.tar.gz: 51344952a67ae841d4f3284fe084178ba6af764e482c7f16d730c5d22a3ffc64560af6010602c416427c8f42706e822b3228073ee84714f7508c07b787f2da18
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# Getting Started
|
2
|
+
|
3
|
+
This guide explains how to get started with `falcon-limiter` for advanced concurrency control and resource limiting in Falcon web applications.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add the gem to your project:
|
8
|
+
|
9
|
+
```bash
|
10
|
+
$ bundle add falcon-limiter
|
11
|
+
```
|
12
|
+
|
13
|
+
## Core Concepts
|
14
|
+
|
15
|
+
`falcon-limiter` has one main concept:
|
16
|
+
|
17
|
+
- {ruby Falcon::Limiter::LongTask} represents operations that take significant time but aren't CPU-intensive (like database queries or API calls).
|
18
|
+
|
19
|
+
When you start a Long Task, the server:
|
20
|
+
|
21
|
+
- Releases the connection token so new requests can be accepted.
|
22
|
+
- Continues processing your I/O operation in the background.
|
23
|
+
- Prevents too many I/O operations from running simultaneously.
|
24
|
+
- Maintains responsiveness for CPU-bound requests.
|
25
|
+
|
26
|
+
This means your server can handle many concurrent I/O operations without blocking quick CPU-bound requests.
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
The easiest way to get started is using the {ruby Falcon::Limiter::Environment} module:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
#!/usr/bin/env falcon-host
|
34
|
+
# frozen_string_literal: true
|
35
|
+
|
36
|
+
require "falcon/limiter/environment"
|
37
|
+
require "falcon/environment/rack"
|
38
|
+
|
39
|
+
service "myapp.localhost" do
|
40
|
+
include Falcon::Environment::Rack
|
41
|
+
include Falcon::Limiter::Environment
|
42
|
+
|
43
|
+
# If you use a custom endpoint, you need to use it with the limiter wrapper:
|
44
|
+
endpoint do
|
45
|
+
Async::HTTP::Endpoint.parse("http://localhost:9292").with(wrapper: limiter_wrapper)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
Then in your Rack application:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
# config.ru
|
54
|
+
require "falcon/limiter/long_task"
|
55
|
+
|
56
|
+
run do |env|
|
57
|
+
path = env["PATH_INFO"]
|
58
|
+
|
59
|
+
case path
|
60
|
+
when "/io"
|
61
|
+
# For I/O bound work, start a long task to release the connection token:
|
62
|
+
Falcon::Limiter::LongTask.current.start
|
63
|
+
|
64
|
+
# Long I/O operation (database query, external API call, etc.).
|
65
|
+
sleep(5) # Simulating I/O.
|
66
|
+
|
67
|
+
when "/cpu"
|
68
|
+
# For CPU bound work, keep the connection token.
|
69
|
+
# This ensures only limited CPU work happens concurrently.
|
70
|
+
sleep(5) # Simulating CPU work.
|
71
|
+
end
|
72
|
+
|
73
|
+
[200, {"content-type" => "text/plain"}, ["Request completed"]]
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
### Understanding the Behavior
|
78
|
+
|
79
|
+
With the default configuration:
|
80
|
+
|
81
|
+
- **Connection limit**: 1 (only 1 connection accepted at a time)
|
82
|
+
- **Long task limit**: 10 (up to 10 concurrent I/O operations)
|
83
|
+
|
84
|
+
This means:
|
85
|
+
|
86
|
+
1. **CPU-bound requests** (`/cpu`) will be processed sequentially - only one at a time
|
87
|
+
2. **I/O-bound requests** (`/io`) can run concurrently (up to 10) because they release their connection token
|
88
|
+
3. **Mixed workloads** work optimally - I/O requests don't block CPU requests from being accepted
|
89
|
+
|
90
|
+
### Configuration
|
91
|
+
|
92
|
+
You can customize the limits by overriding the environment methods:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
service "myapp.localhost" do
|
96
|
+
include Falcon::Environment::Rack
|
97
|
+
include Falcon::Limiter::Environment
|
98
|
+
|
99
|
+
# Override default limits
|
100
|
+
def limiter_maximum_connections
|
101
|
+
2 # Allow 2 concurrent connections
|
102
|
+
end
|
103
|
+
|
104
|
+
def limiter_maximum_long_tasks
|
105
|
+
8 # Allow 8 concurrent I/O operations
|
106
|
+
end
|
107
|
+
|
108
|
+
def limiter_start_delay
|
109
|
+
0.05 # Reduce delay before starting long tasks
|
110
|
+
end
|
111
|
+
|
112
|
+
endpoint do
|
113
|
+
Async::HTTP::Endpoint.parse("http://localhost:9292").with(wrapper: limiter_wrapper)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
```
|
data/context/index.yaml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Automatically generated context index for Utopia::Project guides.
|
2
|
+
# Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`.
|
3
|
+
---
|
4
|
+
description: Advanced concurrency control and resource limiting for Falcon web server.
|
5
|
+
metadata:
|
6
|
+
documentation_uri: https://socketry.github.io/falcon-limiter/
|
7
|
+
source_code_uri: https://github.com/socketry/falcon-limiter.git
|
8
|
+
files:
|
9
|
+
- path: getting-started.md
|
10
|
+
title: Getting Started
|
11
|
+
description: This guide explains how to get started with `falcon-limiter` for advanced
|
12
|
+
concurrency control and resource limiting in Falcon web applications.
|
13
|
+
- path: long-tasks.md
|
14
|
+
title: Long Tasks
|
15
|
+
description: This guide explains how to use <code class="language-ruby">Falcon::Limiter::LongTask</code>
|
16
|
+
to effectively manage I/O vs CPU bound workloads in your Falcon applications.
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# Long Tasks
|
2
|
+
|
3
|
+
This guide explains how to use {ruby Falcon::Limiter::LongTask} to effectively manage I/O vs CPU bound workloads in your Falcon applications.
|
4
|
+
|
5
|
+
## Understanding Long Tasks
|
6
|
+
|
7
|
+
A **long task** in `falcon-limiter` is any operation that takes significant time (1+ seconds) and isn't CPU-bound, typically I/O operations like:
|
8
|
+
|
9
|
+
- Database queries.
|
10
|
+
- External API calls.
|
11
|
+
- File system operations.
|
12
|
+
- Network requests.
|
13
|
+
- Message queue operations.
|
14
|
+
|
15
|
+
The key insight is that during I/O operations, your application is waiting rather than consuming CPU resources. Long tasks allow the server to:
|
16
|
+
|
17
|
+
1. **Release the connection token** during I/O, allowing other requests to be accepted.
|
18
|
+
2. **Maintain responsiveness** by not blocking CPU-bound requests.
|
19
|
+
3. **Optimize resource utilization** by running multiple I/O operations concurrently.
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
When using {ruby Falcon::Limiter::Environment}, a long task is automatically created for each request:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
# In your Rack application
|
27
|
+
run do |env|
|
28
|
+
# Long task is available via Falcon::Limiter::LongTask.current
|
29
|
+
long_task = Falcon::Limiter::LongTask.current
|
30
|
+
|
31
|
+
if long_task
|
32
|
+
# Start the long task for I/O operations
|
33
|
+
long_task.start
|
34
|
+
|
35
|
+
# Perform I/O operation
|
36
|
+
database_query
|
37
|
+
|
38
|
+
# Long task automatically stops when request completes
|
39
|
+
end
|
40
|
+
|
41
|
+
[200, {}, ["Response"]]
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
### Custom Delays
|
46
|
+
|
47
|
+
A delay is used to avoid starting a long task until we know that it's likely to be slow.
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
run do |env|
|
51
|
+
path = env["PATH_INFO"]
|
52
|
+
|
53
|
+
case path
|
54
|
+
when "/io"
|
55
|
+
# Start long task with default delay (0.1 seconds):
|
56
|
+
Falcon::Limiter::LongTask.current.start
|
57
|
+
|
58
|
+
# Perform I/O operation:
|
59
|
+
external_api_call
|
60
|
+
|
61
|
+
when "/io-immediate"
|
62
|
+
# Start immediately without delay:
|
63
|
+
Falcon::Limiter::LongTask.current.start(delay: false)
|
64
|
+
|
65
|
+
# Perform I/O operation:
|
66
|
+
database_query
|
67
|
+
|
68
|
+
when "/io-custom-delay"
|
69
|
+
# Start with custom delay:
|
70
|
+
Falcon::Limiter::LongTask.current.start(delay: 0.5)
|
71
|
+
|
72
|
+
# Perform I/O operation:
|
73
|
+
slow_file_operation
|
74
|
+
|
75
|
+
when "/cpu"
|
76
|
+
# Don't start long task for CPU-bound work:
|
77
|
+
cpu_intensive_calculation
|
78
|
+
end
|
79
|
+
|
80
|
+
[200, {}, ["Completed"]]
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
### Block-based Long Tasks
|
85
|
+
|
86
|
+
You can use long tasks with blocks for automatic cleanup:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
require "net/http"
|
90
|
+
|
91
|
+
run do |env|
|
92
|
+
path = env["PATH_INFO"]
|
93
|
+
|
94
|
+
case path
|
95
|
+
when "/api/weather"
|
96
|
+
# Use block-based long task for automatic cleanup:
|
97
|
+
response = Falcon::Limiter::LongTask.current.start do |long_task|
|
98
|
+
# Make external API call:
|
99
|
+
uri = URI("https://api.openweathermap.org/data/2.5/weather?q=London")
|
100
|
+
Net::HTTP.get_response(uri)
|
101
|
+
# Automatic LongTask#stop when block exits.
|
102
|
+
end
|
103
|
+
|
104
|
+
# CPU work happens outside the long task:
|
105
|
+
result = JSON.parse(response.body)
|
106
|
+
[200, {"content-type" => "application/json"}, [result.to_json]]
|
107
|
+
|
108
|
+
when "/api/database"
|
109
|
+
# Multiple I/O operations in one long task:
|
110
|
+
users, enriched_data = Falcon::Limiter::LongTask.current.start do
|
111
|
+
# Database query
|
112
|
+
users = database.query("SELECT * FROM users WHERE active = true")
|
113
|
+
|
114
|
+
# External service call:
|
115
|
+
uri = URI("https://api.example.com/enrich")
|
116
|
+
enriched_data = Net::HTTP.post_form(uri, {user_ids: users.map(&:id)})
|
117
|
+
|
118
|
+
[users, enriched_data]
|
119
|
+
end
|
120
|
+
|
121
|
+
# CPU work happens outside the long task:
|
122
|
+
parsed_data = JSON.parse(enriched_data.body)
|
123
|
+
result = users.zip(parsed_data)
|
124
|
+
[200, {"content-type" => "application/json"}, [result.to_json]]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
129
|
+
The block form ensures the long task is properly stopped even if an exception occurs. They can also be nested.
|
130
|
+
|
131
|
+
## Long Task Lifecycle
|
132
|
+
|
133
|
+
### 1. Creation
|
134
|
+
|
135
|
+
Long tasks are created automatically by {ruby Falcon::Limiter::Middleware} for each request when long task support is enabled.
|
136
|
+
|
137
|
+
### 2. Starting
|
138
|
+
|
139
|
+
When you call `start()`, the long task:
|
140
|
+
|
141
|
+
- Waits for the configured delay (default: 0.1 seconds).
|
142
|
+
- Acquires a long task token from the limiter.
|
143
|
+
- Releases the connection token, allowing new connections.
|
144
|
+
- Marks the connection as non-persistent to prevent token leakage
|
145
|
+
|
146
|
+
### 3. Execution
|
147
|
+
|
148
|
+
During long task execution:
|
149
|
+
|
150
|
+
- The connection token is released, so new requests can be accepted.
|
151
|
+
- The long task token prevents too many I/O operations from running concurrently.
|
152
|
+
- Your I/O operation runs normally.
|
153
|
+
|
154
|
+
### 4. Completion
|
155
|
+
|
156
|
+
When the request completes, the long task automatically:
|
157
|
+
|
158
|
+
- Releases the long task token.
|
159
|
+
- Re-acquires the connection token with high priority.
|
160
|
+
- Cleans up resources.
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: falcon-limiter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josh Teeter
|
@@ -59,9 +59,9 @@ executables: []
|
|
59
59
|
extensions: []
|
60
60
|
extra_rdoc_files: []
|
61
61
|
files:
|
62
|
-
-
|
63
|
-
-
|
64
|
-
-
|
62
|
+
- context/getting-started.md
|
63
|
+
- context/index.yaml
|
64
|
+
- context/long-tasks.md
|
65
65
|
- lib/falcon/limiter.rb
|
66
66
|
- lib/falcon/limiter/environment.rb
|
67
67
|
- lib/falcon/limiter/long_task.rb
|
metadata.gz.sig
CHANGED
Binary file
|
data/examples/load/config.ru
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "console"
|
4
|
-
require "falcon/limiter/long_task"
|
5
|
-
|
6
|
-
run do |env|
|
7
|
-
path = env["PATH_INFO"]
|
8
|
-
|
9
|
-
case path
|
10
|
-
when "/io"
|
11
|
-
Console.info(self, "Starting \"I/O intensive\" task...")
|
12
|
-
Falcon::Limiter::LongTask.current.start(delay: 0.1)
|
13
|
-
sleep(10)
|
14
|
-
when "/cpu"
|
15
|
-
Console.info(self, "Starting \"CPU intensive\" task...")
|
16
|
-
sleep(10)
|
17
|
-
else
|
18
|
-
Console.info(self, "Unknown path: #{path}")
|
19
|
-
return [404, {"content-type" => "text/plain"}, ["Not Found"]]
|
20
|
-
end
|
21
|
-
|
22
|
-
Console.info(self, "Request completed");
|
23
|
-
|
24
|
-
[200, {"content-type" => "text/plain"}, ["Hello from Falcon Limiter!"]]
|
25
|
-
end
|
data/examples/load/falcon.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
#!/usr/bin/env falcon-host
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
# Released under the MIT License.
|
5
|
-
# Copyright, 2025, by Samuel Williams.
|
6
|
-
|
7
|
-
require "falcon/limiter/environment"
|
8
|
-
require "falcon/environment/rack"
|
9
|
-
|
10
|
-
service "hello.localhost" do
|
11
|
-
include Falcon::Environment::Rack
|
12
|
-
include Falcon::Limiter::Environment
|
13
|
-
|
14
|
-
endpoint do
|
15
|
-
Async::HTTP::Endpoint.parse("http://localhost:9292").with(wrapper: limiter_wrapper)
|
16
|
-
end
|
17
|
-
|
18
|
-
count {1}
|
19
|
-
end
|
data/examples/load/readme.md
DELETED
@@ -1,87 +0,0 @@
|
|
1
|
-
# Load Example
|
2
|
-
|
3
|
-
This example provides pseudo "io"-bound and "cpu"-bound endpoints for testing Falcon Limiter.
|
4
|
-
|
5
|
-
## Usage
|
6
|
-
|
7
|
-
First, start the server:
|
8
|
-
|
9
|
-
```bash
|
10
|
-
$ bundle exec ./falcon.rb
|
11
|
-
0.0s info: Falcon::Command::Host [oid=0x448] [ec=0x450] [pid=8064] [2025-09-20 16:20:08 +1200]
|
12
|
-
| Falcon Host v0.52.3 taking flight!
|
13
|
-
| - Configuration: ././falcon.rb
|
14
|
-
| - To terminate: Ctrl-C or kill 8064
|
15
|
-
| - To reload: kill -HUP 8064
|
16
|
-
0.08s info: Async::Container::Notify::Console [oid=0x510] [ec=0x450] [pid=8064] [2025-09-20 16:20:08 +1200]
|
17
|
-
| {status: "Initializing controller..."}
|
18
|
-
0.08s info: Falcon::Service::Server [oid=0x520] [ec=0x450] [pid=8064] [2025-09-20 16:20:08 +1200]
|
19
|
-
| Starting hello.localhost on #<Async::HTTP::Endpoint http://localhost:9292ps/ {wrapper: #<Falcon::Limiter::Wrapper:0x0000000104efe0e8 @limiter=#<Async::Limiter::Queued:0x00000001086ffcd0 @timing=Async::Limiter::Timing::None, @parent=nil, @tags=nil, @mutex=#<Thread::Mutex:0x0000000104efe188>, @queue=#<Async::PriorityQueue:0x00000001086ffd20 @items=[true], @closed=false, @parent=nil, @waiting=#<IO::Event::PriorityHeap:0x0000000104efe4a8 @contents=[]>, @sequence=0, @mutex=#<Thread::Mutex:0x0000000104efe3e0>>>>}>
|
20
|
-
0.08s info: Async::Service::Controller [oid=0x528] [ec=0x450] [pid=8064] [2025-09-20 16:20:08 +1200]
|
21
|
-
| Controller starting...
|
22
|
-
0.08s info: Async::Container::Notify::Console [oid=0x510] [ec=0x450] [pid=8064] [2025-09-20 16:20:08 +1200]
|
23
|
-
| {ready: true, size: 1}
|
24
|
-
0.08s info: Async::Service::Controller [oid=0x528] [ec=0x450] [pid=8064] [2025-09-20 16:20:08 +1200]
|
25
|
-
| Controller started...
|
26
|
-
```
|
27
|
-
|
28
|
-
Note that the server is starting with a wrapper `Falcon::Limiter::Wrapper` which provides connection limiting, and with only a single worker (for the sake of making testing predictable).
|
29
|
-
|
30
|
-
## "CPU"-bound Work
|
31
|
-
|
32
|
-
Make several requests to the `/cpu` path:
|
33
|
-
|
34
|
-
```bash
|
35
|
-
# Start multiple requests in background
|
36
|
-
$ curl http://localhost:9292/cpu &
|
37
|
-
$ curl http://localhost:9292/cpu &
|
38
|
-
$ curl http://localhost:9292/cpu &
|
39
|
-
|
40
|
-
$ curl -v http://localhost:9292/cpu
|
41
|
-
```
|
42
|
-
|
43
|
-
You will note that these will be sequential as the connection limiter is limited to 1 connection at a time.
|
44
|
-
|
45
|
-
## "IO"-bound Work
|
46
|
-
|
47
|
-
Make several requests to the `/io` path:
|
48
|
-
|
49
|
-
```bash
|
50
|
-
# These will run concurrently (up to the long task limit)
|
51
|
-
$ curl http://localhost:9292/io &
|
52
|
-
$ curl http://localhost:9292/io &
|
53
|
-
$ curl http://localhost:9292/io &
|
54
|
-
$ curl http://localhost:9292/io &
|
55
|
-
|
56
|
-
$ curl -v http://localhost:9292/io
|
57
|
-
```
|
58
|
-
|
59
|
-
You will note that these will be concurrent, as the connection limiter is released once `LongTask.current.start` is invoked.
|
60
|
-
|
61
|
-
Also note that in order to prevent persistent connections from overloading the limiter, once a connection handles an "IO"-bound request, it will be marked as non-persistent (`connection: close`). This prevents us from having several "IO"-bound requests and a "CPU"-bound request from exceeding the limit on "CPU"-bound requests, by preventing a connection that was handling an "IO"-bound request from submitting a "CPU"-bound request (or otherwise hanging while waiting on the connection limiter).
|
62
|
-
|
63
|
-
## Mixed Workload Testing
|
64
|
-
|
65
|
-
In addition, if you perform a "CPU"-bound request after starting several "IO"-bound requests, a single "CPU"-bound request will be allowed, but until it completes, no further connections will be accepted.
|
66
|
-
|
67
|
-
To see the limiter behavior with mixed workloads:
|
68
|
-
|
69
|
-
```bash
|
70
|
-
# Start several I/O requests
|
71
|
-
$ curl http://localhost:9292/io &
|
72
|
-
$ curl http://localhost:9292/io &
|
73
|
-
$ curl http://localhost:9292/io &
|
74
|
-
|
75
|
-
# Then try a CPU request (should be queued until I/O requests complete)
|
76
|
-
$ curl http://localhost:9292/cpu
|
77
|
-
```
|
78
|
-
|
79
|
-
## Configuration
|
80
|
-
|
81
|
-
The example is configured with:
|
82
|
-
- **Connection limit**: 1 (only 1 connection accepted at a time).
|
83
|
-
- **Long task limit**: 4 (up to 4 concurrent I/O operations).
|
84
|
-
- **Start delay**: 0.1 seconds (delay before releasing connection token).
|
85
|
-
- **Workers**: 1 (single process for predictable behavior).
|
86
|
-
|
87
|
-
You can modify these values in `falcon.rb` by overriding the environment methods.
|