Skip to content

Commit

Permalink
Improved socket-level configuration and site update
Browse files Browse the repository at this point in the history
  • Loading branch information
attipaci committed Dec 14, 2024
1 parent dc24fac commit c5a5395
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 45 deletions.
88 changes: 55 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 + callback |
| socket error handling | __yes__ | user-defined callback |
| RESP to JSON | __yes__ | via `xchange` library |
| RESP to structured data | __yes__ | via `xchange` library |
Expand Down Expand Up @@ -261,26 +261,6 @@ 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
`redisxInitSentinel()`, e.g.:

```c
// An array defining N sentinel servers and ports to use.
// A port number 0 or negative will use the default Redis port of 6379.
RedisServer sentinels[N] = { { "server1", 0 }, { "server2", 7024 } ... };

// Configure a Redis client instance for the Sentinel servers and "my-service" service name
Redis *redis = redisxInitSentinel(sentinels, N, "my-service");
if (redis == NULL) {
// Abort: something did not got to plan...
return;
}

// (optional) set a sentinel discovery timeout in ms...
redisxSetSentinelTimeout(redis, 30);
```
Before connecting to the Redis server, you may configure the database authentication (if any):

```c
Expand All @@ -305,30 +285,72 @@ 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.
#### 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
// An array defining N sentinel servers and ports to use.
// A port number 0 or negative will use the default Redis port of 6379.
RedisServer sentinels[N] = { { "server1", 0 }, { "server2", 7024 } ... };
// Configure a Redis client instance for the Sentinel servers and "my-service" service name
Redis *redis = redisxInitSentinel(sentinels, N, "my-service");
if (redis == NULL) {
// Abort: something did not got to plan...
return;
}
// (optional) set a sentinel discovery timeout in ms...
redisxSetSentinelTimeout(redis, 30);
```

After successful initialization, you may proceed with the configuration the same way as for the regular standalone
server connection above.


#### 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);
```
<a name="connecting"></a>
Expand Down Expand Up @@ -1259,7 +1281,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
Expand Down
2 changes: 2 additions & 0 deletions include/redisx-priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
16 changes: 14 additions & 2 deletions include/redisx.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/redisx-client.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
29 changes: 22 additions & 7 deletions src/redisx-net.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;


/**
Expand Down Expand Up @@ -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 &lt;0 to no set.
* \param timeoutMillis [ms] Socket read/write timeout, or &lt;=0 to no set.
* \param tcpBufSize [bytes] Socket read / write buffer sizes, or &lt;=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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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;
}

/**
Expand Down
31 changes: 29 additions & 2 deletions src/redisx.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;

Expand Down

0 comments on commit c5a5395

Please sign in to comment.