From 238972982a69aac87303e01de805e5161d062b7d Mon Sep 17 00:00:00 2001 From: plainbanana Date: Tue, 11 Feb 2025 04:18:55 +0900 Subject: [PATCH 01/13] Fix: To fix timeout related problems, add a libevent adapter for async operations. It introduces ev_io and ev_timer to handle timeout as intended. After issuing run_event_loop and after the command_timeout has passed, when executing the event loop again to read the response, there is a problem that always results in a timeout. Now, fixed it and when there are any readable responses, it reads them. --- src/Fast.xs | 4 +- src/adapters/libevent.h | 223 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 src/adapters/libevent.h diff --git a/src/Fast.xs b/src/Fast.xs index 27ef84c..70e42c7 100644 --- a/src/Fast.xs +++ b/src/Fast.xs @@ -11,7 +11,7 @@ extern "C" { #include #include #include -#include "hiredis_cluster/adapters/libevent.h" +#include "adapters/libevent.h" #include "hiredis_cluster/hircluster.h" #ifdef __cplusplus @@ -26,6 +26,7 @@ extern "C" { #define NANO_SECOND_TO_MICRO 1000 #define MIN_ATTEMPT_TO_GET_RESULT 2 +#define EVENT_BASE_PRIORITY_NUMBER 2 #define DEBUG_MSG(fmt, ...) \ if (self->debug) { \ @@ -272,6 +273,7 @@ SV *Redis__Cluster__Fast_connect(pTHX_ Redis__Cluster__Fast self) { } self->cluster_event_base = event_base_new(); + event_base_priority_init(self->cluster_event_base, EVENT_BASE_PRIORITY_NUMBER); if (redisClusterLibeventAttach(self->acc, self->cluster_event_base) != REDIS_OK) { return newSVpvf("%s", "failed to attach event base"); } diff --git a/src/adapters/libevent.h b/src/adapters/libevent.h new file mode 100644 index 0000000..55bd715 --- /dev/null +++ b/src/adapters/libevent.h @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __HIREDIS_LIBEVENT_H__ +#define __HIREDIS_LIBEVENT_H__ +#include +#include "hiredis/hiredis.h" +#include "hiredis/async.h" + +#define REDIS_LIBEVENT_DELETED 0x01 +#define REDIS_LIBEVENT_ENTERED 0x02 + +typedef struct redisLibeventEvents { + redisAsyncContext *context; + struct event *ev_io; + struct event *ev_timer; + struct event_base *base; + short flags; + short state; +} redisLibeventEvents; + +static void redisLibeventDestroy(redisLibeventEvents *e) { + hi_free(e); +} + +static void redisLibeventHandler(evutil_socket_t fd, short event, void *arg) { + ((void)fd); + redisLibeventEvents *e = (redisLibeventEvents*)arg; + e->state |= REDIS_LIBEVENT_ENTERED; + + #define CHECK_DELETED() if (e->state & REDIS_LIBEVENT_DELETED) {\ + redisLibeventDestroy(e);\ + return; \ + } + + if ((event & EV_READ) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) { + redisAsyncHandleRead(e->context); + CHECK_DELETED(); + } + + if ((event & EV_WRITE) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) { + redisAsyncHandleWrite(e->context); + CHECK_DELETED(); + } + + e->state &= ~REDIS_LIBEVENT_ENTERED; + #undef CHECK_DELETED +} + +static void redisLibeventTimerHandler(evutil_socket_t fd, short event, void *arg) { + ((void)fd); + ((void)event); + redisLibeventEvents *e = (redisLibeventEvents*)arg; + e->state |= REDIS_LIBEVENT_ENTERED; + + #define CHECK_DELETED() if (e->state & REDIS_LIBEVENT_DELETED) {\ + redisLibeventDestroy(e);\ + return; \ + } + + if (e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) { + redisAsyncHandleTimeout(e->context); + CHECK_DELETED(); + } + + e->state &= ~REDIS_LIBEVENT_ENTERED; + #undef CHECK_DELETED +} + +static void redisLibeventUpdate(void *privdata, short flag, int isRemove) { + redisLibeventEvents *e = (redisLibeventEvents *)privdata; + + if (isRemove) { + if ((e->flags & flag) == 0) { + return; + } else { + e->flags &= ~flag; + } + } else { + if (e->flags & flag) { + return; + } else { + e->flags |= flag; + } + } + + event_del(e->ev_io); + event_assign(e->ev_io, e->base, e->context->c.fd, e->flags | EV_PERSIST, + redisLibeventHandler, privdata); + event_add(e->ev_io, NULL); +} + +static void redisLibeventAddRead(void *privdata) { + redisLibeventUpdate(privdata, EV_READ, 0); +} + +static void redisLibeventDelRead(void *privdata) { + redisLibeventUpdate(privdata, EV_READ, 1); +} + +static void redisLibeventAddWrite(void *privdata) { + redisLibeventUpdate(privdata, EV_WRITE, 0); +} + +static void redisLibeventDelWrite(void *privdata) { + redisLibeventUpdate(privdata, EV_WRITE, 1); +} + +static void redisLibeventCleanup(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + if (!e) { + return; + } + if (e->ev_io) { + event_del(e->ev_io); + event_free(e->ev_io); + e->ev_io = NULL; + } + if (e->ev_timer) { + evtimer_del(e->ev_timer); + event_free(e->ev_timer); + e->ev_timer = NULL; + } + + if (e->state & REDIS_LIBEVENT_ENTERED) { + e->state |= REDIS_LIBEVENT_DELETED; + } else { + redisLibeventDestroy(e); + } +} + +static void redisLibeventSetTimeout(void *privdata, struct timeval tv) { + redisLibeventEvents *e = (redisLibeventEvents *)privdata; + evtimer_del(e->ev_timer); + evtimer_add(e->ev_timer, &tv); +} + +static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { + redisContext *c = &(ac->c); + redisLibeventEvents *e; + + /* Nothing should be attached when something is already attached */ + if (ac->ev.data != NULL) + return REDIS_ERR; + + /* Create container for context and r/w events */ + e = (redisLibeventEvents*)hi_calloc(1, sizeof(*e)); + if (e == NULL) + return REDIS_ERR; + + e->context = ac; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisLibeventAddRead; + ac->ev.delRead = redisLibeventDelRead; + ac->ev.addWrite = redisLibeventAddWrite; + ac->ev.delWrite = redisLibeventDelWrite; + ac->ev.cleanup = redisLibeventCleanup; + ac->ev.scheduleTimer = redisLibeventSetTimeout; + ac->ev.data = e; + + /* Initialize and install read/write events */ + e->ev_io = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e); + event_priority_set(e->ev_io, 0); + + /* Initialize and install timer events */ + e->ev_timer = evtimer_new(base, redisLibeventTimerHandler, e); + event_priority_set(e->ev_timer, 1); + + e->base = base; + return REDIS_OK; +} +#endif + +#ifndef __HIREDIS_CLUSTER_LIBEVENT_H__ +#define __HIREDIS_CLUSTER_LIBEVENT_H__ +#include "hiredis_cluster/hircluster.h" + +static int redisLibeventAttach_link(redisAsyncContext *ac, void *base) { + return redisLibeventAttach(ac, (struct event_base *)base); +} + +static int redisClusterLibeventAttach(redisClusterAsyncContext *acc, + struct event_base *base) { + + if (acc == NULL || base == NULL) { + return REDIS_ERR; + } + + acc->adapter = base; + acc->attach_fn = redisLibeventAttach_link; + + return REDIS_OK; +} + +#endif From beb0a77b2396f84aac18622970290d17c5c1171e Mon Sep 17 00:00:00 2001 From: plainbanana Date: Tue, 11 Feb 2025 20:08:12 +0900 Subject: [PATCH 02/13] feat: Update run_event_loop to be non-blocking. Refactored the `run_event_loop` method to exit immediately when no events are available, ensuring it operates in non-blocking mode. --- README.md | 16 +++++++--------- lib/Redis/Cluster/Fast.pm | 14 ++++++-------- src/Fast.xs | 2 +- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index e04fd3d..4152277 100644 --- a/README.md +++ b/README.md @@ -167,8 +167,7 @@ do not execute fork() without issuing `disconnect` if all callbacks are not exec ## run\_event\_loop() -This method allows you to issue commands without waiting for their responses. -You can then perform a blocking wait for those responses later, if needed. +This method is nonblocking and allows you to issue commands without waiting for their responses. Executes one iteration of the event loop to process any pending commands that have not yet been sent and any incoming responses from Redis. @@ -176,19 +175,18 @@ and any incoming responses from Redis. If there are events that can be triggered immediately, they will all be processed. In other words, if there are unsent commands, they will be pipelined and sent, and if there are already-received responses, their corresponding callbacks will be executed. +When a timeout occurs, an error will be propagated to the corresponding callback(s). If there are no events that can be triggered immediately: there are neither unsent commands nor any Redis responses available to read, -but unprocessed callbacks remain, then this method will block for up to `command_timeout` while waiting for a response from Redis. -When a timeout occurs, an error will be propagated to the corresponding callback(s). +but unprocessed callbacks remain, then this method will return immediately with success. -The return value can be either 1 for success (e.g., commands sent or responses read), +The return value can be either 1 for success (e.g., commands sent, responses read, or no immediately events), 0 for no callbacks remained, or undef for other errors. ### Notes -- Be aware that the timeout check will only be triggered when there are neither unsent commands nor Redis responses available to read. -If a timeout occurs, all remaining commands on that node will time out as well. -- Internally, this method calls `event_base_loop(..., EVLOOP_ONCE)`, which +- If a timeout occurs, all remaining commands on that node will time out as well. +- Internally, this method calls `event_base_loop(..., EVLOOP_ONCE | EVLOOP_NONBLOCK)`, which performs a single iteration of the event loop. A command will not be fully processed in a single call. - If you need to process multiple commands or wait for all responses, call this method repeatedly or use `wait_all_responses`. @@ -205,7 +203,7 @@ pending commands are processed, see `wait_all_responses`. # Send commands to Redis without waiting for responses $redis->run_event_loop(); - # Possibly wait for responses + # If any responses are available, read them immediately without waiting for the rest $redis->run_event_loop(); ## wait\_one\_response() diff --git a/lib/Redis/Cluster/Fast.pm b/lib/Redis/Cluster/Fast.pm index 37bae37..a4dd659 100644 --- a/lib/Redis/Cluster/Fast.pm +++ b/lib/Redis/Cluster/Fast.pm @@ -301,8 +301,7 @@ do not execute fork() without issuing C if all callbacks are not exe =head2 run_event_loop() -This method allows you to issue commands without waiting for their responses. -You can then perform a blocking wait for those responses later, if needed. +This method is nonblocking and allows you to issue commands without waiting for their responses. Executes one iteration of the event loop to process any pending commands that have not yet been sent and any incoming responses from Redis. @@ -310,12 +309,12 @@ and any incoming responses from Redis. If there are events that can be triggered immediately, they will all be processed. In other words, if there are unsent commands, they will be pipelined and sent, and if there are already-received responses, their corresponding callbacks will be executed. +When a timeout occurs, an error will be propagated to the corresponding callback(s). If there are no events that can be triggered immediately: there are neither unsent commands nor any Redis responses available to read, -but unprocessed callbacks remain, then this method will block for up to C while waiting for a response from Redis. -When a timeout occurs, an error will be propagated to the corresponding callback(s). +but unprocessed callbacks remain, then this method will return immediately with success. -The return value can be either 1 for success (e.g., commands sent or responses read), +The return value can be either 1 for success (e.g., commands sent, responses read, or no immediately events), 0 for no callbacks remained, or undef for other errors. =head3 Notes @@ -324,12 +323,11 @@ The return value can be either 1 for success (e.g., commands sent or responses r =item * -Be aware that the timeout check will only be triggered when there are neither unsent commands nor Redis responses available to read. If a timeout occurs, all remaining commands on that node will time out as well. =item * -Internally, this method calls C, which +Internally, this method calls C, which performs a single iteration of the event loop. A command will not be fully processed in a single call. =item * @@ -354,7 +352,7 @@ pending commands are processed, see C. # Send commands to Redis without waiting for responses $redis->run_event_loop(); - # Possibly wait for responses + # If any responses are available, read them immediately without waiting for the rest $redis->run_event_loop(); =head2 wait_one_response() diff --git a/src/Fast.xs b/src/Fast.xs index 70e42c7..d294cb6 100644 --- a/src/Fast.xs +++ b/src/Fast.xs @@ -473,7 +473,7 @@ int Redis__Cluster__Fast_run_event_loop(pTHX_ Redis__Cluster__Fast self) { return 0; } DEBUG_EVENT_BASE(); - event_loop_error = event_base_loop(self->cluster_event_base, EVLOOP_ONCE); + event_loop_error = event_base_loop(self->cluster_event_base, EVLOOP_ONCE | EVLOOP_NONBLOCK); if (event_loop_error != 0) { return -1; } From cbb0e1c602389830dfd5186550c1aa56fc4e6720 Mon Sep 17 00:00:00 2001 From: plainbanana Date: Fri, 7 Mar 2025 12:31:39 +0900 Subject: [PATCH 03/13] fix: set priority when ev_io updates --- src/adapters/libevent.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/adapters/libevent.h b/src/adapters/libevent.h index 55bd715..8265cd1 100644 --- a/src/adapters/libevent.h +++ b/src/adapters/libevent.h @@ -114,6 +114,7 @@ static void redisLibeventUpdate(void *privdata, short flag, int isRemove) { event_del(e->ev_io); event_assign(e->ev_io, e->base, e->context->c.fd, e->flags | EV_PERSIST, redisLibeventHandler, privdata); + event_priority_set(e->ev_io, 0); event_add(e->ev_io, NULL); } From 725fee0bd3771dbcf396b58d5d581ed8532ac1b2 Mon Sep 17 00:00:00 2001 From: plainbanana Date: Fri, 7 Mar 2025 15:26:23 +0900 Subject: [PATCH 04/13] refactor: remove redundant flag Removed the EVLOOP_ONCE flag from the event loop call as it is unnecessary when used with EVLOOP_NONBLOCK. This change simplifies the code and ensures clearer handling of the event loop behavior. --- src/Fast.xs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Fast.xs b/src/Fast.xs index d294cb6..512c43e 100644 --- a/src/Fast.xs +++ b/src/Fast.xs @@ -473,7 +473,7 @@ int Redis__Cluster__Fast_run_event_loop(pTHX_ Redis__Cluster__Fast self) { return 0; } DEBUG_EVENT_BASE(); - event_loop_error = event_base_loop(self->cluster_event_base, EVLOOP_ONCE | EVLOOP_NONBLOCK); + event_loop_error = event_base_loop(self->cluster_event_base, EVLOOP_NONBLOCK); if (event_loop_error != 0) { return -1; } From 93bc71ea5ff6391f320a7280e2a4bf222d2abb94 Mon Sep 17 00:00:00 2001 From: plainbanana Date: Fri, 13 Jun 2025 15:30:44 +0900 Subject: [PATCH 05/13] test: call wait_one_response instead of run_event_loop Replaced a duplicate `run_event_loop` call with `wait_one_response` for better clarity and correctness. This ensures the desired response is awaited effectively within the test. --- xt/01_simple.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xt/01_simple.t b/xt/01_simple.t index 219b7b4..29a6c1f 100644 --- a/xt/01_simple.t +++ b/xt/01_simple.t @@ -149,7 +149,7 @@ for my $case ( is $result, 12345; }); ok $redis->run_event_loop; - ok $redis->run_event_loop; + ok $redis->wait_one_response; is $redis->run_event_loop, 0; } From 07050846e33f5bf491fd7c9e4f581f388528c4a7 Mon Sep 17 00:00:00 2001 From: plainbanana Date: Fri, 13 Jun 2025 15:33:18 +0900 Subject: [PATCH 06/13] nits: upgrade minilla --- META.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/META.json b/META.json index 8edcab6..d01a451 100644 --- a/META.json +++ b/META.json @@ -4,7 +4,7 @@ "plainbanana " ], "dynamic_config" : 0, - "generated_by" : "Minilla/v3.1.25, CPAN::Meta::Converter version 2.150010", + "generated_by" : "Minilla/v3.1.26, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], From bebc0e76cb78fdab37e6424499c1dd95a77b2742 Mon Sep 17 00:00:00 2001 From: plainbanana Date: Fri, 13 Jun 2025 15:46:22 +0900 Subject: [PATCH 07/13] docs: simplify method descriptions Streamlined documentation for `run_event_loop` by removing redundant details about event processing and internal mechanics. Focused on clarifying return values, command handling, and timeout behavior for better readability. --- README.md | 16 +++------------- lib/Redis/Cluster/Fast.pm | 22 +++------------------- 2 files changed, 6 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 4152277..6ee5484 100644 --- a/README.md +++ b/README.md @@ -169,27 +169,17 @@ do not execute fork() without issuing `disconnect` if all callbacks are not exec This method is nonblocking and allows you to issue commands without waiting for their responses. -Executes one iteration of the event loop to process any pending commands that have not yet been sent -and any incoming responses from Redis. - -If there are events that can be triggered immediately, they will all be processed. -In other words, if there are unsent commands, they will be pipelined and sent, +If there are unsent commands, they will be pipelined and sent, and if there are already-received responses, their corresponding callbacks will be executed. When a timeout occurs, an error will be propagated to the corresponding callback(s). -If there are no events that can be triggered immediately: there are neither unsent commands nor any Redis responses available to read, -but unprocessed callbacks remain, then this method will return immediately with success. - -The return value can be either 1 for success (e.g., commands sent, responses read, or no immediately events), +The return value can be either 1 for success +(e.g., commands sent, responses read, or exit without waiting for any responses), 0 for no callbacks remained, or undef for other errors. ### Notes - If a timeout occurs, all remaining commands on that node will time out as well. -- Internally, this method calls `event_base_loop(..., EVLOOP_ONCE | EVLOOP_NONBLOCK)`, which -performs a single iteration of the event loop. A command will not be fully processed in a single call. -- If you need to process multiple commands or wait for all responses, call -this method repeatedly or use `wait_all_responses`. - For a simpler, synchronous-like usage where you need at least one response, refer to `wait_one_response`. If you only need to block until all pending commands are processed, see `wait_all_responses`. diff --git a/lib/Redis/Cluster/Fast.pm b/lib/Redis/Cluster/Fast.pm index a4dd659..8edf01e 100644 --- a/lib/Redis/Cluster/Fast.pm +++ b/lib/Redis/Cluster/Fast.pm @@ -303,18 +303,12 @@ do not execute fork() without issuing C if all callbacks are not exe This method is nonblocking and allows you to issue commands without waiting for their responses. -Executes one iteration of the event loop to process any pending commands that have not yet been sent -and any incoming responses from Redis. - -If there are events that can be triggered immediately, they will all be processed. -In other words, if there are unsent commands, they will be pipelined and sent, +If there are unsent commands, they will be pipelined and sent, and if there are already-received responses, their corresponding callbacks will be executed. When a timeout occurs, an error will be propagated to the corresponding callback(s). -If there are no events that can be triggered immediately: there are neither unsent commands nor any Redis responses available to read, -but unprocessed callbacks remain, then this method will return immediately with success. - -The return value can be either 1 for success (e.g., commands sent, responses read, or no immediately events), +The return value can be either 1 for success +(e.g., commands sent, responses read, or exit without waiting for any responses), 0 for no callbacks remained, or undef for other errors. =head3 Notes @@ -327,16 +321,6 @@ If a timeout occurs, all remaining commands on that node will time out as well. =item * -Internally, this method calls C, which -performs a single iteration of the event loop. A command will not be fully processed in a single call. - -=item * - -If you need to process multiple commands or wait for all responses, call -this method repeatedly or use C. - -=item * - For a simpler, synchronous-like usage where you need at least one response, refer to C. If you only need to block until all pending commands are processed, see C. From 2aeef4ad8392efd65ee6ac64397b2d32b85e71e4 Mon Sep 17 00:00:00 2001 From: plainbanana Date: Fri, 13 Jun 2025 16:50:39 +0900 Subject: [PATCH 08/13] fix: add error check for event_base_priority_init Check the return value of event_base_priority_init and return an error if the initialization fails. This improves error handling during the connection setup phase. --- src/Fast.xs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Fast.xs b/src/Fast.xs index 512c43e..99ae286 100644 --- a/src/Fast.xs +++ b/src/Fast.xs @@ -273,7 +273,11 @@ SV *Redis__Cluster__Fast_connect(pTHX_ Redis__Cluster__Fast self) { } self->cluster_event_base = event_base_new(); - event_base_priority_init(self->cluster_event_base, EVENT_BASE_PRIORITY_NUMBER); + + if (event_base_priority_init(self->cluster_event_base, EVENT_BASE_PRIORITY_NUMBER) != 0) { + return newSVpvf("%s", "failed to initialize event base priorities"); + } + if (redisClusterLibeventAttach(self->acc, self->cluster_event_base) != REDIS_OK) { return newSVpvf("%s", "failed to attach event base"); } From c0493c73f6e006125f86997f9033cab37ec74b84 Mon Sep 17 00:00:00 2001 From: plainbanana Date: Fri, 13 Jun 2025 16:56:10 +0900 Subject: [PATCH 09/13] docs: add comments explaining event base priority levels Added detailed comments to explain the priority system for libevent: - Priority 0 (highest): I/O events for immediate Redis response processing - Priority 1 (lower): Timer events for timeout handling after I/O processing This clarifies why I/O events are prioritized over timer events in the event processing loop. --- src/Fast.xs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Fast.xs b/src/Fast.xs index 99ae286..4088058 100644 --- a/src/Fast.xs +++ b/src/Fast.xs @@ -26,6 +26,10 @@ extern "C" { #define NANO_SECOND_TO_MICRO 1000 #define MIN_ATTEMPT_TO_GET_RESULT 2 + +// Event priorities: +// Priority 0: I/O events (highest priority) - Process Redis responses immediately +// Priority 1: Timer events (lower priority) - Handle timeouts after I/O processing #define EVENT_BASE_PRIORITY_NUMBER 2 #define DEBUG_MSG(fmt, ...) \ From 98f6b45186fe4f6b3c7b52a6cebb06570b7fad5f Mon Sep 17 00:00:00 2001 From: plainbanana Date: Fri, 13 Jun 2025 18:41:15 +0900 Subject: [PATCH 10/13] docs: fix typos in method descriptions Corrected the spelling of "unexcuted" to "unexecuted" in method documentation. This improves clarity and accuracy in the code comments. --- README.md | 4 ++-- lib/Redis/Cluster/Fast.pm | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6ee5484..a1119fd 100644 --- a/README.md +++ b/README.md @@ -198,12 +198,12 @@ pending commands are processed, see `wait_all_responses`. ## wait\_one\_response() -If there are any unexcuted callbacks, it will block until at least one is executed. +If there are any unexecuted callbacks, it will block until at least one is executed. The return value can be either 1 for success, 0 for no callbacks remained, or undef for other errors. ## wait\_all\_responses() -If there are any unexcuted callbacks, it will block until all of them are executed. +If there are any unexecuted callbacks, it will block until all of them are executed. The return value can be either 1 for success, 0 for no callbacks remained, or undef for other errors. ## disconnect() diff --git a/lib/Redis/Cluster/Fast.pm b/lib/Redis/Cluster/Fast.pm index 8edf01e..fecd157 100644 --- a/lib/Redis/Cluster/Fast.pm +++ b/lib/Redis/Cluster/Fast.pm @@ -341,12 +341,12 @@ pending commands are processed, see C. =head2 wait_one_response() -If there are any unexcuted callbacks, it will block until at least one is executed. +If there are any unexecuted callbacks, it will block until at least one is executed. The return value can be either 1 for success, 0 for no callbacks remained, or undef for other errors. =head2 wait_all_responses() -If there are any unexcuted callbacks, it will block until all of them are executed. +If there are any unexecuted callbacks, it will block until all of them are executed. The return value can be either 1 for success, 0 for no callbacks remained, or undef for other errors. =head2 disconnect() From fdab172f63adbdaef87e8caf82cada6bc58ae109 Mon Sep 17 00:00:00 2001 From: plainbanana Date: Wed, 24 Sep 2025 00:54:32 +0900 Subject: [PATCH 11/13] docs: update comment style for events Replaced single-line comments with multi-line block comments for event priority explanations. --- src/Fast.xs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Fast.xs b/src/Fast.xs index 4088058..cf11afe 100644 --- a/src/Fast.xs +++ b/src/Fast.xs @@ -27,9 +27,9 @@ extern "C" { #define MIN_ATTEMPT_TO_GET_RESULT 2 -// Event priorities: -// Priority 0: I/O events (highest priority) - Process Redis responses immediately -// Priority 1: Timer events (lower priority) - Handle timeouts after I/O processing +/* Event priorities: + Priority 0: I/O events (highest priority) - Process Redis responses immediately + Priority 1: Timer events (lower priority) - Handle timeouts after I/O processing */ #define EVENT_BASE_PRIORITY_NUMBER 2 #define DEBUG_MSG(fmt, ...) \ From d6c732b05baaa6783ed7d2044fc6db70822b5991 Mon Sep 17 00:00:00 2001 From: plainbanana Date: Wed, 24 Sep 2025 02:04:34 +0900 Subject: [PATCH 12/13] refactor: reorder variable initialization Moved variable initialization before unused parameter casting for consistency and clarity. --- src/adapters/libevent.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/adapters/libevent.h b/src/adapters/libevent.h index 8265cd1..837a532 100644 --- a/src/adapters/libevent.h +++ b/src/adapters/libevent.h @@ -51,8 +51,8 @@ static void redisLibeventDestroy(redisLibeventEvents *e) { } static void redisLibeventHandler(evutil_socket_t fd, short event, void *arg) { - ((void)fd); redisLibeventEvents *e = (redisLibeventEvents*)arg; + ((void)fd); e->state |= REDIS_LIBEVENT_ENTERED; #define CHECK_DELETED() if (e->state & REDIS_LIBEVENT_DELETED) {\ @@ -75,9 +75,9 @@ static void redisLibeventHandler(evutil_socket_t fd, short event, void *arg) { } static void redisLibeventTimerHandler(evutil_socket_t fd, short event, void *arg) { + redisLibeventEvents *e = (redisLibeventEvents*)arg; ((void)fd); ((void)event); - redisLibeventEvents *e = (redisLibeventEvents*)arg; e->state |= REDIS_LIBEVENT_ENTERED; #define CHECK_DELETED() if (e->state & REDIS_LIBEVENT_DELETED) {\ From 6e3568cb28bed5198289b6a6e1b13cc46a714c26 Mon Sep 17 00:00:00 2001 From: plainbanana Date: Thu, 25 Sep 2025 03:28:08 +0900 Subject: [PATCH 13/13] docs: enhance event priority comments Improved the explanation of libevent priority levels in the comments. Clarified the purpose of priorities 0 and 1 and added context on total priority configuration with `EVENT_BASE_PRIORITY_NUMBER`. --- src/Fast.xs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Fast.xs b/src/Fast.xs index cf11afe..61b2768 100644 --- a/src/Fast.xs +++ b/src/Fast.xs @@ -27,9 +27,11 @@ extern "C" { #define MIN_ATTEMPT_TO_GET_RESULT 2 -/* Event priorities: - Priority 0: I/O events (highest priority) - Process Redis responses immediately - Priority 1: Timer events (lower priority) - Handle timeouts after I/O processing */ +/* libevent adapter priority configuration + Uses 2 priority levels to ensure I/O events are processed before timeouts: + - Priority 0: I/O events (Redis responses) - highest priority + - Priority 1: Timer events (timeouts) - lower priority + EVENT_BASE_PRIORITY_NUMBER sets the total priorities for event_base_priority_init() */ #define EVENT_BASE_PRIORITY_NUMBER 2 #define DEBUG_MSG(fmt, ...) \