PR:https://github.com/MicrosoftArchive/redis/pull/598
錯(cuò)誤描述與處理辦法
用redis-cli連接服務(wù)端時(shí),錯(cuò)誤提示好像是Could not connect to Redis at xxx:poll(2),事實(shí)上使用微軟的redis on windows編譯的Win32_Interop庫在window版本低于6.0時(shí)都不能連接上redis server。
先說結(jié)論吧,造成bug的原因是低版本時(shí)調(diào)用select函數(shù)前多進(jìn)行了一次rfd(redis的文件描述符)與socket的映射,解決方案是
- 把Win32_FDAPI.cpp的1005~1019行刪除(FDAPI_select函數(shù)的三個(gè)if代碼塊);
- 674行的
fds[i].fd == INVALID_SOCKET
改為pollCopy[i].fd == INVALID_SOCKET
; - 677行的
if (pollCopy[i].fd >= FD_SETSIZE)
改為if (fds[i].fd >= FD_SETSIZE)
,或者直接刪除677~680行; - 重新編譯Win32_Interop,hiredis和RedisCli即可。
順便一提我在使用xp工具集編譯時(shí)出現(xiàn)了一個(gè)問題,WS2tcpip.h的第48~51行的預(yù)處理并沒有起作用,無法包含winapifamily.h,于是我把它直接改為了#include "win32_winapifamily.h"。
問題分析
先來看一下相關(guān)部分的源碼:
FDAPI_poll函數(shù)
FDAPI_poll函數(shù),注意667到680行的合法性檢查.png
原FDAPI_select函數(shù)
- 在635行,F(xiàn)DAPI_poll函數(shù)進(jìn)行了一次RFD到socket的映射,此時(shí)pollCopy[n].fd已經(jīng)是socket句柄并在682~684行復(fù)制到fd_set中,當(dāng)WindowsVersion低于6.0時(shí)作為WSAPoll的替代,在688行FDAPI_poll調(diào)用了FDAPI_select;
- 但在FDAPI_select中,調(diào)用select之前又進(jìn)行了一次多余的映射(1005~1019行),試圖通過RFDMap來從RFD映射到socket,但由于此時(shí)的RFD已經(jīng)是socket了,因?yàn)镽FDMap中并不存在這些key,結(jié)果就是所有的socket都是INVALID_SOCKET,導(dǎo)致無法成功連接到redis服務(wù)器。
- 在677行,原redis代碼用pollCopy[i].fd來判斷socket數(shù)量是否超過64的邏輯也是錯(cuò)誤的,因?yàn)榻?jīng)過一次映射后pollCopy[i].fd代表的是socket,而socket函數(shù)返回的是OS文件描述符,超過64很正常,應(yīng)當(dāng)用fds[i].fd(也就是rfd)來判斷。另外這部分的check我認(rèn)為是多余的,也可以直接刪除,因?yàn)樵?67~670行已經(jīng)判斷過了。
在redis中rfd和OS類似,0~2也是被占用的,新的fd從3開始,因此實(shí)際上的合法socket數(shù)量不超過62。 - 吐槽:我覺得select部分和poll部分肯定不是同一個(gè)人寫的,所以寫select的人真的很不認(rèn)真,把fds和pollCopy的涵義完全搞反了并且還沒有進(jìn)行測(cè)試。或者寫poll的人也許應(yīng)該把映射部分放在分支內(nèi)進(jìn)行?就是多了一部分重復(fù)代碼。
p.s. 這個(gè)PR是不可能merge了,提個(gè)PR做提醒也好,另外這個(gè)人好像有在維護(hù)這個(gè)坑,鏈接在這里,但是沒有修復(fù)這個(gè)bug(2018.05.14),我也懶得再提一次PR了:https://github.com/tporadowski/redis