220 Commits

Author SHA1 Message Date
Marius Seufzer 35f9e7aab8 Add onUnexpectedConnectionClose callback to pool
Currently there is no way to set `RedisConnection`'s `onUnexpectedClosure` for connections within a `RedisConnectionPool`. This PR adds a new closure to `RedisConnectionPool.onUnexpectedConnectionClose` which will be triggered for every pool connection that closed unexpectedly.
2023-06-04 18:10:05 +00:00
Gwynne Raskind 8ac2d742cb Make the parameter-less RedisClientError factory statics computed properties. As stored properties, they trigger Thread Sanitizer errors when multiple connections trigger the same errors (usually connectionClosed) too close together due to lazy once-only initialization. 2023-05-28 02:30:15 +00:00
Nathan Harris aa185a0133 Lay groundwork for RESP3 support and flat ChannelHandler hierarchy
## Motivation

Since Redis 6.0, a new serialization protocol format (v3) is available that gives richer semantic reasoning behind the different types to enable commands to better understand the return types to provide in their programming language.

In addition, the `RESPTranslator` type is going to see more direct usage, and the current API doesn't make read well.

## Changes

- Add: Internal `RESPVersion` enum that the `RESPTranslator` will start to use
- Rename: `RESPTranslator.parseBytes` to `RESPTranslator.read(from:)`
2022-12-26 14:43:21 -06:00
Nathan Harris a431ae8c6c [List] Properly map results when key has no values
Fixes #116
2022-12-12 20:38:00 -06:00
Fabian Fett e0cab21f95 Graceful connection close without sending a QUIT command first 2022-12-07 17:10:40 +01:00
Fabian Fett 4ea66c4788 Add support for graceful shutdown to the RedisCommandHandler 2022-12-07 15:51:43 +00:00
Nathan Harris b88fac059d #115 -- Remove logging(to:) method 2022-12-01 13:06:35 -06:00
Fabian Fett c366a16fe8 Explicitly depend on NIO modules 2022-12-01 13:46:41 +01:00
Fabian Fett d0f15ad55b Conform RedisByteDecoder to NIOSingleStepByteToMessageDecoder 2022-11-30 14:48:34 +01:00
Nathan Harris 00eb9b5e33 Unify PubSub Handler Signature
Motivation

Right now the PubSub handlers are split into three separate closures, with the subscribe/unsubscribe handlers being optional. This won't play well with AsyncStream for being able to respond to all events that a PubSub subscription can cause.

Additionally, the current structure is very verbose in code to maintain - but also adds complexity to developers who are first getting started to understand the lifecycle of PubSub events.

Changes

- Add: New `RedisPubSubEvent` enum that captures the subscribe, unsubscribe, and message lifecycle events
- Add: New `RedisPubSubEventReceiver` that combines the previous 3 closure types
- Add: Dedicated DocC Symbol Extension file for `RedisPubSubHandler`
- Change: `RedisClient.subscribe` and `RedisClient.psubscribe` method signatures to only require a single unlabeled closure
- Rename: `RedisUnsubscribeEventSource` to `RedisPubSubEvent.UnsubscribeEventSource`
- Remove: `RedisSubscriptionMessageReceiver`, `RedisSubscriptionChangeDetails`, `RedisSubscribeHandler`, and `RedisUnsubscribeHandler` types

Result

Developers should have a much easier time getting started and understanding PubSub with assistance from the compiler with types to understand
what they're being given and what's available to them as information to make more informed decisions in their app logic.
2022-11-30 04:33:31 +00:00
Fabian Fett 459f2cc4cb Fix NIOLock warning 2022-11-17 16:05:10 +01:00
Michael Stegeman adbc2e3e16 Switch from NIOAtomic to ManagedAtomic. 2022-08-21 02:47:00 +00:00
Nathan Harris c76203c61a #103 -- Provide greater context to Pub/Sub Unsubscribe events 2022-08-13 00:22:18 -05:00
Nathan Harris 555062c62e [Docs] Fix symbol links resolution for various redis commands 2022-04-24 23:37:39 -05:00
Nathan Harris 820820d877 Significantly Improve the Configuration API for Pools and Connections
## Motivation

The API for establishing the configuration of a connection pool had a lot of jargon and properties that developers had issues keeping straight and understanding what each does.

This commit provides first-class API support for concepts such as retry strategies, and how the pool handles connection counts.

## Changes

