diff --git a/README.md b/README.md
index f4879eb..645eabb 100644
--- a/README.md
+++ b/README.md
@@ -78,7 +78,7 @@ Before then the API may undergo slight changes and tweaks. Use the repository as
| connect over TCP | __yes__ | |
| connect over UDP | no | (why would you, really?) |
| connect / disconnect hooks | __yes__ | |
- | socket level configuration | __yes__ | User adjustable timeout and buffer size |
+ | socket level configuration | __yes__ | user-defined timeout and buffer size and/or callback |
| socket error handling | __yes__ | user-defined callback |
| RESP to JSON | __yes__ | via `xchange` library |
| RESP to structured data | __yes__ | via `xchange` library |
@@ -234,6 +234,7 @@ to linking.
## Managing Redis server connections
- [Initializing](#initializing)
+ - [Configuring](#configuring)
- [Connecting](#connecting)
- [Disconnecting](#disconnecting)
- [Connection hooks](#connection-hooks)
@@ -261,8 +262,10 @@ The first step is to create a `Redis` object, with the server name or IP address
redisxSetPort(redis, 7089);
```
-Alternatively, you may initialize the client for a high-availability configuration using with a set of
-[Redis Sentinel](https://redis.io/docs/latest/develop/reference/sentinel-clients/) servers, using
+#### Sentinel
+
+Alternatively, instead of `redisxInit()` above you may initialize the client for a high-availability configuration
+using with a set of [Redis Sentinel](https://redis.io/docs/latest/develop/reference/sentinel-clients/) servers, using
`redisxInitSentinel()`, e.g.:
```c
@@ -281,6 +284,13 @@ Alternatively, you may initialize the client for a high-availability configurati
redisxSetSentinelTimeout(redis, 30);
```
+After successful initialization, you may proceed with the configuration the same way as for the regular standalone
+server connection above.
+
+
+
+### Configuring
+
Before connecting to the Redis server, you may configure the database authentication (if any):
```c
@@ -305,30 +315,47 @@ will not be used, and RESP2 will be assumed -- which is best for older servers.
actual protocol used after connecting, using `redisxGetProtocol()`). Note, that after connecting, you may retrieve
the set of server properties sent in response to `HELLO` using `redisxGetHelloData()`.
+Optionally, you can select the database index to use now (or later, after connecting), if not the default (index
+0):
+
+```c
+ // (optional) Select the database index 2
+ redisxSelectDB(redis, 2);
+```
+
+Note, that you can switch the database index any time, with the caveat that it's not possible to change it for the
+subscription client when there are active subscriptions.
+
+
+#### Socket-level configuration
+
You might also tweak the socket options used for clients, if you find the socket defaults sub-optimal for your
-application (note, that this setting is common to all `Redis` instances managed by the library):
+application:
```c
// (optional) Set 1000 ms socket read/write timeout for future connections.
redisxSetSocketTimeout(redis, 1000);
// (optional) Set the TCP send/rcv buffer sizes to use if not default values.
- // This setting applies to all new connections after...
- redisxSetTcpBuf(65536);
+ redisxSetTcpBuf(redis, 65536);
```
-Optionally, you can select the database index to use now (or later, after connecting), if not the default (index
-0):
+If you want, you can perform further customization of the client sockets via a user-defined callback function, e.g.:
```c
- Redis *redis = ...
-
- // (optional) Select the database index 2
- redisxSelectDB(redis, 2);
+ int my_socket_config(int sock, enum redisx_channel channel) {
+ // Set up the socket any way you like...
+ ...
+
+ return X_SUCCESS;
+ }
```
-Note, that you can switch the database index any time, with the caveat that it's not possible to change it for the
-subscription client when there are active subscriptions.
+which you can then apply to your Redis instance as:
+
+```c
+ redisxSetSocketConfigurator(my_socket_config);
+```
@@ -1259,7 +1286,7 @@ Then activate it as:
```c
Redis *redis = ...
- redisxSetTransmitErrorHandler(redis, my_error_handler);
+ redisxSetSocketErrorHandler(redis, my_error_handler);
```
After that, every time there is an error with sending or receiving packets over the network to any of the Redis
diff --git a/include/redisx-priv.h b/include/redisx-priv.h
index e2bc4f3..5b62c74 100644
--- a/include/redisx-priv.h
+++ b/include/redisx-priv.h
@@ -70,9 +70,11 @@ typedef struct {
char *password; ///< Redis password (if any)
int timeoutMillis; ///< [ms] Socket read/write timeout
+ int tcpBufSize; ///< [bytes] TCP read/write buffer sizes to use
int protocol; ///< RESP version to use
boolean hello; ///< whether to use HELLO (introduced in Redis 6.0.0 only)
RESP *helloData; ///< RESP data received from server during last connection.
+ RedisSocketConfigurator socketConf; ///< Additional user configuration of client sockets
RedisClient *clients;
diff --git a/include/redisx.h b/include/redisx.h
index 50462dd..2af4a47 100644
--- a/include/redisx.h
+++ b/include/redisx.h
@@ -343,14 +343,26 @@ typedef void (*RedisPushProcessor)(RedisClient *cl, RESP *message, void *ptr);
+/**
+ * User callback function allowing additional customization of the client socket before connection.
+ *
+ * @param socket The socket descriptor
+ * @param channel REDISX_INTERACTIVE_CHANNEL, REDISX_PIPELINE_CHANNEL, REDISX_SUBSCRIPTION_CHANNEL
+ * @return X_SUCCESS (0) if the socket may be used as is after the return. Any other value
+ * will indicate that the socket should not be used and that the caller itself should
+ * fail with an error.
+ */
+typedef int (*RedisSocketConfigurator)(int socket, enum redisx_channel channel);
+
void redisxSetVerbose(boolean value);
boolean redisxIsVerbose();
void redisxDebugTraffic(boolean value);
-void redisxSetTcpBuf(int size);
int redisxSetSocketTimeout(Redis *redis, int millis);
+int redisxSetTcpBuf(Redis *redis, int size);
int redisxSetSentinelTimeout(Redis *redis, int millis);
-int redisxSetTransmitErrorHandler(Redis *redis, RedisErrorHandler f);
+int redisxSetSocketConfigurator(Redis *redis, RedisSocketConfigurator func);
+int redisxSetSocketErrorHandler(Redis *redis, RedisErrorHandler f);
int redisxSetPort(Redis *redis, int port);
int redisxSetUser(Redis *redis, const char *username);
diff --git a/src/redisx-client.c b/src/redisx-client.c
index b9b9bc2..c337f21 100644
--- a/src/redisx-client.c
+++ b/src/redisx-client.c
@@ -69,7 +69,7 @@ int rCheckClient(const RedisClient *cl) {
* @param op The operation that failed, e.g. 'send' or 'read'.
* @return X_NO_SERVICE
*
- * @sa redisxSetTransmitErrorHandler()
+ * @sa redisxSetSocketErrorHandler()
*/
static int rTransmitError(ClientPrivate *cp, const char *op) {
if(cp->isEnabled) {
diff --git a/src/redisx-net.c b/src/redisx-net.c
index 43f41ae..7b0be78 100644
--- a/src/redisx-net.c
+++ b/src/redisx-net.c
@@ -49,7 +49,6 @@ typedef struct ServerLink {
static ServerLink *serverList;
static pthread_mutex_t serverLock = PTHREAD_MUTEX_INITIALIZER;
-static int tcpBufSize = REDISX_TCP_BUF_SIZE;
/**
@@ -113,11 +112,12 @@ static int rSetServerAsync(Redis *redis, const char *desc, const char *hostname,
* Configure the Redis client sockets for optimal performance...
*
* \param socket The socket file descriptor.
- * \param timeoutMillis [ms] Socket read/write timeout, or <0 to no set.
+ * \param timeoutMillis [ms] Socket read/write timeout, or <=0 to no set.
+ * \param tcpBufSize [bytes] Socket read / write buffer sizes, or <=0 to not set;
* \param lowLatency TRUE (non-zero) if socket is to be configured for low latency, or else FALSE (0).
*
*/
-static void rConfigSocket(int socket, int timeoutMillis, boolean lowLatency) {
+static void rConfigSocket(int socket, int timeoutMillis, int tcpBufSize, boolean lowLatency) {
const boolean enable = TRUE;
if(timeoutMillis > 0) {
@@ -716,7 +716,11 @@ int rConnectClient(Redis *redis, enum redisx_channel channel) {
if((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
return x_error(X_NO_SERVICE, errno, fn, "client %d socket creation failed", channel);
- rConfigSocket(sock, p->timeoutMillis, rIsLowLatency(cp));
+ rConfigSocket(sock, p->timeoutMillis, p->tcpBufSize, rIsLowLatency(cp));
+
+ if(p->socketConf) {
+ prop_error(fn, p->socketConf(sock, channel));
+ }
while(connect(sock, (struct sockaddr *) &serverAddress, sizeof(serverAddress)) != 0) {
close(sock);
@@ -827,6 +831,7 @@ Redis *redisxInit(const char *server) {
p->protocol = REDISX_RESP2; // Default
p->timeoutMillis = REDISX_DEFAULT_TIMEOUT_MILLIS;
+ p->tcpBufSize = REDISX_TCP_BUF_SIZE;
// Create clients...
p->clients = (RedisClient *) calloc(3, sizeof(RedisClient));
@@ -1010,11 +1015,21 @@ void redisxDestroy(Redis *redis) {
/**
* Set the size of the TCP/IP buffers (send and receive) for future client connections.
*
+ * @param redis Pointer to a Redis instance.
* @param size (bytes) requested buffer size, or <= 0 to use default value
+ * @return X_SUCCESS (0) if successful, or else X_NULL if the redis instance is NULL,
+ * or X_NO_INIT if the redis instance is not initialized, or X_FAILURE
+ * if Redis was initialized in Sentinel configuration.
*/
-void redisxSetTcpBuf(int size) {
- xvprintf("Redis-X> Setting TCP buffer to %d\n.", size);
- tcpBufSize = size;
+int redisxSetTcpBuf(Redis *redis, int size) {
+ RedisPrivate *p;
+
+ prop_error("redisxSetTcpBuf", rConfigLock(redis));
+ p = (RedisPrivate *) redis->priv;
+ p->tcpBufSize = size;
+ rConfigUnlock(redis);
+
+ return X_SUCCESS;
}
/**
diff --git a/src/redisx.c b/src/redisx.c
index da99e6f..5e6a287 100644
--- a/src/redisx.c
+++ b/src/redisx.c
@@ -230,6 +230,31 @@ enum redisx_protocol redisxGetProtocol(Redis *redis) {
return protocol;
}
+/**
+ * Sets a user-defined callback for additioan custom configuring of client sockets
+ *
+ *
+ * @param redis The Redis server instance
+ * @param func The user-defined callback function, which performs the additional socket configuration
+ * @return X_SUCCESS (0) if successful, or or X_NULL if the redis argument in NULL, X_NO_INIT
+ * if the redis instance was not initialized.
+ *
+ * @sa redisxSetSocketErrorHandler()
+ */
+int redisxSetSocketConfigurator(Redis *redis, RedisSocketConfigurator func) {
+ static const char *fn = "redisxSetSocketConfigurator";
+
+ RedisPrivate *p;
+
+ prop_error(fn, rConfigLock(redis));
+ p = (RedisPrivate *) redis->priv;
+ p->hello = TRUE;
+ p->socketConf = func;
+ rConfigUnlock(redis);
+
+ return X_SUCCESS;
+}
+
/**
* Sets the user-specific error handler to call if a socket level trasmit error occurs.
* It replaces any prior handlers set earlier.
@@ -247,9 +272,11 @@ enum redisx_protocol redisxGetProtocol(Redis *redis) {
*
* \return X_SUCCESS if the handler was successfully configured, or X_NULL if the
* Redis instance is NULL.
+ *
+ * @sa redisxSetSocketConfigurator()
*/
-int redisxSetTransmitErrorHandler(Redis *redis, RedisErrorHandler f) {
- static const char *fn = "redisxSetTransmitErrorHandler";
+int redisxSetSocketErrorHandler(Redis *redis, RedisErrorHandler f) {
+ static const char *fn = "redisxSetSocketErrorHandler";
RedisPrivate *p;