Repository: redis/hiredis (C)

Issue:
Change of redisConnectCallback breaks legacy code
[This commit](https://github.com/redis/hiredis/commit/f69fac7690fb22a7fc19dba61ef70e5f79ccb2e9) drops `const` on `redisAsyncContext` in `redisConnectCallback`, and breaks legacy code:

```
// Legacy code:

void connect_callback(const redisAsyncContext *ctx, int status) {}

redisAsyncSetConnectCallback(ctx, connect_callback);
```

Error message:

```
error: invalid conversion from 'void (*)(const redisAsyncContext*, int)' to 'void (*)(redisAsyncContext*, int)' [-fpermissive]
```

Can you keep the original API, and define a new API for this purpose, i.e. dropping `const` for `redisAsyncContext`?

Regards


Discussion:
Good catch, we really can't change this until a major release.  I should be able to get something up this weekend
I'm going to go through the changes in more detail but I think I've got this sorted.

@sewenew new does this solve your backward compatibility issue?

@kristjanvalur I reworked your `onConnect` improvements to maintain backward compatibility with existing code.  Let me know if my changes have broken any semantics.
Great! I tested the code, and it works! Thanks!

B.T.W I also added some comment for the changes.

Regards
Hi there.  
When I did the original patch last year, the argument wasnt `const` at all.  I think the const modifier must have been added sometime in the past few months, so removing the `const` again was a change that I made when rebasing the PR recently.   Didn't realize that it was a breaking change.
But the change looks fine.  Thanks for fixing this.



Patch:
diff --git a/README.md b/README.md
index b0b2aff99..ff50d13e7 100644
--- a/README.md
+++ b/README.md
@@ -372,6 +372,8 @@ the disconnect callback is a good point to do so.
 Setting the connect or disconnect callbacks can only be done once per context. For subsequent calls the
 api will return `REDIS_ERR`. The function to set the callbacks have the following prototype:
 ```c
+/* Alternatively you can use redisAsyncSetConnectCallbackNC which will be passed a non-const
+   redisAsyncContext* on invocation (e.g. allowing writes to the privdata member). */
 int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
 int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
 ```
diff --git a/async.c b/async.c
index 73b798064..ae097df0b 100644
--- a/async.c
+++ b/async.c
@@ -140,6 +140,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
     ac->ev.scheduleTimer = NULL;
 
     ac->onConnect = NULL;
+    ac->onConnectNC = NULL;
     ac->onDisconnect = NULL;
 
     ac->replies.head = NULL;
@@ -226,17 +227,34 @@ redisAsyncContext *redisAsyncConnectUnix(const char *path) {
     return redisAsyncConnectWithOptions(&options);
 }
 
-int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
-    if (ac->onConnect == NULL) {
-        ac->onConnect = fn;
+static int
+redisAsyncSetConnectCallbackImpl(redisAsyncContext *ac, redisConnectCallback *fn,
+                                 redisConnectCallbackNC *fn_nc)
+{
+    /* If either are already set, this is an error */
+    if (ac->onConnect || ac->onConnectNC)
+        return REDIS_ERR;
 
-        /* The common way to detect an established connection is to wait for
-         * the first write event to be fired. This assumes the related event
-         * library functions are already set. */
-        _EL_ADD_WRITE(ac);
-        return REDIS_OK;
+    if (fn) {
+        ac->onConnect = fn;
+    } else if (fn_nc) {
+        ac->onConnectNC = fn_nc;
     }
-    return REDIS_ERR;
+
+    /* The common way to detect an established connection is to wait for
+     * the first write event to be fired. This assumes the related event
+     * library functions are already set. */
+    _EL_ADD_WRITE(ac);
+
+    return REDIS_OK;
+}
+
+int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
+    return redisAsyncSetConnectCallbackImpl(ac, fn, NULL);
+}
+
+int redisAsyncSetConnectCallbackNC(redisAsyncContext *ac, redisConnectCallbackNC *fn) {
+    return redisAsyncSetConnectCallbackImpl(ac, NULL, fn);
 }
 
 int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) {
@@ -305,14 +323,23 @@ static void __redisRunPushCallback(redisAsyncContext *ac, redisReply *reply) {
 
 static void __redisRunConnectCallback(redisAsyncContext *ac, int status)
 {
-    if (ac->onConnect) {
-        if (!(ac->c.flags & REDIS_IN_CALLBACK)) {
-            ac->c.flags |= REDIS_IN_CALLBACK;
+    if (ac->onConnect == NULL && ac->onConnectNC == NULL)
+        return;
+
+    if (!(ac->c.flags & REDIS_IN_CALLBACK)) {
+        ac->c.flags |= REDIS_IN_CALLBACK;
+        if (ac->onConnect) {
             ac->onConnect(ac, status);
-            ac->c.flags &= ~REDIS_IN_CALLBACK;
         } else {
-            /* already in callback */
+            ac->onConnectNC(ac, status);
+        }
+        ac->c.flags &= ~REDIS_IN_CALLBACK;
+    } else {
+        /* already in callback */
+        if (ac->onConnect) {
             ac->onConnect(ac, status);
+        } else {
+            ac->onConnectNC(ac, status);
         }
     }
 }
diff --git a/async.h b/async.h
index 690b31fa3..4f94660b1 100644
--- a/async.h
+++ b/async.h
@@ -57,7 +57,8 @@ typedef struct redisCallbackList {
 
 /* Connection callback prototypes */
 typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
-typedef void (redisConnectCallback)(struct redisAsyncContext*, int status);
+typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status);
+typedef void (redisConnectCallbackNC)(struct redisAsyncContext *, int status);
 typedef void(redisTimerCallback)(void *timer, void *privdata);
 
 /* Context for an async connection to Redis */
@@ -93,6 +94,7 @@ typedef struct redisAsyncContext {
 
     /* Called when the first write event was received. */
     redisConnectCallback *onConnect;
+    redisConnectCallbackNC *onConnectNC;
 
     /* Regular command callbacks */
     redisCallbackList replies;
@@ -121,6 +123,7 @@ redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
                                                   const char *source_addr);
 redisAsyncContext *redisAsyncConnectUnix(const char *path);
 int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
+int redisAsyncSetConnectCallbackNC(redisAsyncContext *ac, redisConnectCallbackNC *fn);
 int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
 
 redisAsyncPushFn *redisAsyncSetPushCallback(redisAsyncContext *ac, redisAsyncPushFn *fn);


Test patch:
diff --git a/test.c b/test.c
index fccaca444..b90175214 100644
--- a/test.c
+++ b/test.c
@@ -2008,7 +2008,7 @@ static redisAsyncContext *do_aconnect(struct config config, astest_no testno)
 {
     redisOptions options = {0};
     memset(&astest, 0, sizeof(astest));
-    
+
     astest.testno = testno;
     astest.connect_status = astest.disconnect_status = -2;
 
@@ -2039,7 +2039,7 @@ static redisAsyncContext *do_aconnect(struct config config, astest_no testno)
     c->data = &astest;
     c->dataCleanup = asCleanup;
     redisPollAttach(c);
-    redisAsyncSetConnectCallback(c, connectCallback);
+    redisAsyncSetConnectCallbackNC(c, connectCallback);
     redisAsyncSetDisconnectCallback(c, disconnectCallback);
     return c;
 }
@@ -2058,7 +2058,7 @@ static void test_async_polling(struct config config) {
     int status;
     redisAsyncContext *c;
     struct config defaultconfig = config;
-   
+
     test("Async connect: ");
     c = do_aconnect(config, ASTEST_CONNECT);
     assert(c);
@@ -2095,7 +2095,7 @@ static void test_async_polling(struct config config) {
         test_cond(astest.connect_status == REDIS_ERR);
         config = defaultconfig;
     }
-      
+
     /* Test a ping/pong after connection */
     test("Async PING/PONG: ");
     c = do_aconnect(config, ASTEST_PINGPONG);