- Add: New ConnectionCountBehavior for determining leaky / non-leaky behavior
- Add: New ConnectionRetryStrategy for allowing customization of retry behavior
- Change: RedisConnection.defaultPort to be a computed property
- Change: The logging keys of pool connection retry metadata
- Rename: Several configuration properties to drop prefixes or to be combined into new structures

## Result

Developers should have a much better experience exploring the available configuration options for pools and connections, being able to understand how each piece works with the underlying system.
2022-04-24 23:37:39 -05:00
Nathan Harris eedf1581cf Replace remaining usage of pipeline.removeHandler(_:) 2022-04-24 04:29:33 +00:00
Nathan Harris cfb99ba0f7 #100 -- Fix addPubSubHandler not checking if already added 2022-04-24 03:34:10 +00:00
Nathan Harris 284b7f09bc Add overload of ping command for nil message style 2022-04-19 23:18:26 -05:00
Nathan Harris 9da5773e7a 92 -- Accept event loop and logger in RedisClient commands
There are many times that developers want exact control over which EventLoop will be executing their chained EventLoopFuture callbacks
and which Logger will do the logging in calls deep within RediStack.

All commands will now accept an optional EventLoop and Logger to hop to, and using the logger for desired logs.
2022-04-04 00:06:47 -05:00
Nathan Harris a4aec72592 Update command symbol docs to use DocC symbol references 2022-03-15 23:51:43 -05:00
Nathan Harris 20f6c45d76 Add DocC symbol reference file for RedisCommand 2022-03-15 23:51:43 -05:00
Nathan Harris d5f38b7b92 Add basic DocC files for modules 2022-03-15 23:51:43 -05:00
Nathan Harris 498b6a5eb5 Conform RedisCommand to Equatable 2022-03-15 23:38:13 -05:00
Nathan Harris 252b0b8061 Improve KEYS command to be more type-safe 2022-03-15 23:30:30 -05:00
Nathan Harris 370ef8c4ac 101 -- Add KEYS command 2022-03-15 21:12:22 -05:00
Daniel Ramteke b449334c8a [Commands] Add STRLEN 2022-02-21 19:10:34 +00:00
Nathan Harris 5ed6375e43 Set Swift 5.3 as the minimum version 2021-11-02 23:43:25 -07:00
Nathan Harris ad316a97ac 95 -- Add callback closure on RedisConnection invoked on channel close 2021-08-16 21:47:20 -07:00
Peter Adams 3ca471b226 Get pubsub numsub working 2021-05-04 16:26:35 +00:00
Peter Adams e08b42616b Get pubsub numpat working 2021-05-03 20:49:16 +00:00
Peter Adams 9958e2d13b Fix pubsub channels 2021-05-02 20:39:18 +01:00
Peter Adams 0c13e4f26c Get scan working on the same as redis-cli 6.2.1 2021-04-28 13:45:06 +01:00
Nathan Harris 03a066f8f5 Audit log message severity levels
Motivation:

Following the SSWG guidelines for libraries and log levels, because much of the library's behavior is expressed in the language and NIO framework as errors and failed ELFs, logging at error is "verbose" and takes away control from developers.

Modifications:

Log messages have been adjusted to more accurately represent when and how the log message should be used, especially when ELFs are failed or errors are thrown.

Result:

Developers won't have log messages at error or critical unless they opt-in from their own code, unless the library has no way of expressing the failure condition through the language.
2021-04-08 20:22:10 -07:00
Peter Adams 328ef17c2c Correct minor typo in documentation 2021-03-16 15:48:41 +00:00
Cory Benfield 1168ed09f7 Add support for service discovery.
The newly-released Service Discovery framework gives us the interesting
opportunity to make RediStack aware of complex service discovery tools.
This patch supplies a simple adaptor to integrat Service Discovery with
RediStack's pooled client, allowing users to work with arbitrary service
discovery systems.
2021-03-08 17:18:38 +00:00
Cory Benfield 338a6f4aa1 Delay connection attempts without addresses.
In some circumstances users may have connection pools configured without
any SocketAddresses ready to go. This is particularly likely in service
discovery configurations. Right now, the effect of attempting to use
such a pool is two fold. First, we'll emit a bunch of error level logs
telling users we have no addresses. Second, we'll fall into the
exponential backoff phase of connection establishment.

