@@ -174,6 +174,7 @@ client *createClient(int fd, int iel) {
174174 c->bufAsync = NULL ;
175175 c->buflenAsync = 0 ;
176176 c->bufposAsync = 0 ;
177+ c->casyncOpsPending = 0 ;
177178 memset (c->uuid , 0 , UUID_BINARY_LEN);
178179
179180 listSetFreeMethod (c->pubsub_patterns ,decrRefCountVoid);
@@ -1003,7 +1004,6 @@ static void acceptCommonHandler(int fd, int flags, char *ip, int iel) {
10031004 serverLog (LL_WARNING,
10041005 " Error registering fd event for the new client: %s (fd=%d)" ,
10051006 strerror (errno),fd);
1006- close (fd); /* May be already closed, just ignore errors */
10071007 return ;
10081008 }
10091009
@@ -1266,17 +1266,17 @@ void unlinkClient(client *c) {
12661266 }
12671267}
12681268
1269- void freeClient (client *c) {
1269+ bool freeClient (client *c) {
12701270 listNode *ln;
12711271 serverAssert (c->fd == -1 || GlobalLocksAcquired ());
12721272 AssertCorrectThread (c);
12731273 std::unique_lock<decltype (c->lock )> ulock (c->lock );
12741274
12751275 /* If a client is protected, yet we need to free it right now, make sure
12761276 * to at least use asynchronous freeing. */
1277- if (c->flags & CLIENT_PROTECTED) {
1277+ if (c->flags & CLIENT_PROTECTED || c-> casyncOpsPending ) {
12781278 freeClientAsync (c);
1279- return ;
1279+ return false ;
12801280 }
12811281
12821282 /* If it is our master that's beging disconnected we should make sure
@@ -1291,7 +1291,7 @@ void freeClient(client *c) {
12911291 CLIENT_BLOCKED)))
12921292 {
12931293 replicationCacheMaster (MasterInfoFromClient (c), c);
1294- return ;
1294+ return false ;
12951295 }
12961296 }
12971297
@@ -1370,6 +1370,7 @@ void freeClient(client *c) {
13701370 ulock.unlock ();
13711371 fastlock_free (&c->lock );
13721372 zfree (c);
1373+ return true ;
13731374}
13741375
13751376/* Schedule a client to free it at a safe time in the serverCron() function.
@@ -1382,28 +1383,37 @@ void freeClientAsync(client *c) {
13821383 * may access the list while Redis uses I/O threads. All the other accesses
13831384 * are in the context of the main thread while the other threads are
13841385 * idle. */
1385- if (c->flags & CLIENT_CLOSE_ASAP || c->flags & CLIENT_LUA) return ;
1386+ if (c->flags & CLIENT_CLOSE_ASAP || c->flags & CLIENT_LUA) return ; // check without the lock first
13861387 std::lock_guard<decltype (c->lock )> clientlock (c->lock );
13871388 AeLocker lock;
13881389 lock.arm (c);
1390+ if (c->flags & CLIENT_CLOSE_ASAP || c->flags & CLIENT_LUA) return ; // race condition after we acquire the lock
13891391 c->flags |= CLIENT_CLOSE_ASAP;
13901392 listAddNodeTail (g_pserver->clients_to_close ,c);
13911393}
13921394
13931395void freeClientsInAsyncFreeQueue (int iel) {
1396+ serverAssert (GlobalLocksAcquired ());
13941397 listIter li;
13951398 listNode *ln;
13961399 listRewind (g_pserver->clients_to_close ,&li);
13971400
1398- while ((ln = listNext (&li))) {
1401+ // Store the clients in a temp vector since freeClient will modify this list
1402+ std::vector<client*> vecclientsFree;
1403+ while ((ln = listNext (&li)))
1404+ {
13991405 client *c = (client*)listNodeValue (ln);
1400- if (c->iel != iel)
1401- continue ; // wrong thread
1406+ if (c->iel == iel)
1407+ {
1408+ vecclientsFree.push_back (c);
1409+ listDelNode (g_pserver->clients_to_close , ln);
1410+ }
1411+ }
14021412
1413+ for (client *c : vecclientsFree)
1414+ {
14031415 c->flags &= ~CLIENT_CLOSE_ASAP;
14041416 freeClient (c);
1405- listDelNode (g_pserver->clients_to_close ,ln);
1406- listRewind (g_pserver->clients_to_close ,&li);
14071417 }
14081418}
14091419
@@ -1551,6 +1561,15 @@ void ProcessPendingAsyncWrites()
15511561 std::lock_guard<decltype (c->lock )> lock (c->lock );
15521562
15531563 serverAssert (c->fPendingAsyncWrite );
1564+ if (c->flags & (CLIENT_CLOSE_ASAP | CLIENT_CLOSE_AFTER_REPLY))
1565+ {
1566+ c->bufposAsync = 0 ;
1567+ c->buflenAsync = 0 ;
1568+ zfree (c->bufAsync );
1569+ c->bufAsync = nullptr ;
1570+ c->fPendingAsyncWrite = FALSE ;
1571+ continue ;
1572+ }
15541573
15551574 // TODO: Append to end of reply block?
15561575
@@ -1587,8 +1606,36 @@ void ProcessPendingAsyncWrites()
15871606 continue ;
15881607
15891608 asyncCloseClientOnOutputBufferLimitReached (c);
1590- if (aeCreateRemoteFileEvent (g_pserver->rgthreadvar [c->iel ].el , c->fd , ae_flags, sendReplyToClient, c, FALSE ) == AE_ERR)
1591- continue ; // We can retry later in the cron
1609+ if (c->flags & CLIENT_CLOSE_ASAP)
1610+ continue ; // we will never write this so don't post an op
1611+
1612+ std::atomic_thread_fence (std::memory_order_seq_cst);
1613+
1614+ if (c->casyncOpsPending == 0 )
1615+ {
1616+ if (FCorrectThread (c))
1617+ {
1618+ prepareClientToWrite (c, false ); // queue an event
1619+ }
1620+ else
1621+ {
1622+ // We need to start the write on the client's thread
1623+ if (aePostFunction (g_pserver->rgthreadvar [c->iel ].el , [c]{
1624+ // Install a write handler. Don't do the actual write here since we don't want
1625+ // to duplicate the throttling and safety mechanisms of the normal write code
1626+ std::lock_guard<decltype (c->lock )> lock (c->lock );
1627+ serverAssert (c->casyncOpsPending > 0 );
1628+ c->casyncOpsPending --;
1629+ aeCreateFileEvent (g_pserver->rgthreadvar [c->iel ].el , c->fd , AE_WRITABLE|AE_WRITE_THREADSAFE, sendReplyToClient, c);
1630+ }, false ) == AE_ERR
1631+ )
1632+ {
1633+ // Posting the function failed
1634+ continue ; // We can retry later in the cron
1635+ }
1636+ ++c->casyncOpsPending ; // race is handled by the client lock in the lambda
1637+ }
1638+ }
15921639 }
15931640}
15941641
@@ -1628,13 +1675,15 @@ int handleClientsWithPendingWrites(int iel) {
16281675 std::unique_lock<decltype (c->lock )> lock (c->lock );
16291676
16301677 /* Try to write buffers to the client socket. */
1631- if (writeToClient (c->fd ,c,0 ) == C_ERR) {
1678+ if (writeToClient (c->fd ,c,0 ) == C_ERR)
1679+ {
16321680 if (c->flags & CLIENT_CLOSE_ASAP)
16331681 {
16341682 lock.release (); // still locked
16351683 AeLocker ae;
16361684 ae.arm (c);
1637- freeClient (c); // writeToClient will only async close, but there's no need to wait
1685+ if (!freeClient (c)) // writeToClient will only async close, but there's no need to wait
1686+ c->lock .unlock (); // if we just got put on the async close list, then we need to remove the lock
16381687 }
16391688 continue ;
16401689 }
0 commit comments