falqon 0.0.1 → 1.0.0
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
- data/CHANGELOG.md +26 -1
- data/Gemfile +40 -8
- data/README.md +108 -8
- data/bin/falqon +8 -0
- data/config/inflections.rb +3 -1
- data/lib/falqon/cli/base.rb +35 -0
- data/lib/falqon/cli/clear.rb +86 -0
- data/lib/falqon/cli/delete.rb +152 -0
- data/lib/falqon/cli/kill.rb +143 -0
- data/lib/falqon/cli/list.rb +26 -0
- data/lib/falqon/cli/refill.rb +36 -0
- data/lib/falqon/cli/revive.rb +36 -0
- data/lib/falqon/cli/schedule.rb +40 -0
- data/lib/falqon/cli/show.rb +189 -0
- data/lib/falqon/cli/stats.rb +44 -0
- data/lib/falqon/cli/status.rb +47 -0
- data/lib/falqon/cli/version.rb +14 -0
- data/lib/falqon/cli.rb +168 -0
- data/lib/falqon/concerns/hooks.rb +101 -0
- data/lib/falqon/configuration.rb +141 -0
- data/lib/falqon/connection_pool_snooper.rb +15 -0
- data/lib/falqon/data.rb +11 -0
- data/lib/falqon/error.rb +13 -0
- data/lib/falqon/identifier.rb +11 -0
- data/lib/falqon/message.rb +221 -0
- data/lib/falqon/middlewares/logger.rb +32 -0
- data/lib/falqon/queue.rb +640 -0
- data/lib/falqon/strategies/linear.rb +82 -0
- data/lib/falqon/strategies/none.rb +44 -0
- data/lib/falqon/strategy.rb +26 -0
- data/lib/falqon/sub_queue.rb +96 -0
- data/lib/falqon/sub_set.rb +92 -0
- data/lib/falqon/version.rb +7 -2
- data/lib/falqon.rb +63 -0
- data/lib/generators/falqon/install.rb +37 -0
- metadata +100 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a6de2c2359a6d2aebf3f7f0531b9dea885d46f579a64aa0b46036c204c7df52
|
4
|
+
data.tar.gz: 8896a1d994cd209f436c8c260c5eba7985c9b3055064f186e26d7ff8452f7407
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 465c3dc7415da3ee83118227916a0d21a653fd8c74b2876a73e2510e545e7b210d0a96425d3f014ad997516f9314fea9de819b81deb373ec949162a3b7d6ca15
|
7
|
+
data.tar.gz: 231afe294759e0cc1fe0b0a884a99044808021e0d6d1f7201a8406f7a5b0b0128c6821f25b6d743d9c2475e9397c94dce10fc12ec831fbd123c7875495e067a1
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,30 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [Unreleased]
|
9
|
+
|
10
|
+
### Added
|
11
|
+
|
12
|
+
### Changed
|
13
|
+
|
14
|
+
### Removed
|
15
|
+
|
16
|
+
## Falqon v1.0.0 (2024-12-07)
|
17
|
+
|
18
|
+
### Changed
|
19
|
+
|
20
|
+
- Use atomic execution (MULTI/EXEC) where possible.
|
21
|
+
|
22
|
+
## Falqon v0.1.0 (2024-11-16)
|
4
23
|
|
5
24
|
Initial release
|
25
|
+
|
26
|
+
## Falqon v0.0.1 (2023-06-22)
|
27
|
+
|
28
|
+
[unreleased]: https://github.com/floriandejonckheere/falqon/compare/v1.0.0...HEAD
|
29
|
+
[1.0.0]: https://github.com/floriandejonckheere/falqon-pro/releases/tag/v1.0.0
|
30
|
+
[0.1.0]: https://github.com/floriandejonckheere/falqon-pro/releases/tag/v0.1.0
|
data/Gemfile
CHANGED
@@ -5,13 +5,45 @@ source "https://rubygems.org"
|
|
5
5
|
# Specify your gem's dependencies in falqon.gemspec
|
6
6
|
gemspec
|
7
7
|
|
8
|
+
# Task runner
|
9
|
+
gem "rake", "13.2.1", require: false
|
10
|
+
|
11
|
+
group :development do
|
12
|
+
# Documentation
|
13
|
+
gem "yard", "0.9.37", require: false
|
14
|
+
gem "yard-sorbet", "0.9.0", require: false
|
15
|
+
|
16
|
+
# Simple server
|
17
|
+
gem "webrick", "1.9.1", require: false
|
18
|
+
end
|
19
|
+
|
8
20
|
group :development, :test do
|
9
|
-
|
10
|
-
gem "
|
11
|
-
|
12
|
-
|
13
|
-
gem "
|
14
|
-
|
15
|
-
|
16
|
-
gem "
|
21
|
+
# Debugger
|
22
|
+
gem "debug", "1.9.2", require: false
|
23
|
+
|
24
|
+
# Build objects for tests
|
25
|
+
gem "factory_bot", "6.5.0", require: false
|
26
|
+
|
27
|
+
# Generate fake data
|
28
|
+
gem "ffaker", "2.23.0", require: false
|
29
|
+
|
30
|
+
# Mock Redis server
|
31
|
+
gem "mock_redis", "0.48.1", require: false
|
32
|
+
|
33
|
+
# Behavior-driven test framework
|
34
|
+
gem "rspec", "3.13.0", require: false
|
35
|
+
|
36
|
+
# Linter
|
37
|
+
gem "rubocop", "1.69.0", require: false
|
38
|
+
gem "rubocop-factory_bot", "2.26.1", require: false
|
39
|
+
gem "rubocop-performance", "1.23.0", require: false
|
40
|
+
gem "rubocop-rake", "0.6.0", require: false
|
41
|
+
gem "rubocop-rspec", "3.2.0", require: false
|
42
|
+
|
43
|
+
# Type checker
|
44
|
+
gem "sorbet", "0.5.11672", require: false
|
45
|
+
gem "tapioca", "0.16.5", require: false
|
46
|
+
|
47
|
+
# Time control
|
48
|
+
gem "timecop", "0.9.10", require: false
|
17
49
|
end
|
data/README.md
CHANGED
@@ -1,13 +1,40 @@
|
|
1
1
|
# Falqon
|
2
|
-
|
3
|
-

