falqon 0.0.1 → 0.1.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 +21 -1
- data/Gemfile +40 -8
- data/README.md +107 -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 +220 -0
- data/lib/falqon/middlewares/logger.rb +32 -0
- data/lib/falqon/queue.rb +626 -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 +95 -0
- data/lib/falqon/sub_set.rb +91 -0
- data/lib/falqon/version.rb +7 -2
- data/lib/falqon.rb +63 -0
- data/lib/generators/falqon/install.rb +37 -0
- metadata +96 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 315b92a750d6358553f6b0a46623c016f871e556eb9c787fe154454cd426ef56
|
4
|
+
data.tar.gz: c0fda1f5f23662c9c0ff2316bdf976fec620fb3f4b8a3be29c79fd65b5da5ac9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2205cb5ebf976ee5a61b33dd8157619d27ae6d2d0f2f8eca5d93fbf52b2e6719d38d697218525bfab105ec7db6fd496f70a681587a2eb4da6677e10b20a170a4
|
7
|
+
data.tar.gz: d460ebf86b84a2814f41cdb94f134f28eafcebaab3d7ec9bf90fe11d1b8329f739ae7a17c4b40c07c69834c393fad1db79997602ae45b8a1637aa4b2838d451a
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,25 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
## Falqon v0.0
|
3
|
+
## Falqon v0.1.0 (2024-11-16)
|
4
4
|
|
5
5
|
Initial release
|
6
|
+
|
7
|
+
## Falqon v0.0.1 (2023-06-22)
|
8
|
+
|
9
|
+
# Changelog
|
10
|
+
|
11
|
+
All notable changes to this project will be documented in this file.
|
12
|
+
|
13
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
14
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
15
|
+
|
16
|
+
## [Unreleased]
|
17
|
+
|
18
|
+
### Added
|
19
|
+
|
20
|
+
### Changed
|
21
|
+
|
22
|
+
### Removed
|
23
|
+
|
24
|
+
[unreleased]: https://github.com/floriandejonckheere/falqon/compare/v0.1.0...HEAD
|
25
|
+
[0.1.0]: https://github.com/floriandejonckheere/falqon/compare/v0.0.1...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.8.2", 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.45.0", require: false
|
32
|
+
|
33
|
+
# Behavior-driven test framework
|
34
|
+
gem "rspec", "3.13.0", require: false
|
35
|
+
|
36
|
+
# Linter
|
37
|
+
gem "rubocop", "1.68.0", require: false
|
38
|
+
gem "rubocop-factory_bot", "2.26.1", require: false
|
39
|
+
gem "rubocop-performance", "1.22.1", 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.11645", require: false
|
45
|
+
gem "tapioca", "0.16.4", require: false
|
46
|
+
|
47
|
+
# Time control
|
48
|
+
gem "timecop", "0.9.10", require: false
|
17
49
|
end
|
data/README.md
CHANGED
@@ -1,13 +1,39 @@
|
|
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/) 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
|
+
|
29
|
+
## Quickstart
|
30
|
+
|
31
|
+
### Requirements
|
7
32
|
|
8
|
-
Falqon
|
33
|
+
Falqon requires a Redis 6+ server to be available.
|
34
|
+
Use the [docker-compose.yml](https://github.com/floriandejonckheere/falqon/blob/master/docker-compose.yml) file to quickly spin up a Redis server.
|
9
35
|
|
10
|
-
|
36
|
+
### Installation
|
11
37
|
|
12
38
|
Add this line to your application's Gemfile:
|
13
39
|
|
@@ -23,14 +49,72 @@ Or install it yourself as:
|
|
23
49
|
|
24
50
|
$ gem install falqon
|
25
51
|
|
26
|
-
|
52
|
+
### Configuration
|
53
|
+
|
54
|
+
The default configuration works out of the box with the provided `docker-compose.yml` file.
|
55
|
+
See [configuration](#configuration) if you want to adjust the configuration.
|
56
|
+
|
57
|
+
### Usage
|
27
58
|
|
28
59
|
```ruby
|
29
60
|
require "falqon"
|
30
61
|
|
31
|
-
|
62
|
+
queue = Falqon::Queue.new("my_queue")
|
63
|
+
|
64
|
+
# Push a message to the queue
|
65
|
+
queue.push("Hello, world!", "Hello, world again!")
|
66
|
+
|
67
|
+
# Pop a message from the queue (return style)
|
68
|
+
puts queue.pop # => "Hello, world!"
|
69
|
+
|
70
|
+
queue.empty? # => false
|
71
|
+
|
72
|
+
queue.peek # => "Hello, world again!"
|
73
|
+
|
74
|
+
# Pop a message from the queue (block style)
|
75
|
+
queue.pop do |message|
|
76
|
+
puts message # => "Hello, world again!"
|
77
|
+
|
78
|
+
# Raising a Falqon::Error exception will cause the message to be requeued
|
79
|
+
raise Falqon::Error, "Something went wrong"
|
80
|
+
end
|
81
|
+
|
82
|
+
queue.empty? # => false
|
83
|
+
|
84
|
+
puts queue.pop # => "Hello, world again!"
|
85
|
+
|
86
|
+
queue.empty? # => true
|
87
|
+
|
88
|
+
queue.peek # => nil
|
32
89
|
```
|
33
90
|
|
91
|
+
For more comprehensive examples, see the [examples directory](examples/) in the repository.
|
92
|
+
|
93
|
+
## Architecture
|
94
|
+
|
95
|
+
A queue is identified with a name, which is used as a key prefix.
|
96
|
+
Queues are stored in Redis as a list of incrementing integers representing unique message identifiers.
|
97
|
+
The messages itself are stored in Redis as strings.
|
98
|
+
|
99
|
+
The following Redis keys are used to store data.
|
100
|
+
|
101
|
+
- `[{prefix}:]queues`: set of queue names
|
102
|
+
|
103
|
+
- `[{prefix}/]{name}`: list of message identifiers on the (pending) queue
|
104
|
+
|
105
|
+
- `[{prefix}/]{name}:id`: message identifier sequence
|
106
|
+
|
107
|
+
- `[{prefix}/]{name}:processing`: list of message identifiers being processed
|
108
|
+
|
109
|
+
- `[{prefix}/]{name}:scheduled`: list of message identifiers scheduled to retry
|
110
|
+
|
111
|
+
- `[{prefix}/]{name}:dead`: list of message identifiers that have been discarded
|
112
|
+
|
113
|
+
- `[{prefix}/]{name}:data:{id}`: message data for identifier `{id}`
|
114
|
+
|
115
|
+
- `[{prefix}/]{name}:metadata`: metadata for the queue
|
116
|
+
|
117
|
+
- `[{prefix}/]{name}:metadata:{id}`: metadata for identifier `{id}`
|
34
118
|
|
35
119
|
## Testing
|
36
120
|
|
@@ -44,10 +128,25 @@ bundle exec rspec
|
|
44
128
|
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
129
|
Github Actions will automatically run the test suite, build the `.gem` file and push it to [rubygems.org](https://rubygems.org).
|
46
130
|
|
131
|
+
## Documentation
|
132
|
+
|
133
|
+
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.
|
134
|
+
Locally, you can build the documentation using the following commands:
|
135
|
+
|
136
|
+
```sh
|
137
|
+
rake yrad
|
138
|
+
```
|
139
|
+
|
140
|
+
In development, you can start a local server to preview the documentation:
|
141
|
+
|
142
|
+
```sh
|
143
|
+
yard server --reload
|
144
|
+
```
|
145
|
+
|
47
146
|
## Contributing
|
48
147
|
|
49
148
|
Bug reports and pull requests are welcome on GitHub at [https://github.com/floriandejonckheere/falqon](https://github.com/floriandejonckheere/falqon).
|
50
149
|
|
51
150
|
## License
|
52
151
|
|
53
|
-
The software is available as open source under the terms of the [
|
152
|
+
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
|