The first property is annoying, but the second one is actively harmful.
If your construction is timed incorrectly, we'll have the awkward
problem of burning a bunch of CPU trying to create connections we know
we cannot, and then a lengthy delay after the addresses are actually
configured before we start trying to use them. That's the worst of all
worlds.

This patch adds logic to detect the attempt to create connections when
we don't have any configured addresses and delays them. This path should
improve performance and ergonomics when in this mode.
2021-03-03 21:53:45 +00:00
Nathan Harris 5b05e26300 Remove 1.x deprecation support 2021-02-22 11:08:04 -08:00
Nathan Harris 3c6713038d Refactor RedisCommand into a general use object
Motivation:

RediStack today represents a command as a temporary object for the purpose of writing to the channel.

While it is useful to have an object for that purpose, commands handled in this way require immediate execution
and aren't available for other purposes.

Commands can serve a better purpose as a lightweight object to support delayed execution,
so that pipeling as described in issue #63 could be possible.

Modifications:

- Add: `get` overloads for JSON codable interaction on `RedisClient`
- Add: New `RedisZRangeResultOption` type for better interactions with zrange operations that can optionally return scores
- Add: New `RedisHashFieldKey` for type-safety when working with Hash field keys much like `RedisKey`
- Change: A few API types from enums to structs for library evolution
- Change: `RedisCommandHandler` to operate on a tuple of `RESPValue, EventLoopPromise<RESPValue>` rather than `RedisCommand`
- Change: `RedisCommand` to be a generic struct with the keyword, arguments, and a transform closure to defer execution
- Change: Almost all `RedisClient` command extensions to be factory methods on `RedisCommand` instead
- Change: Many response types to be optional to avoid developers having to do `isNull` checks on their own
- Change: `RedisClient.send(command:arguments:)` to be generic with `send(_:)` as the signature
- Rename: RedisClient extensions for scan methods to be more discoverable and legible as `scanHashField`, etc.

Result:

It should be easier to support a clean pipelining API with deferred command execution,
with extensions being easier for 2nd party developers, and the maintenance overhead of all of the command extensions
should be a little lighter when it comes to changing HOW commands are sent such as adding a context parameter
2020-12-02 08:41:59 +00:00
Nathan Harris 8bf26fb661 Rename RedisKeyLifetime to be nested in RedisKey
Motivation:

`RedisKeyLifetime` already has "RedisKey" as a prefix so it naturally fits as a nested type.

Modifications:

- Change: `RedisKeyLifetime` to be nested in `RedisKey` and named `Lifetime`
- Rename: `RedisKeyLifetime.Lifetime` to `Duration`
- Deprecate: `RedisKeyLifetime` and the nested type `Lifetime`

Result:

The global namespace is a little less cluttered with the types falling naturally where they already are.
2020-11-27 14:05:14 -08:00
Nathan Harris b2367ac33e 81 -- Add Configuration types for initialization 2020-11-22 00:17:22 -08:00
Nathan Harris 833fc33881 Fix RedisConnectionPool.leaseConnection code example 2020-10-18 18:48:46 +00:00
Nathan Harris 61cc879efe Change RedisConnection to end subscriptions when not allowed
Motivation:

When `RedisConnection.allowSubscriptions` is set to `false`, the connection could still be in a subscription state
leaving further commands to fail slowly from a full roundtrip to Redis, rather than succeeding as expected.

This changes the implementation so that it triggers a full unsubscribe from patterns and channels when set to `false`.

Modifications:

- Change: `RedisConnection.allowSubscriptions` to call `unsubscribe()` and `punsubscribe()` when set to `false`
- Change: `RedisPubSubHandler` to prefix storage of all dictionary keys to avoid name clashes between pattern and channel subscriptions

Result:

Developers should now have more deterministic and unsurprising behavior with PubSub
in regards to subscription management and connection state.
2020-10-16 22:14:56 -07:00
Nathan Harris e0d47f7330 Fix [p]unsubscribe from all
Motivation:

The methods of unsubscribing from all channels / patterns were not working as expected as they need to be special-case handled.

Modifications:

- Change: `RedisPubSubHandler` to be special-case unsubscribe when no arguments are provided

Result:

Developers should now properly be able to unsubscribe from all channels / patterns with a single method call.
2020-10-16 20:41:27 -07:00
Nathan Harris 42e8d4b127 Allow repeated commands to same connection in pool
Motivation:

Some Redis commands are very connection specific that have impacts on future access that makes it difficult in the current
checkout-use-return cycle that `RedisConnectionPool` uses.

