falqon 0.0.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
![Continuous Integration](https://github.com/floriandejonckheere/falqon/workflows/Continuous%20Integration/badge.svg)
|
2
|
+
[![Continuous Integration](https://github.com/floriandejonckheere/falqon/actions/workflows/ci.yml/badge.svg)](https://github.com/floriandejonckheere/falqon/actions/workflows/ci.yml)
|
4
3
|
![Release](https://img.shields.io/github/v/release/floriandejonckheere/falqon?label=Latest%20release)
|
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
|