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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f3dddcc61c0937bb6126b7a27298982a6159438479753b717fff59491ab9048
4
- data.tar.gz: 6430a166f1ffb09e7dbdc6b0e4897abdcfc82c4b299af550aebe3a0ace1deba5
3
+ metadata.gz: 61d3807604e041bf6affc784d16a9dc6003016e0f716081f2fb85efd26f17f12
4
+ data.tar.gz: 9b1ca7661095b7f609b5cd027975e52bbd9b87951d09d2c6b947c16503fa9396
5
5
  SHA512:
6
- metadata.gz: 4313993a4aa621ed7be5449759e8e874224c1d7ad88f1708e54f8fc2b070064b8ec43cce7b43775d98295c90eb46bd273592bd592331d20cb0d3f7990cef0a4a
7
- data.tar.gz: a4b12e91879182a082573553293615843d07f08c8aa67d4f9a2aa13af1121dda6b13cff654887e7da585d21eb10f1d7a6c06d009818e03fbcb2f6a363b9b6a87
6
+ metadata.gz: d09553c8da363d761041e05850e6e38a6f562eb00108ea6944f6724e4546fc114332367c73ee1e87ff5445ed4fbc0a8d3519fbf2a42a0bc7806ff8088e73af5d
7
+ data.tar.gz: c46e60261492d8f0da23738dffe8fbdf20844e999acf766ee3e93cf3f3a2b351e8a9fd804249dc12284b987b35f843644d977db8536156be15e082c3277bbcff
checksums.yaml.gz.sig CHANGED
Binary file
@@ -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
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: 4).
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
- 4
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] True if the long task token has been acquired, false otherwise.
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: 4).
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: 4, start_delay: 0.1)
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
- "#<#{self.class}(#{@delegate.class}) #{@delegate.inspect}>"
54
+ "#<#{Socket} #{@delegate.inspect}>"
63
55
  end
64
56
 
65
57
  # String representation of the wrapped socket.
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Falcon
7
7
  module Limiter
8
- VERSION = "0.1.0"
8
+ VERSION = "0.1.1"
9
9
  end
10
10
  end
@@ -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
- ## Installation
17
+ ## Usage
18
18
 
19
- Add this line to your application's Gemfile:
19
+ Please see the [project documentation](https://socketry.github.io/falcon-limiter/) for more details.
20
20
 
21
- ``` ruby
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
- ## Usage
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
- Please see the [project documentation](https://socketry.github.io/falcon-limiter/) for more details.
25
+ ## Releases
26
+
27
+ Please see the [project releases](https://socketry.github.io/falcon-limiter/releases/index) for all releases.
28
28
 
29
- ### Basic Falcon Environment Integration
30
-
31
- ``` ruby
32
- #!/usr/bin/env falcon-host
33
-
34
- require "falcon-limiter"
35
-
36
- service "myapp.localhost" do
37
- include Falcon::Environment::Limiter
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
@@ -1,3 +1,5 @@
1
1
  # Releases
2
2
 
3
3
  ## v0.1.0
4
+
5
+ - Initial implementation.
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.0
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
@@ -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
@@ -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
@@ -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
- }
@@ -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