Developers need a way to borrow a specific connection, chain several commands together, and then return the connection to the pool.

Modifications:

- Add: `leaseConnection` method to `RedisConnectionPool` which provides a connection from the pool and returns it after a provided closure's ELF resolves
- Add: `allowSubscriptions` property to `RedisConnection` for controlling the ability to make PubSub subscriptions
- Add: `RedisClientError.pubsubNotAllowed` case for when `RedisConnection.allowSubscriptions` is set to `false` and a subscription was still attempted

Result:

Developers should now have an "escape hatch" with `RedisConnectionPool` to do limited exclusive chains of operations on a specific connection.
2020-10-15 13:39:58 -07:00
Nathan Harris 56f0ab0bc0 Add test case for RedisConnectionPool connectionRetryTimeout 2020-10-06 20:19:26 -07:00
Nathan Harris c8cb256b59 Add default buffer connectionRetryTimeout to avoid literal immediate timeouts
Motivation:

When trying to allow users to configure the connection retry timeout offset,
not having a value provided (deadline of `now`) caused all attempts to use the pool to fail.

Modifications:

- Change: RedisConnectionPool to always have a timeout offset defined

Result:

If users don't specify any value, then the default of 60 seconds will be used.

If users specify "nil" (or `.none`) as the timeout, then a minimum of 10 milliseconds will be used to avoid immediate timeouts

Otherwise, use the user's specified `TimeAmount` as the offset of the timeout
2020-10-06 20:06:29 -07:00
Nathan Harris 3e28e75b6a Add configuration option for RedisConnectionPool lease timeout
Motivation:

With RedisConnectionPool a timeout is provided to prevent infinite loops of
retrying connections, but right now it is hardcoded to 60 seconds.

Users of downstream projects such as Vapor are noticing a "regression" of sorts, as previously
EventLoopFutures would fail immediately if a connection was not made available.

Modifications:

- Add: `connectionRetryTimeout` parameter to `RedisConnectionPool` initializer that still defaults to 60 seconds
- Change: RedisConnectionPool to use the new parameter if available to offset a deadline from "now"

Result:

Users can now configure the connection pool to fail immediately if connections are not available.
2020-10-06 19:33:43 -07:00
Nathan Harris e858c0a66e Resolve PubSub post-commit feedback
Motivation:

To ship PubSub faster, it was merged to the master branch without a peer review. This commit is to address the critical points of feedback given in a post-commit review.

Modifications:

- Add: New RedisClientError case where a "race condition" of removing a pubsub handler and subscription can happen
- Add: New state to RedisPubSubHandler for when it has been removed from a ChannelPipeline
- Change: RedisPubSubHandler to require an `eventLoop` in its initializer
- Change: The subscribe and unsubscribe methods on RedisPubSubHandler to handle the EventLoop hopping to be thread-safe

Result:

PubSub should have a more robust and thread-safe implementation.
2020-10-01 22:30:59 -07:00
Nathan Harris e7b597fc65 Add support for PubSub
Motivation:

One of the great features of Redis is being able to subscribe and receive messages published to specific channels
as a way of acting as a message queue for processing jobs.

PubSub requires a specific understanding of the connection model that can only be implemented directly in this library.

Modifications:

- Add: `RedisPubSubHandler` to sit in front of `RedisCommandHandler` to manage subscription callbacks and Redis registration
- Add: `publish` and the `pubsub` commands
- Add: `addPubSubHandler` extension to `NIO.Channel`
- Add: Type-safe String wrapper of `RedisChannelName` for PubSub methods
- Add: `pubsubSubscriptionNotFound` error case
- Add: `isSubscribed` property to `RedisConnection`
- Add: `availableConnectionCount` and `leasedConnectionCount` properties to `RedisConnectionPool`
- Add: Metrics for PubSub
- Add: `makeNewPool` factory method to `RedisConnectionPoolIntegrationTestCase`
- Change: `RedisClient` to require methods for PubSub management, as they are intrinsicly tied to the client's connection model
- Change: Parsing of `PING` response for handling special case in PubSub mode
- Rename: `ActiveConnectionGauge` to `RedisMetrics.IncrementalGauge`

Result:

Developers will now be able to use Redis in PubSub mode with both connections and pools.

This resolves #6
2020-09-29 22:23:44 -07:00
Nathan Harris 6fe37cb7e4 Fix RedisCommandHandler never reaching error state 2020-09-08 10:56:44 -07:00