|
2
|
+
[](https://github.com/floriandejonckheere/falqon/actions/workflows/ci.yml)
|
4
3
|

|
5
4
|
|
6
|
-
Simple, efficient messaging queue for Ruby.
|
5
|
+
Simple, efficient, and reliable messaging queue for Ruby.
|
6
|
+
|
7
|
+
Falqon is a simple messaging queue implementation, backed by the in-memory Redis key-value store.
|
8
|
+
It exposes a simple Ruby API to send and receive messages between different processes, between threads in the same process, or even fibers in the same thread.
|
9
|
+
It is perfect when you require a lightweight solution for processing messages, but don't want to deal with the complexity of a full-blown message queue like RabbitMQ or Kafka.
|
10
|
+
|
11
|
+
See the [documentation](https://docs.falqon.dev) for more information on how to use Falqon in your application.
|
12
|
+
|
13
|
+
## Features
|
14
|
+
|
15
|
+
Falqon offers an elegant solution for messaging queues in Ruby.
|
16
|
+
|
17
|
+
- Elegant: only two methods to send and receive messages
|
18
|
+
- Reliable: no data is lost when a client crashes unexpectedly
|
19
|
+
- Fast: Falqon is built on top of Redis, a fast in-memory data store
|
20
|
+
- Flexible: tune the behaviour of the queue to your needs
|
21
|
+
|
22
|
+
## Get started
|
23
|
+
|
24
|
+
- Install Falqon and get working with messaging queues in a heartbeat using the [quickstart guide](#quickstart)
|
25
|
+
- Check out the [API documentation](https://docs.falqon.dev/) for more information on how to use Falqon in your application
|
26
|
+
- Check out the [CLI documentation](https://docs.falqon.dev/Falqon/CLI.html) for more information on how to manage queues and messages from the command line
|
27
|
+
- Read the [architecture documentation](#architecture) to learn more about how Falqon works under the hood
|
28
|
+
- Read about [Falqon Pro](https://docs.falqon.dev/pro), the commercial addon for Falqon that offers additional features
|
29
|
+
|
30
|
+
## Quickstart
|
31
|
+
|
32
|
+
### Requirements
|
7
33
|
|
8
|
-
Falqon
|
34
|
+
Falqon requires a Redis 6+ server to be available.
|
35
|
+
Use the [docker-compose.yml](https://github.com/floriandejonckheere/falqon/blob/master/docker-compose.yml) file to quickly spin up a Redis server.
|
9
36
|
|
10
|
-
|
37
|
+
### Installation
|
11
38
|
|
12
39
|
Add this line to your application's Gemfile:
|
13
40
|
|
@@ -23,14 +50,72 @@ Or install it yourself as:
|
|
23
50
|
|
24
51
|
$ gem install falqon
|
25
52
|
|
26
|
-
|
53
|
+
### Configuration
|
54
|
+
|
55
|
+
The default configuration works out of the box with the provided `docker-compose.yml` file.
|
56
|
+
See [configuration](https://docs.falqon.dev/Falqon/Configuration.html) if you want to adjust the configuration.
|
57
|
+
|
58
|
+
### Usage
|
27
59
|
|
28
60
|
```ruby
|
29
61
|
require "falqon"
|
30
62
|
|
31
|
-
|
63
|
+
queue = Falqon::Queue.new("my_queue")
|
64
|
+
|
65
|
+
# Push a message to the queue
|
66
|
+
queue.push("Hello, world!", "Hello, world again!")
|
67
|
+
|
68
|
+
# Pop a message from the queue (return style)
|
69
|
+
puts queue.pop # => "Hello, world!"
|
70
|
+
|
71
|
+
queue.empty? # => false
|
72
|
+
|
73
|
+
queue.peek # => "Hello, world again!"
|
74
|
+
|
75
|
+
# Pop a message from the queue (block style)
|
76
|
+
queue.pop do |message|
|
77
|
+
puts message # => "Hello, world again!"
|
78
|
+
|
79
|
+
# Raising a Falqon::Error exception will cause the message to be requeued
|
80
|
+
raise Falqon::Error, "Something went wrong"
|
81
|
+
end
|
82
|
+
|
83
|
+
queue.empty? # => false
|
84
|
+
|
85
|
+
puts queue.pop # => "Hello, world again!"
|
86
|
+
|
87
|
+
queue.empty? # => true
|
88
|
+
|
89
|
+
queue.peek # => nil
|
32
90
|
```
|
33
91
|
|
92
|
+
For more comprehensive examples, see the [examples directory](examples/) in the repository.
|
93
|
+
|
94
|
+
## Architecture
|
95
|
+
|
96
|
+
A queue is identified with a name, which is used as a key prefix.
|
97
|
+
Queues are stored in Redis as a list of incrementing integers representing unique message identifiers.
|
98
|
+
The messages itself are stored in Redis as strings.
|
99
|
+
|
100
|
+
The following Redis keys are used to store data.
|
101
|
+
|
102
|
+
- `[{prefix}:]queues`: set of queue names
|
103
|
+
|
104
|
+
- `[{prefix}/]{name}`: list of message identifiers on the (pending) queue
|
105
|
+
|
106
|
+
- `[{prefix}/]{name}:id`: message identifier sequence
|
107
|
+
|
108
|
+
- `[{prefix}/]{name}:processing`: list of message identifiers being processed
|
109
|
+
|
110
|
+
- `[{prefix}/]{name}:scheduled`: list of message identifiers scheduled to retry
|
111
|
+
|
112
|
+
- `[{prefix}/]{name}:dead`: list of message identifiers that have been discarded
|
113
|
+
|
114
|
+
- `[{prefix}/]{name}:data:{id}`: message data for identifier `{id}`
|
115
|
+
|
116
|
+
- `[{prefix}/]{name}:metadata`: metadata for the queue
|
117
|
+
|
118
|
+
- `[{prefix}/]{name}:metadata:{id}`: metadata for identifier `{id}`
|
34
119
|
|
35
120
|
## Testing
|
36
121
|
|
@@ -44,10 +129,25 @@ bundle exec rspec
|
|
44
129
|
To release a new version, update the version number in `lib/falqon/version.rb`, update the changelog, commit the files and create a git tag starting with `v`, and push it to the repository.
|
45
130
|
Github Actions will automatically run the test suite, build the `.gem` file and push it to [rubygems.org](https://rubygems.org).
|
46
131
|
|
132
|
+
## Documentation
|
133
|
+
|
134
|
+
The documentation in `docs/` is automatically built by [YARD](https://yardoc.org) and pushed to [docs.falqon.dev](https://docs.falqon.dev) on every push to the `main` branch.
|
135
|
+
Locally, you can build the documentation using the following commands:
|
136
|
+
|
137
|
+
```sh
|
138
|
+
rake yard
|
139
|
+
```
|
140
|
+
|
141
|
+
In development, you can start a local server to preview the documentation:
|
142
|
+
|
143
|
+
```sh
|
144
|
+
yard server --reload
|
145
|
+
```
|
146
|
+
|
47
147
|
## Contributing
|
48
148
|
|
49
149
|
Bug reports and pull requests are welcome on GitHub at [https://github.com/floriandejonckheere/falqon](https://github.com/floriandejonckheere/falqon).
|
50
150
|
|
51
151
|
## License
|
52
152
|
|
53
|
-
The software is available as open source under the terms of the [
|
153
|
+
The software is available as open source under the terms of the [LGPL-3.0 License](https://www.gnu.org/licenses/lgpl-3.0.html).
|
data/bin/falqon
ADDED
data/config/inflections.rb
CHANGED
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Falqon
|
4
|
+
class CLI
|
5
|
+
# @!visibility private
|
6
|
+
class Base
|
7
|
+
attr_reader :options
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
validate
|
15
|
+
execute
|
16
|
+
rescue StandardError => e
|
17
|
+
puts e.message
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def validate
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
|
26
|
+
def execute
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
def pluralize(count, singular, plural)
|
31
|
+
"#{count} #{count == 1 ? singular : plural}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Falqon
|
4
|
+
class CLI
|
5
|
+
# Clear messages from a queue
|
6
|
+
#
|
7
|
+
# Clearing a subqueue removes all messages and their data from the subqueue.
|
8
|
+
#
|
9
|
+
# Usage:
|
10
|
+
# falqon clear -q, --queue=QUEUE
|
11
|
+
#
|
12
|
+
# Options:
|
13
|
+
# -q, --queue=QUEUE # Queue name
|
14
|
+
# [--pending], [--no-pending], [--skip-pending] # Clear only pending messages
|
15
|
+
# [--processing], [--no-processing], [--skip-processing] # Clear only processing messages
|
16
|
+
# [--dead], [--no-dead], [--skip-dead] # Clear only dead messages
|
17
|
+
#
|
18
|
+
# If none of the +--pending+, +--processing+, +--scheduled+, or +--dead+ options are specified, all messages are cleared.
|
19
|
+
#
|
20
|
+
# @example Clear all messages in a queue
|
21
|
+
# $ falqon clear --queue jobs
|
22
|
+
# Cleared 3 messages from queue jobs
|
23
|
+
#
|
24
|
+
# @example Clear only pending messages
|
25
|
+
# $ falqon clear --queue jobs --pending
|
26
|
+
# Cleared 3 messages from queue jobs
|
27
|
+
#
|
28
|
+
# @example Clear only processing messages
|
29
|
+
# $ falqon clear --queue jobs --processing
|
30
|
+
# Cleared 3 messages from queue jobs
|
31
|
+
#
|
32
|
+
# @example Clear only scheduled messages
|
33
|
+
# $ falqon clear --queue jobs --scheduled
|
34
|
+
# Cleared 3 messages from queue jobs
|
35
|
+
#
|
36
|
+
# @example Clear only dead messages
|
37
|
+
# $ falqon clear --queue jobs --dead
|
38
|
+
# Cleared 3 messages from queue jobs
|
39
|
+
#
|
40
|
+
class Clear < Base
|
41
|
+
# @!visibility private
|
42
|
+
def validate
|
43
|
+
raise "No queue registered with this name: #{options[:queue]}" if options[:queue] && !Falqon::Queue.all.map(&:name).include?(options[:queue])
|
44
|
+
raise "--pending, --processing, --scheduled, and --dead are mutually exclusive" if [options[:pending], options[:processing], options[:scheduled], options[:dead]].count(true) > 1
|
45
|
+
end
|
46
|
+
|
47
|
+
# @!visibility private
|
48
|
+
def execute
|
49
|
+
# Clear messages
|
50
|
+
ids = subqueue.clear
|
51
|
+
|
52
|
+
if options[:pending]
|
53
|
+
puts "Cleared #{pluralize(ids.count, 'pending message', 'pending messages')} from queue #{queue.name}"
|
54
|
+
elsif options[:processing]
|
55
|
+
puts "Cleared #{pluralize(ids.count, 'processing message', 'processing messages')} from queue #{queue.name}"
|
56
|
+
elsif options[:scheduled]
|
57
|
+
puts "Cleared #{pluralize(ids.count, 'scheduled message', 'scheduled messages')} from queue #{queue.name}"
|
58
|
+
elsif options[:dead]
|
59
|
+
puts "Cleared #{pluralize(ids.count, 'dead message', 'dead messages')} from queue #{queue.name}"
|
60
|
+
else
|
61
|
+
puts "Cleared #{pluralize(ids.count, 'message', 'messages')} from queue #{queue.name}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def queue
|
68
|
+
@queue ||= Falqon::Queue.new(options[:queue])
|
69
|
+
end
|
70
|
+
|
71
|
+
def subqueue
|
72
|
+
@subqueue ||= if options[:pending]
|
73
|
+
queue.pending
|
74
|
+
elsif options[:processing]
|
75
|
+
queue.processing
|
76
|
+
elsif options[:scheduled]
|
77
|
+
queue.scheduled
|
78
|
+
elsif options[:dead]
|
79
|
+
queue.dead
|
80
|
+
else
|
81
|
+
queue
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Falqon
|
4
|
+
class CLI
|
5
|
+
# Delete messages from a queue
|
6
|
+
#
|
7
|
+
# Deleting a message removes it including its data from the queue.
|
8
|
+
#
|
9
|
+
# Usage:
|
10
|
+
# falqon delete -q, --queue=QUEUE
|
11
|
+
#
|
12
|
+
# Options:
|
13
|
+
# -q, --queue=QUEUE # Queue name
|
14
|
+
# [--pending], [--no-pending], [--skip-pending] # Delete only pending messages (default)
|
15
|
+
# [--processing], [--no-processing], [--skip-processing] # Delete only processing messages
|
16
|
+
# [--dead], [--no-dead], [--skip-dead] # Delete only dead messages
|
17
|
+
# [--head=N] # Delete N messages from head of queue
|
18
|
+
# [--tail=N] # Delete N messages from tail of queue
|
19
|
+
# [--index=N] # Delete message at index N
|
20
|
+
# [--range=N M] # Delete messages at index N to M
|
21
|
+
# [--id=N] # Delete message with ID N
|
22
|
+
#
|
23
|
+
# @example Delete all messages in the queue (by default only pending messages are deleted)
|
24
|
+
# $ falqon delete --queue jobs
|
25
|
+
# Deleted 10 messages from queue jobs
|
26
|
+
#
|
27
|
+
# @example Delete only pending messages
|
28
|
+
# $ falqon delete --queue jobs --pending
|
29
|
+
# Deleted 10 pending messages from queue jobs
|
30
|
+
#
|
31
|
+
# @example Delete only processing messages
|
32
|
+
# $ falqon delete --queue jobs --processing
|
33
|
+
# Deleted 1 processing message from queue jobs
|
34
|
+
#
|
35
|
+
# @example Delete only scheduled messages
|
36
|
+
# $ falqon delete --queue jobs --scheduled
|
37
|
+
# Deleted 1 scheduled message from queue jobs
|
38
|
+
#
|
39
|
+
# @example Delete only dead messages
|
40
|
+
# $ falqon delete --queue jobs --dead
|
41
|
+
# Deleted 5 dead messages from queue jobs
|
42
|
+
#
|
43
|
+
# @example Delete first 5 messages
|
44
|
+
# $ falqon delete --queue jobs --head 5
|
45
|
+
# Deleted 5 messages from queue jobs
|
46
|
+
#
|
47
|
+
# @example Delete last 5 messages
|
48
|
+
# $ falqon delete --queue jobs --tail 5
|
49
|
+
# Deleted 5 messages from queue jobs
|
50
|
+
#
|
51
|
+
# @example Delete message at index 5
|
52
|
+
# $ falqon delete --queue jobs --index 3 --index 5
|
53
|
+
# Deleted 1 message from queue jobs
|
54
|
+
#
|
55
|
+
# @example Delete messages from index 5 to 10
|
56
|
+
# $ falqon delete --queue jobs --range 5 10
|
57
|
+
# Deleted 6 messages from queue jobs
|
58
|
+
#
|
59
|
+
# @example Delete message with ID 5
|
60
|
+
# $ falqon delete --queue jobs --id 5 --id 1
|
61
|
+
# Deleted 2 messages from queue jobs
|
62
|
+
#
|
63
|
+
class Delete < Base
|
64
|
+
# @!visibility private
|
65
|
+
def validate
|
66
|
+
raise "No queue registered with this name: #{options[:queue]}" if options[:queue] && !Falqon::Queue.all.map(&:name).include?(options[:queue])
|
67
|
+
|
68
|
+
raise "--pending, --processing, --scheduled, and --dead are mutually exclusive" if [options[:pending], options[:processing], options[:scheduled], options[:dead]].count(true) > 1
|
69
|
+
|
70
|
+
raise "--head, --tail, --index, and --range are mutually exclusive" if [options[:head], options[:tail], options[:index], options[:range]].count { |o| o } > 1
|
71
|
+
raise "--range must be specified as two integers" if options[:range] && options[:range].count != 2
|
72
|
+
|
73
|
+
raise "--id is mutually exclusive with --head, --tail, --index, and --range" if options[:id] && [options[:head], options[:tail], options[:index], options[:range]].count { |o| o }.positive?
|
74
|
+
end
|
75
|
+
|
76
|
+
# @!visibility private
|
77
|
+
def execute
|
78
|
+
# Collect identifiers
|
79
|
+
ids = if options[:id]
|
80
|
+
Array(options[:id])
|
81
|
+
elsif options[:index]
|
82
|
+
Array(options[:index]).map do |i|
|
83
|
+
subqueue.peek(index: i) || raise("No message at index #{i}")
|
84
|
+
end
|
85
|
+
else
|
86
|
+
start, stop = range_options
|
87
|
+
|
88
|
+
subqueue.range(start:, stop:).map(&:to_i)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Transform identifiers to messages
|
92
|
+
messages = ids.map do |id|
|
93
|
+
message = Falqon::Message.new(queue, id: id.to_i)
|
94
|
+
|
95
|
+
raise "No message with ID #{id}" unless message.exists?
|
96
|
+
|
97
|
+
message
|
98
|
+
end
|
99
|
+
|
100
|
+
# Delete messages
|
101
|
+
messages.each(&:delete)
|
102
|
+
|
103
|
+
if options[:processing]
|
104
|
+
puts "Deleted #{pluralize(messages.count, 'processing message', 'processing messages')} from queue #{queue.name}"
|
105
|
+
elsif options[:scheduled]
|
106
|
+
puts "Deleted #{pluralize(messages.count, 'scheduled message', 'scheduled messages')} from queue #{queue.name}"
|
107
|
+
elsif options[:dead]
|
108
|
+
puts "Deleted #{pluralize(messages.count, 'dead message', 'dead messages')} from queue #{queue.name}"
|
109
|
+
else # options[:pending]
|
110
|
+
puts "Deleted #{pluralize(messages.count, 'pending message', 'pending messages')} from queue #{queue.name}"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def queue
|
117
|
+
@queue ||= Falqon::Queue.new(options[:queue])
|
118
|
+
end
|
119
|
+
|
120
|
+
def subqueue
|
121
|
+
@subqueue ||= if options[:processing]
|
122
|
+
queue.processing
|
123
|
+
elsif options[:scheduled]
|
124
|
+
queue.scheduled
|
125
|
+
elsif options[:dead]
|
126
|
+
queue.dead
|
127
|
+
else # options[:pending]
|
128
|
+
queue.pending
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def range_options
|
133
|
+
if options[:tail]
|
134
|
+
[
|
135
|
+
-options[:tail],
|
136
|
+
-1,
|
137
|
+
]
|
138
|
+
elsif options[:range]
|
139
|
+
[
|
140
|
+
options[:range].first,
|
141
|
+
options[:range].last,
|
142
|
+
]
|
143
|
+
else # options[:head]
|
144
|
+
[
|
145
|
+
0,
|
146
|
+
options.fetch(:head, 0) - 1,
|
147
|
+
]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Falqon
|
4
|
+
class CLI
|
5
|
+
# Kill messages in a queue
|
6
|
+
#
|
7
|
+
# Killing a message removes it from the pending or scheduled queue, and moves it to the dead queue.
|
8
|
+
#
|
9
|
+
# Usage:
|
10
|
+
# falqon kill -q, --queue=QUEUE
|
11
|
+
#
|
12
|
+
# Options:
|
13
|
+
# -q, --queue=QUEUE # Queue name
|
14
|
+
# [--pending], [--no-pending], [--skip-pending] # Kill only pending messages (default)
|
15
|
+
# [--processing], [--no-processing], [--skip-processing] # Kill only processing messages
|
16
|
+
# [--head=N] # Kill N messages from head of queue
|
17
|
+
# [--tail=N] # Kill N messages from tail of queue
|
18
|
+
# [--index=N] # Kill message at index N
|
19
|
+
# [--range=N M] # Kill messages at index N to M
|
20
|
+
# [--id=N] # Kill message with ID N
|
21
|
+
#
|
22
|
+
# @example Kill all messages in the queue (by default only pending messages are killed)
|
23
|
+
# $ falqon kill --queue jobs
|
24
|
+
# Killed 10 messages from queue jobs
|
25
|
+
#
|
26
|
+
# @example Kill only pending messages
|
27
|
+
# $ falqon kill --queue jobs --pending
|
28
|
+
# Killed 10 pending messages from queue jobs
|
29
|
+
#
|
30
|
+
# @example Kill only processing messages
|
31
|
+
# $ falqon kill --queue jobs --processing
|
32
|
+
# Killed 1 processing message from queue jobs
|
33
|
+
#
|
34
|
+
# @example Kill only scheduled messages
|
35
|
+
# $ falqon kill --queue jobs --scheduled
|
36
|
+
# Killed 1 scheduled message from queue jobs
|
37
|
+
#
|
38
|
+
# @example Kill first 5 messages
|
39
|
+
# $ falqon kill --queue jobs --head 5
|
40
|
+
# Killed 5 messages from queue jobs
|
41
|
+
#
|
42
|
+
# @example Kill last 5 messages
|
43
|
+
# $ falqon kill --queue jobs --tail 5
|
44
|
+
# Killed 5 messages from queue jobs
|
45
|
+
#
|
46
|
+
# @example Kill message at index 5
|
47
|
+
# $ falqon kill --queue jobs --index 3 --index 5
|
48
|
+
# Killed 1 message from queue jobs
|
49
|
+
#
|
50
|
+
# @example Kill messages from index 5 to 10
|
51
|
+
# $ falqon kill --queue jobs --range 5 10
|
52
|
+
# Killed 6 messages from queue jobs
|
53
|
+
#
|
54
|
+
# @example Kill message with ID 5
|
55
|
+
# $ falqon kill --queue jobs --id 5 --id 1
|
56
|
+
# Killed 2 messages from queue jobs
|
57
|
+
#
|
58
|
+
class Kill < Base
|
59
|
+
# @!visibility private
|
60
|
+
def validate
|
61
|
+
raise "No queue registered with this name: #{options[:queue]}" if options[:queue] && !Falqon::Queue.all.map(&:name).include?(options[:queue])
|
62
|
+
|
63
|
+
raise "--pending, --scheduled, and --processing are mutually exclusive" if [options[:pending], options[:scheduled], options[:processing]].count(true) > 1
|
64
|
+
|
65
|
+
raise "--head, --tail, --index, and --range are mutually exclusive" if [options[:head], options[:tail], options[:index], options[:range]].count { |o| o } > 1
|
66
|
+
raise "--range must be specified as two integers" if options[:range] && options[:range].count != 2
|
67
|
+
|
68
|
+
raise "--id is mutually exclusive with --head, --tail, --index, and --range" if options[:id] && [options[:head], options[:tail], options[:index], options[:range]].count { |o| o }.positive?
|
69
|
+
end
|
70
|
+
|
71
|
+
# @!visibility private
|
72
|
+
def execute
|
73
|
+
# Collect identifiers
|
74
|
+
ids = if options[:id]
|
75
|
+
Array(options[:id])
|
76
|
+
elsif options[:index]
|
77
|
+
Array(options[:index]).map do |i|
|
78
|
+
subqueue.peek(index: i) || raise("No message at index #{i}")
|
79
|
+
end
|
80
|
+
else
|
81
|
+
start, stop = range_options
|
82
|
+
|
83
|
+
subqueue.range(start:, stop:).map(&:to_i)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Transform identifiers to messages
|
87
|
+
messages = ids.map do |id|
|
88
|
+
message = Falqon::Message.new(queue, id: id.to_i)
|
89
|
+
|
90
|
+
raise "No message with ID #{id}" unless message.exists?
|
91
|
+
|
92
|
+
message
|
93
|
+
end
|
94
|
+
|
95
|
+
# Kill messages
|
96
|
+
messages.each(&:kill)
|
97
|
+
|
98
|
+
if options[:processing]
|
99
|
+
puts "Killed #{pluralize(messages.count, 'processing message', 'processing messages')} in queue #{queue.name}"
|
100
|
+
elsif options[:scheduled]
|
101
|
+
puts "Killed #{pluralize(messages.count, 'scheduled message', 'scheduled messages')} in queue #{queue.name}"
|
102
|
+
else # options[:pending]
|
103
|
+
puts "Killed #{pluralize(messages.count, 'pending message', 'pending messages')} in queue #{queue.name}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def queue
|
110
|
+
@queue ||= Falqon::Queue.new(options[:queue])
|
111
|
+
end
|
112
|
+
|
113
|
+
def subqueue
|
114
|
+
@subqueue ||= if options[:processing]
|
115
|
+
queue.processing
|
116
|
+
elsif options[:scheduled]
|
117
|
+
queue.scheduled
|
118
|
+
else # options[:pending]
|
119
|
+
queue.pending
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def range_options
|
124
|
+
if options[:tail]
|
125
|
+
[
|
126
|
+
-options[:tail],
|
127
|
+
-1,
|
128
|
+
]
|
129
|
+
elsif options[:range]
|
130
|
+
[
|
131
|
+
options[:range].first,
|
132
|
+
options[:range].last,
|
133
|
+
]
|
134
|
+
else # options[:head]
|
135
|
+
[
|
136
|
+
0,
|
137
|
+
options.fetch(:head, 0) - 1,
|
138
|
+
]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|