falcon-limiter 0.1.0 → 0.1.1
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/examples/load/config.ru +7 -1
- data/examples/load/falcon.rb +0 -0
- data/examples/load/readme.md +87 -0
- data/lib/falcon/limiter/environment.rb +3 -2
- data/lib/falcon/limiter/long_task.rb +8 -6
- data/lib/falcon/limiter/middleware.rb +2 -2
- data/lib/falcon/limiter/socket.rb +1 -9
- data/lib/falcon/limiter/version.rb +1 -1
- data/lib/falcon/limiter/wrapper.rb +4 -0
- data/readme.md +21 -126
- data/releases.md +2 -0
- data.tar.gz.sig +0 -0
- metadata +2 -7
- metadata.gz.sig +0 -0
- data/examples/basic_limiting.rb +0 -40
- data/examples/environment_usage.rb +0 -77
- data/examples/falcon_environment.rb +0 -55
- data/examples/limiter/README.md +0 -95
- data/examples/limiter/config.ru +0 -50
- data/examples/limiter/falcon.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 61d3807604e041bf6affc784d16a9dc6003016e0f716081f2fb85efd26f17f12
|
4
|
+
data.tar.gz: 9b1ca7661095b7f609b5cd027975e52bbd9b87951d09d2c6b947c16503fa9396
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d09553c8da363d761041e05850e6e38a6f562eb00108ea6944f6724e4546fc114332367c73ee1e87ff5445ed4fbc0a8d3519fbf2a42a0bc7806ff8088e73af5d
|
7
|
+
data.tar.gz: c46e60261492d8f0da23738dffe8fbdf20844e999acf766ee3e93cf3f3a2b351e8a9fd804249dc12284b987b35f843644d977db8536156be15e082c3277bbcff
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/examples/load/config.ru
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "console"
|
3
4
|
require "falcon/limiter/long_task"
|
4
5
|
|
5
6
|
run do |env|
|
@@ -8,12 +9,17 @@ run do |env|
|
|
8
9
|
case path
|
9
10
|
when "/io"
|
10
11
|
Console.info(self, "Starting \"I/O intensive\" task...")
|
11
|
-
Falcon::Limiter::LongTask.current.start
|
12
|
+
Falcon::Limiter::LongTask.current.start(delay: 0.1)
|
12
13
|
sleep(10)
|
13
14
|
when "/cpu"
|
14
15
|
Console.info(self, "Starting \"CPU intensive\" task...")
|
15
16
|
sleep(10)
|
17
|
+
else
|
18
|
+
Console.info(self, "Unknown path: #{path}")
|
19
|
+
return [404, {"content-type" => "text/plain"}, ["Not Found"]]
|
16
20
|
end
|
17
21
|
|
22
|
+
Console.info(self, "Request completed");
|
23
|
+
|
18
24
|
[200, {"content-type" => "text/plain"}, ["Hello from Falcon Limiter!"]]
|
19
25
|
end
|
data/examples/load/falcon.rb
CHANGED
File without changes
|
@@ -0,0 +1,87 @@
|
|
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.
|
@@ -15,11 +15,11 @@ module Falcon
|
|
15
15
|
# Provides simple, declarative configuration for concurrency limiting.
|
16
16
|
# Override these methods in your service to customize behavior.
|
17
17
|
module Environment
|
18
|
-
# Maximum number of concurrent long tasks (default:
|
18
|
+
# Maximum number of concurrent long tasks (default: 10).
|
19
19
|
# If this is nil or non-positive, long task support will be disabled.
|
20
20
|
# @returns [Integer] The maximum number of concurrent long tasks.
|
21
21
|
def limiter_maximum_long_tasks
|
22
|
-
|
22
|
+
10
|
23
23
|
end
|
24
24
|
|
25
25
|
# @returns [Integer] The maximum number of concurrent connection accepts.
|
@@ -66,6 +66,7 @@ module Falcon
|
|
66
66
|
limiter_middleware(super)
|
67
67
|
end
|
68
68
|
|
69
|
+
# @returns [Falcon::Limiter::Wrapper] The wrapper for the connection limiter.
|
69
70
|
def limiter_wrapper
|
70
71
|
Wrapper.new(connection_limiter)
|
71
72
|
end
|
@@ -65,12 +65,18 @@ module Falcon
|
|
65
65
|
@delayed_start_task = nil
|
66
66
|
end
|
67
67
|
|
68
|
-
# Check if the long task has been started.
|
69
|
-
# @returns [Boolean]
|
68
|
+
# Check if the long task has been started, but not necessarily acquired (e.g. if there was a delay).
|
69
|
+
# @returns [Boolean] If the long task has been started.
|
70
70
|
def started?
|
71
71
|
@token.acquired? || @delayed_start_task
|
72
72
|
end
|
73
73
|
|
74
|
+
# Check if the long task has been acquired.
|
75
|
+
# @returns [Boolean] If the long task token has been acquired.
|
76
|
+
def acquired?
|
77
|
+
@token.acquired?
|
78
|
+
end
|
79
|
+
|
74
80
|
# Start the long task, optionally with a delay to avoid overhead for short operations
|
75
81
|
def start(delay: @start_delay)
|
76
82
|
# If already started, nothing to do:
|
@@ -88,10 +94,6 @@ module Falcon
|
|
88
94
|
delay = nil
|
89
95
|
end
|
90
96
|
|
91
|
-
def acquired?
|
92
|
-
@token.acquired?
|
93
|
-
end
|
94
|
-
|
95
97
|
# Otherwise, start the long task:
|
96
98
|
if delay&.positive?
|
97
99
|
# Wait specified delay before starting the long task:
|
@@ -16,9 +16,9 @@ module Falcon
|
|
16
16
|
# Initialize the middleware with limiting configuration.
|
17
17
|
# @parameter delegate [Object] The next middleware in the chain to call.
|
18
18
|
# @parameter connection_limiter [Async::Limiter] Connection limiter instance for managing accepts.
|
19
|
-
# @parameter maximum_long_tasks [Integer] Maximum number of concurrent long tasks (default:
|
19
|
+
# @parameter maximum_long_tasks [Integer] Maximum number of concurrent long tasks (default: 10).
|
20
20
|
# @parameter start_delay [Float] Delay in seconds before starting long tasks (default: 0.1).
|
21
|
-
def initialize(delegate, connection_limiter:, maximum_long_tasks:
|
21
|
+
def initialize(delegate, connection_limiter:, maximum_long_tasks: 10, start_delay: 0.1)
|
22
22
|
super(delegate)
|
23
23
|
|
24
24
|
@maximum_long_tasks = maximum_long_tasks
|
@@ -49,17 +49,9 @@ module Falcon
|
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
|
-
# Support for method_missing delegation by checking delegate's methods.
|
53
|
-
# @parameter method [Symbol] The method name to check.
|
54
|
-
# @parameter include_private [Boolean] Whether to include private methods (default: false).
|
55
|
-
# @returns [Boolean] True if the delegate responds to the method.
|
56
|
-
def respond_to_missing?(method, include_private = false)
|
57
|
-
@delegate.respond_to?(method, include_private) || super
|
58
|
-
end
|
59
|
-
|
60
52
|
# Forward common inspection methods
|
61
53
|
def inspect
|
62
|
-
"#<#{
|
54
|
+
"#<#{Socket} #{@delegate.inspect}>"
|
63
55
|
end
|
64
56
|
|
65
57
|
# String representation of the wrapped socket.
|
@@ -43,6 +43,10 @@ module Falcon
|
|
43
43
|
token.release unless socket
|
44
44
|
end
|
45
45
|
|
46
|
+
# Wrap the socket with a transparent token management.
|
47
|
+
# @parameter socket [Object] The socket to wrap.
|
48
|
+
# @parameter token [Async::Limiter::Token] The limiter token to release when socket closes.
|
49
|
+
# @returns [Falcon::Limiter::Socket] The wrapped socket.
|
46
50
|
def wrap_socket(socket, token)
|
47
51
|
Socket.new(socket, token)
|
48
52
|
end
|
data/readme.md
CHANGED
@@ -8,126 +8,33 @@ Advanced concurrency control and resource limiting for Falcon web server, built
|
|
8
8
|
|
9
9
|
This gem provides sophisticated concurrency management for Falcon applications by:
|
10
10
|
|
11
|
-
- **Connection Limiting**: Control the number of concurrent connections to prevent server overload
|
12
|
-
- **Long Task Management**: Handle I/O vs CPU bound workloads effectively by releasing resources during long operations
|
13
|
-
- **Priority-based Resource Allocation**: Higher priority tasks get preferential access to limited resources
|
14
|
-
- **Automatic Resource Cleanup**: Ensures proper resource release even when exceptions occur
|
15
|
-
- **Built-in Statistics**: Monitor resource utilization and contention
|
11
|
+
- **Connection Limiting**: Control the number of concurrent connections to prevent server overload.
|
12
|
+
- **Long Task Management**: Handle I/O vs CPU bound workloads effectively by releasing resources during long operations.
|
13
|
+
- **Priority-based Resource Allocation**: Higher priority tasks get preferential access to limited resources.
|
14
|
+
- **Automatic Resource Cleanup**: Ensures proper resource release even when exceptions occur.
|
15
|
+
- **Built-in Statistics**: Monitor resource utilization and contention.
|
16
16
|
|
17
|
-
##
|
17
|
+
## Usage
|
18
18
|
|
19
|
-
|
19
|
+
Please see the [project documentation](https://socketry.github.io/falcon-limiter/) for more details.
|
20
20
|
|
21
|
-
|
22
|
-
gem 'falcon-limiter'
|
23
|
-
```
|
21
|
+
- [Getting Started](https://socketry.github.io/falcon-limiter/guides/getting-started/index) - This guide explains how to get started with `falcon-limiter` for advanced concurrency control and resource limiting in Falcon web applications.
|
24
22
|
|
25
|
-
|
23
|
+
- [Long Tasks](https://socketry.github.io/falcon-limiter/guides/long-tasks/index) - This guide explains how to use <code class="language-ruby">Falcon::Limiter::LongTask</code> to effectively manage I/O vs CPU bound workloads in your Falcon applications.
|
26
24
|
|
27
|
-
|
25
|
+
## Releases
|
26
|
+
|
27
|
+
Please see the [project releases](https://socketry.github.io/falcon-limiter/releases/index) for all releases.
|
28
28
|
|
29
|
-
###
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
# Configure concurrency limits
|
40
|
-
limiter_configuration.max_long_tasks = 8
|
41
|
-
limiter_configuration.max_accepts = 2
|
42
|
-
|
43
|
-
scheme "http"
|
44
|
-
url "http://localhost:9292"
|
45
|
-
|
46
|
-
rack_app do
|
47
|
-
run lambda { |env|
|
48
|
-
# Start long task for I/O bound work
|
49
|
-
Falcon::Limiter::LongTask.current&.start
|
50
|
-
|
51
|
-
# Long I/O operation (database query, external API call, etc.)
|
52
|
-
external_api_call
|
53
|
-
|
54
|
-
# Optional manual stop (auto-cleanup on response end)
|
55
|
-
Falcon::Limiter::LongTask.current&.stop
|
56
|
-
|
57
|
-
[200, {}, ["OK"]]
|
58
|
-
}
|
59
|
-
end
|
60
|
-
end
|
61
|
-
```
|
62
|
-
|
63
|
-
### Manual Middleware Setup
|
64
|
-
|
65
|
-
``` ruby
|
66
|
-
require "falcon-limiter"
|
67
|
-
require "protocol/http/middleware"
|
68
|
-
|
69
|
-
# Configure middleware stack
|
70
|
-
middleware = Protocol::HTTP::Middleware.build do
|
71
|
-
use Falcon::Limiter::Middleware,
|
72
|
-
max_long_tasks: 4,
|
73
|
-
max_accepts: 2
|
74
|
-
use Protocol::Rack::Adapter
|
75
|
-
run rack_app
|
76
|
-
end
|
77
|
-
```
|
78
|
-
|
79
|
-
### Direct Semaphore Usage
|
80
|
-
|
81
|
-
``` ruby
|
82
|
-
require "falcon-limiter"
|
83
|
-
|
84
|
-
# Create a semaphore for database connections
|
85
|
-
db_semaphore = Falcon::Limiter::Semaphore.new(5)
|
86
|
-
|
87
|
-
# Acquire a connection
|
88
|
-
token = db_semaphore.acquire
|
89
|
-
|
90
|
-
begin
|
91
|
-
# Use database connection
|
92
|
-
database_operation
|
93
|
-
ensure
|
94
|
-
# Release the connection
|
95
|
-
token.release
|
96
|
-
end
|
97
|
-
```
|
98
|
-
|
99
|
-
## Configuration
|
100
|
-
|
101
|
-
Configure limits using environment variables or programmatically:
|
102
|
-
|
103
|
-
``` bash
|
104
|
-
export FALCON_LIMITER_MAX_LONG_TASKS=8
|
105
|
-
export FALCON_LIMITER_MAX_ACCEPTS=2
|
106
|
-
export FALCON_LIMITER_START_DELAY=0.6
|
107
|
-
```
|
108
|
-
|
109
|
-
Or in code:
|
110
|
-
|
111
|
-
``` ruby
|
112
|
-
Falcon::Limiter.configure do |config|
|
113
|
-
config.max_long_tasks = 8
|
114
|
-
config.max_accepts = 2
|
115
|
-
config.start_delay = 0.6
|
116
|
-
end
|
117
|
-
```
|
118
|
-
|
119
|
-
## Architecture
|
120
|
-
|
121
|
-
Falcon Limiter is built on top of [async-limiter](https://github.com/socketry/async-limiter), providing:
|
122
|
-
|
123
|
-
- **Thread-safe resource management** using priority queues
|
124
|
-
- **Integration with Falcon's HTTP pipeline** through Protocol::HTTP::Middleware
|
125
|
-
- **Automatic connection token management** for optimal resource utilization
|
126
|
-
- **Priority-based task scheduling** to prevent resource starvation
|
127
|
-
|
128
|
-
## Development
|
129
|
-
|
130
|
-
After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec sus` to run the tests.
|
29
|
+
### v0.1.0
|
30
|
+
|
31
|
+
- Initial implementation.
|
32
|
+
|
33
|
+
## See Also
|
34
|
+
|
35
|
+
- [falcon](https://github.com/socketry/falcon) - A fast, asynchronous, rack-compatible web server.
|
36
|
+
- [async-limiter](https://github.com/socketry/async-limiter) - Execution rate limiting for Async.
|
37
|
+
- [async](https://github.com/socketry/async) - A concurrency framework for Ruby.
|
131
38
|
|
132
39
|
## Contributing
|
133
40
|
|
@@ -146,15 +53,3 @@ In order to protect users of this project, we require all contributors to comply
|
|
146
53
|
### Community Guidelines
|
147
54
|
|
148
55
|
This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers.
|
149
|
-
|
150
|
-
## Releases
|
151
|
-
|
152
|
-
Please see the [project releases](https://socketry.github.io/falcon-limiter/releases/index) for all releases.
|
153
|
-
|
154
|
-
### v0.1.0
|
155
|
-
|
156
|
-
## See Also
|
157
|
-
|
158
|
-
- [falcon](https://github.com/socketry/falcon) - A fast, asynchronous, rack-compatible web server.
|
159
|
-
- [async-limiter](https://github.com/socketry/async-limiter) - Execution rate limiting for Async.
|
160
|
-
- [async](https://github.com/socketry/async) - A concurrency framework for Ruby.
|
data/releases.md
CHANGED
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.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josh Teeter
|
@@ -59,14 +59,9 @@ executables: []
|
|
59
59
|
extensions: []
|
60
60
|
extra_rdoc_files: []
|
61
61
|
files:
|
62
|
-
- examples/basic_limiting.rb
|
63
|
-
- examples/environment_usage.rb
|
64
|
-
- examples/falcon_environment.rb
|
65
|
-
- examples/limiter/README.md
|
66
|
-
- examples/limiter/config.ru
|
67
|
-
- examples/limiter/falcon.rb
|
68
62
|
- examples/load/config.ru
|
69
63
|
- examples/load/falcon.rb
|
64
|
+
- examples/load/readme.md
|
70
65
|
- lib/falcon/limiter.rb
|
71
66
|
- lib/falcon/limiter/environment.rb
|
72
67
|
- lib/falcon/limiter/long_task.rb
|
metadata.gz.sig
CHANGED
Binary file
|
data/examples/basic_limiting.rb
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
# Released under the MIT License.
|
5
|
-
# Copyright, 2025, by Samuel Williams.
|
6
|
-
|
7
|
-
require_relative "../lib/falcon/limiter"
|
8
|
-
|
9
|
-
# Create a semaphore with a limit of 3 concurrent resources
|
10
|
-
semaphore = Falcon::Limiter::Semaphore.new(3)
|
11
|
-
|
12
|
-
puts "Available resources: #{semaphore.available_count}"
|
13
|
-
puts "Acquired resources: #{semaphore.acquired_count}"
|
14
|
-
|
15
|
-
# Simulate acquiring and releasing resources
|
16
|
-
tasks = []
|
17
|
-
|
18
|
-
10.times do |i|
|
19
|
-
tasks << Thread.new do
|
20
|
-
puts "Task #{i}: Waiting for resource..."
|
21
|
-
|
22
|
-
# Acquire a resource token
|
23
|
-
token = semaphore.acquire
|
24
|
-
|
25
|
-
puts "Task #{i}: Acquired resource (#{semaphore.available_count} remaining)"
|
26
|
-
|
27
|
-
# Simulate work
|
28
|
-
sleep(rand * 2)
|
29
|
-
|
30
|
-
# Release the resource
|
31
|
-
token.release
|
32
|
-
|
33
|
-
puts "Task #{i}: Released resource (#{semaphore.available_count} available)"
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
# Wait for all tasks to complete
|
38
|
-
tasks.each(&:join)
|
39
|
-
|
40
|
-
puts "Final state - Available: #{semaphore.available_count}, Acquired: #{semaphore.acquired_count}"
|
@@ -1,77 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
# Released under the MIT License.
|
5
|
-
# Copyright, 2025, by Samuel Williams.
|
6
|
-
|
7
|
-
require_relative "../lib/falcon/limiter"
|
8
|
-
|
9
|
-
# Example service module that includes the limiter environment
|
10
|
-
module MyService
|
11
|
-
include Falcon::Limiter::Environment
|
12
|
-
|
13
|
-
# Customize limiter configuration by overriding methods
|
14
|
-
def limiter_maximum_long_tasks
|
15
|
-
8 # Override default of 4
|
16
|
-
end
|
17
|
-
|
18
|
-
def limiter_maximum_accepts
|
19
|
-
3 # Override default of 1
|
20
|
-
end
|
21
|
-
|
22
|
-
def limiter_start_delay
|
23
|
-
0.8 # Override default of 0.1
|
24
|
-
end
|
25
|
-
|
26
|
-
# Mock middleware method
|
27
|
-
def middleware
|
28
|
-
proc {|_env| [200, {}, ["Base middleware"]]}
|
29
|
-
end
|
30
|
-
|
31
|
-
# Mock endpoint method
|
32
|
-
def endpoint
|
33
|
-
Object.new.tap do |endpoint|
|
34
|
-
endpoint.define_singleton_method(:to_s) {"MockEndpoint"}
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
# Demonstrate the environment with proper async-service pattern
|
40
|
-
require "async/service"
|
41
|
-
environment = Async::Service::Environment.build(MyService)
|
42
|
-
service = environment.evaluator
|
43
|
-
|
44
|
-
puts "=== Falcon Limiter Environment Demo ==="
|
45
|
-
puts
|
46
|
-
puts "Configuration:"
|
47
|
-
options = service.limiter_semaphore_options
|
48
|
-
puts " Max Long Tasks: #{options[:maximum_long_tasks]}"
|
49
|
-
puts " Max Accepts: #{options[:maximum_accepts]}"
|
50
|
-
puts " Start Delay: #{options[:start_delay]}s"
|
51
|
-
puts
|
52
|
-
|
53
|
-
puts "Semaphore:"
|
54
|
-
semaphore = service.limiter_semaphore
|
55
|
-
puts " Limiter Semaphore: #{semaphore.available_count}/#{semaphore.limit} available"
|
56
|
-
puts " Limited: #{semaphore.limited?}"
|
57
|
-
puts " Waiting: #{semaphore.waiting_count}"
|
58
|
-
puts
|
59
|
-
|
60
|
-
puts "Middleware Integration:"
|
61
|
-
base_middleware = service.middleware
|
62
|
-
wrapped_middleware = service.limiter_middleware(base_middleware)
|
63
|
-
puts " Base middleware: #{base_middleware.class}"
|
64
|
-
puts " Wrapped middleware: #{wrapped_middleware.class}"
|
65
|
-
puts
|
66
|
-
|
67
|
-
puts "Endpoint Integration:"
|
68
|
-
endpoint = service.endpoint
|
69
|
-
puts " Final Endpoint: #{endpoint.class}"
|
70
|
-
|
71
|
-
# Check if it's actually wrapped (only if it's a Wrapper)
|
72
|
-
if endpoint.respond_to?(:semaphore)
|
73
|
-
puts " Semaphore: #{endpoint.semaphore.class}"
|
74
|
-
puts " Same semaphore instance: #{endpoint.semaphore == service.limiter_semaphore}"
|
75
|
-
else
|
76
|
-
puts " Note: Endpoint is not wrapped (likely mock object)"
|
77
|
-
end
|
@@ -1,55 +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_relative "../lib/falcon/limiter"
|
8
|
-
|
9
|
-
service "limiter-example.localhost" do
|
10
|
-
include Falcon::Limiter::Environment
|
11
|
-
|
12
|
-
# Configure concurrency limits by overriding methods
|
13
|
-
def limiter_max_long_tasks = 4
|
14
|
-
def limiter_max_accepts = 2
|
15
|
-
def limiter_start_delay = 0.5
|
16
|
-
|
17
|
-
scheme "http"
|
18
|
-
url "http://localhost:9292"
|
19
|
-
|
20
|
-
rack_app do
|
21
|
-
run lambda {|env|
|
22
|
-
request = env["protocol.http.request"]
|
23
|
-
|
24
|
-
case env["PATH_INFO"]
|
25
|
-
when "/fast"
|
26
|
-
# Fast response - no need for long task
|
27
|
-
[200, { "Content-Type" => "text/plain" }, ["Fast response"]]
|
28
|
-
|
29
|
-
when "/slow"
|
30
|
-
# Slow response - use long task management
|
31
|
-
Falcon::Limiter::LongTask.current&.start
|
32
|
-
|
33
|
-
# Simulate I/O operation (database query, API call, etc.)
|
34
|
-
sleep(2)
|
35
|
-
|
36
|
-
Falcon::Limiter::LongTask.current&.stop
|
37
|
-
|
38
|
-
[200, { "Content-Type" => "text/plain" }, ["Slow response completed"]]
|
39
|
-
|
40
|
-
when "/stats"
|
41
|
-
# Show limiter statistics
|
42
|
-
stats = if respond_to?(:statistics)
|
43
|
-
statistics
|
44
|
-
else
|
45
|
-
{ message: "Statistics not available" }
|
46
|
-
end
|
47
|
-
|
48
|
-
[200, { "Content-Type" => "application/json" }, [stats.to_json]]
|
49
|
-
|
50
|
-
else
|
51
|
-
[404, { "Content-Type" => "text/plain" }, ["Not found"]]
|
52
|
-
end
|
53
|
-
}
|
54
|
-
end
|
55
|
-
end
|
data/examples/limiter/README.md
DELETED
@@ -1,95 +0,0 @@
|
|
1
|
-
# Falcon Limiter Examples
|
2
|
-
|
3
|
-
This directory contains examples demonstrating the Falcon::Limiter functionality for managing concurrent workloads.
|
4
|
-
|
5
|
-
## Overview
|
6
|
-
|
7
|
-
The Falcon::Limiter system helps distinguish between I/O bound and CPU bound workloads:
|
8
|
-
|
9
|
-
- **I/O bound work**: Long tasks that benefit from releasing connection tokens to improve concurrency
|
10
|
-
- **CPU bound work**: Tasks that should keep connection tokens to prevent GVL contention
|
11
|
-
|
12
|
-
## Examples
|
13
|
-
|
14
|
-
### 1. Falcon Environment (`falcon.rb`)
|
15
|
-
|
16
|
-
Uses `Falcon::Environment::Limiter` for turn-key setup:
|
17
|
-
|
18
|
-
```bash
|
19
|
-
falcon-host ./falcon.rb
|
20
|
-
```
|
21
|
-
|
22
|
-
**Endpoints:**
|
23
|
-
- `/fast` - Quick response without long task
|
24
|
-
- `/slow` - I/O bound task using long task management
|
25
|
-
- `/cpu` - CPU bound task without long task
|
26
|
-
- `/stats` - Show limiter statistics
|
27
|
-
|
28
|
-
### 2. Rack Application (`config.ru`)
|
29
|
-
|
30
|
-
Basic Rack app with manual limiter middleware:
|
31
|
-
|
32
|
-
```bash
|
33
|
-
falcon serve -c config.ru
|
34
|
-
```
|
35
|
-
|
36
|
-
**Endpoints:**
|
37
|
-
- `/long-io` - Long I/O operation with long task
|
38
|
-
- `/short` - Short operation (long task delay avoids overhead)
|
39
|
-
- `/token-info` - Shows connection token information
|
40
|
-
- `/` - Simple hello response
|
41
|
-
|
42
|
-
## Key Concepts
|
43
|
-
|
44
|
-
### Long Task Management
|
45
|
-
|
46
|
-
```ruby
|
47
|
-
# For I/O bound operations
|
48
|
-
request.long_task&.start
|
49
|
-
external_api_call() # Long I/O operation
|
50
|
-
request.long_task&.stop # Optional - auto cleanup on response end
|
51
|
-
```
|
52
|
-
|
53
|
-
### Connection Token Release
|
54
|
-
|
55
|
-
Long tasks automatically:
|
56
|
-
1. Extract and release connection tokens during I/O operations
|
57
|
-
2. Acquire long task tokens from a separate semaphore
|
58
|
-
3. Allow more connections to be accepted while I/O is pending
|
59
|
-
4. Clean up automatically when response finishes
|
60
|
-
|
61
|
-
### Configuration
|
62
|
-
|
63
|
-
```ruby
|
64
|
-
# Environment-based
|
65
|
-
ENV["FALCON_LIMITER_MAX_LONG_TASKS"] = "8"
|
66
|
-
ENV["FALCON_LIMITER_MAX_ACCEPTS"] = "2"
|
67
|
-
|
68
|
-
# Or programmatic
|
69
|
-
Falcon::Limiter.configure do |config|
|
70
|
-
config.max_long_tasks = 8
|
71
|
-
config.max_accepts = 2
|
72
|
-
config.start_delay = 0.6
|
73
|
-
end
|
74
|
-
```
|
75
|
-
|
76
|
-
## Testing Load Scenarios
|
77
|
-
|
78
|
-
Test with multiple concurrent requests:
|
79
|
-
|
80
|
-
```bash
|
81
|
-
# Test slow endpoint concurrency
|
82
|
-
curl -s "http://localhost:9292/slow" &
|
83
|
-
curl -s "http://localhost:9292/slow" &
|
84
|
-
curl -s "http://localhost:9292/slow" &
|
85
|
-
|
86
|
-
# Should still be responsive for fast requests
|
87
|
-
curl -s "http://localhost:9292/fast"
|
88
|
-
```
|
89
|
-
|
90
|
-
## Benefits
|
91
|
-
|
92
|
-
1. **Better Concurrency**: I/O operations don't block connection acceptance
|
93
|
-
2. **Graceful Degradation**: System remains responsive under high load
|
94
|
-
3. **Resource Management**: Prevents GVL contention for CPU work
|
95
|
-
4. **Automatic Cleanup**: Long tasks clean up automatically on response completion
|
data/examples/limiter/config.ru
DELETED
@@ -1,50 +0,0 @@
|
|
1
|
-
#!/usr/bin/env falcon --verbose serve -c
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require "falcon/limiter"
|
5
|
-
|
6
|
-
# Configure limiter globally
|
7
|
-
Falcon::Limiter.configure do |config|
|
8
|
-
config.max_long_tasks = 4
|
9
|
-
config.max_accepts = 2
|
10
|
-
config.start_delay = 0.1
|
11
|
-
end
|
12
|
-
|
13
|
-
# Basic Rack app demonstrating limiter usage
|
14
|
-
run lambda {|env|
|
15
|
-
request = env["protocol.http.request"]
|
16
|
-
path = env["PATH_INFO"]
|
17
|
-
|
18
|
-
case path
|
19
|
-
when "/long-io"
|
20
|
-
# Start long task for I/O bound work
|
21
|
-
request.long_task&.start
|
22
|
-
|
23
|
-
# Simulate database query or external API call
|
24
|
-
sleep(1.5)
|
25
|
-
|
26
|
-
[200, { "content-type" => "text/plain" }, ["Long I/O operation completed at #{Time.now}"]]
|
27
|
-
|
28
|
-
when "/short"
|
29
|
-
# Short operation - long task overhead avoided by delay
|
30
|
-
sleep(0.05)
|
31
|
-
[200, { "content-type" => "text/plain" }, ["Short operation at #{Time.now}"]]
|
32
|
-
|
33
|
-
when "/token-info"
|
34
|
-
# Show connection token information
|
35
|
-
token_info = "No connection token"
|
36
|
-
|
37
|
-
if request.respond_to?(:connection)
|
38
|
-
io = request.connection.stream.io
|
39
|
-
if io.respond_to?(:token)
|
40
|
-
token = io.token
|
41
|
-
token_info = "Token: #{token.inspect}, Released: #{token.released?}"
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
[200, { "content-type" => "text/plain" }, [token_info]]
|
46
|
-
|
47
|
-
else
|
48
|
-
[200, { "content-type" => "text/plain" }, ["Hello from Falcon Limiter!"]]
|
49
|
-
end
|
50
|
-
}
|
data/examples/limiter/falcon.rb
DELETED
@@ -1,60 +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"
|
8
|
-
|
9
|
-
service "limiter-example.localhost" do
|
10
|
-
include Falcon::Environment::Limiter
|
11
|
-
|
12
|
-
# Configure limiter settings
|
13
|
-
limiter_configuration.max_long_tasks = 4
|
14
|
-
limiter_configuration.max_accepts = 2
|
15
|
-
limiter_configuration.start_delay = 0.1 # Shorter delay for demo
|
16
|
-
|
17
|
-
scheme "http"
|
18
|
-
url "http://localhost:9292"
|
19
|
-
|
20
|
-
rack_app do
|
21
|
-
run lambda {|env|
|
22
|
-
# Access HTTP request directly
|
23
|
-
request = env["protocol.http.request"]
|
24
|
-
path = env["PATH_INFO"]
|
25
|
-
|
26
|
-
case path
|
27
|
-
when "/fast"
|
28
|
-
# Fast request - no long task needed
|
29
|
-
[200, { "content-type" => "text/plain" }, ["Fast response: #{Time.now}"]]
|
30
|
-
|
31
|
-
when "/slow"
|
32
|
-
# Slow I/O bound request - use long task
|
33
|
-
request.long_task&.start
|
34
|
-
|
35
|
-
# Simulate I/O operation
|
36
|
-
sleep(2.0)
|
37
|
-
|
38
|
-
# Optional manual stop (auto-cleanup on response end)
|
39
|
-
request.long_task&.stop
|
40
|
-
|
41
|
-
[200, { "content-type" => "text/plain" }, ["Slow response: #{Time.now}"]]
|
42
|
-
|
43
|
-
when "/cpu"
|
44
|
-
# CPU bound request - don't use long task to prevent GVL contention
|
45
|
-
# Simulate CPU work
|
46
|
-
result = (1..1_000_000).sum
|
47
|
-
|
48
|
-
[200, { "content-type" => "text/plain" }, ["CPU result: #{result}"]]
|
49
|
-
|
50
|
-
when "/stats"
|
51
|
-
# Show limiter statistics
|
52
|
-
stats = statistics
|
53
|
-
[200, { "content-type" => "application/json" }, [stats.to_json]]
|
54
|
-
|
55
|
-
else
|
56
|
-
[404, { "content-type" => "text/plain" }, ["Not found"]]
|
57
|
-
end
|
58
|
-
}
|
59
|
-
end
|
60
|
-
end
|