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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 61d3807604e041bf6affc784d16a9dc6003016e0f716081f2fb85efd26f17f12
4
- data.tar.gz: 9b1ca7661095b7f609b5cd027975e52bbd9b87951d09d2c6b947c16503fa9396
3
+ metadata.gz: 19aed9c67b7fe7b68716eb8b4db0ffaa0424733a2e3e18abda65e367293fda17
4
+ data.tar.gz: 1eabdea4a1110e92a86a5d30bc1dba115cc10f7d33fb8ef545d3f5f6806eaac0
5
5
  SHA512:
6
- metadata.gz: d09553c8da363d761041e05850e6e38a6f562eb00108ea6944f6724e4546fc114332367c73ee1e87ff5445ed4fbc0a8d3519fbf2a42a0bc7806ff8088e73af5d
7
- data.tar.gz: c46e60261492d8f0da23738dffe8fbdf20844e999acf766ee3e93cf3f3a2b351e8a9fd804249dc12284b987b35f843644d977db8536156be15e082c3277bbcff
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
+ ```
@@ -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.
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Falcon
7
7
  module Limiter
8
- VERSION = "0.1.1"
8
+ VERSION = "0.1.2"
9
9
  end
10
10
  end
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.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
- - examples/load/config.ru
63
- - examples/load/falcon.rb
64
- - examples/load/readme.md
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
@@ -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
@@ -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
@@ -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.