Skip to content

Commit

Permalink
feat(pipeline): add flag to disable raising exceptions
Browse files Browse the repository at this point in the history
  • Loading branch information
philippeboyd committed Apr 10, 2024
1 parent 75f8dab commit 662d269
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- Made various performance optimizations to the Ruby driver. See #184.
- Always assume UTF-8 encoding instead of relying on `Encoding.default_external`.
- Add `exception` flag in `pipelined` allowing failed commands to be returned in the result array when set to `false`. See #187.

# 0.21.1

Expand Down
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,27 @@ end
# => ["OK", 1]
```

#### Exception management

The `exception` flag in the `#pipelined` method of `RedisClient` is a feature that modifies the pipeline execution
behavior. When set to `false`, it doesn't raise an exception when a command error occurs. Instead, it allows the
pipeline to execute all commands, and any failed command will be available in the returned array. (Defaults to `true`)

```ruby
results = redis.pipelined(exception: false) do |pipeline|
pipeline.call("SET", "foo", "bar") # => nil
pipeline.call("DOESNOTEXIST", 12) # => nil
pipeline.call("INCR", "baz") # => nil
end
# results => ["OK", #<RedisClient::CommandError: ERR unknown command 'DOESNOTEXIST', with args beginning with: '12'>, 2]

results.each do |result|
if result.is_a?(RedisClient::CommandError)
# Do something with the failed result
end
end
```

### Transactions

You can use [`MULTI/EXEC` to run a number of commands in an atomic fashion](https://redis.io/topics/transactions).
Expand Down
4 changes: 2 additions & 2 deletions lib/redis_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ def disable_reconnection(&block)
ensure_connected(retryable: false, &block)
end

def pipelined
def pipelined(exception: true)
pipeline = Pipeline.new(@command_builder)
yield pipeline

Expand All @@ -431,7 +431,7 @@ def pipelined
results = ensure_connected(retryable: pipeline._retryable?) do |connection|
commands = pipeline._commands
@middlewares.call_pipelined(commands, config) do
connection.call_pipelined(commands, pipeline._timeouts)
connection.call_pipelined(commands, pipeline._timeouts, exception: exception)
end
end

Expand Down
10 changes: 5 additions & 5 deletions lib/redis_client/connection_mixin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ def call(command, timeout)
end
end

def call_pipelined(commands, timeouts)
exception = nil
def call_pipelined(commands, timeouts, exception: true)
first_exception = nil

size = commands.size
results = Array.new(commands.size)
Expand All @@ -61,14 +61,14 @@ def call_pipelined(commands, timeouts)
elsif result.is_a?(Error)
result._set_command(commands[index])
result._set_config(config)
exception ||= result
first_exception ||= result
end

results[index] = result
end

if exception
raise exception
if first_exception && exception
raise first_exception
else
results
end
Expand Down
4 changes: 2 additions & 2 deletions lib/redis_client/decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ def with(*args)
end
ruby2_keywords :with if respond_to?(:ruby2_keywords, true)

def pipelined
@client.pipelined { |p| yield @_pipeline_class.new(p) }
def pipelined(exception: true)
@client.pipelined(exception: exception) { |p| yield @_pipeline_class.new(p) }
end

def multi(**kwargs)
Expand Down
27 changes: 27 additions & 0 deletions test/shared/redis_client_tests.rb
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,33 @@ def test_pipelining_error
assert_equal "42", @redis.call("GET", "foo")
end

def test_pipelining_error_with_explicit_raising_exception
error = assert_raises RedisClient::CommandError do
@redis.pipelined(exception: true) do |pipeline|
pipeline.call("DOESNOTEXIST", 12)
pipeline.call("SET", "foo", "42")
end
end

assert_equal ["DOESNOTEXIST", "12"], error.command

assert_equal "42", @redis.call("GET", "foo")
end

def test_pipelining_error_without_raising_exception
result = @redis.pipelined(exception: false) do |pipeline|
pipeline.call("DOESNOTEXIST", 12)
pipeline.call("SET", "foo", "42")
end

assert result[0].is_a?(RedisClient::CommandError)
assert_equal ["DOESNOTEXIST", "12"], result[0].command

assert_equal "OK", result[1]

assert_equal "42", @redis.call("GET", "foo")
end

def test_multi_error
error = assert_raises RedisClient::CommandError do
@redis.multi do |pipeline|
Expand Down

0 comments on commit 662d269

Please sign in to comment.