PR:https://github.com/MicrosoftArchive/redis/pull/598
錯誤描述與處理辦法
用redis-cli連接服務端時六敬,錯誤提示好像是Could not connect to Redis at xxx:poll(2)瑟俭,事實上使用微軟的redis on windows編譯的Win32_Interop庫在window版本低于6.0時都不能連接上redis server肮街。
先說結(jié)論吧沉眶,造成bug的原因是低版本時調(diào)用select函數(shù)前多進行了一次rfd(redis的文件描述符)與socket的映射保礼,解決方案是
- 把Win32_FDAPI.cpp的1005~1019行刪除(FDAPI_select函數(shù)的三個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工具集編譯時出現(xiàn)了一個問題色查,WS2tcpip.h的第48~51行的預處理并沒有起作用薯演,無法包含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ù)進行了一次RFD到socket的映射验毡,此時pollCopy[n].fd已經(jīng)是socket句柄并在682~684行復制到fd_set中衡创,當WindowsVersion低于6.0時作為WSAPoll的替代,在688行FDAPI_poll調(diào)用了FDAPI_select晶通;
- 但在FDAPI_select中璃氢,調(diào)用select之前又進行了一次多余的映射(1005~1019行),試圖通過RFDMap來從RFD映射到socket狮辽,但由于此時的RFD已經(jīng)是socket了一也,因為RFDMap中并不存在這些key巢寡,結(jié)果就是所有的socket都是INVALID_SOCKET,導致無法成功連接到redis服務器椰苟。
- 在677行抑月,原redis代碼用pollCopy[i].fd來判斷socket數(shù)量是否超過64的邏輯也是錯誤的,因為經(jīng)過一次映射后pollCopy[i].fd代表的是socket舆蝴,而socket函數(shù)返回的是OS文件描述符谦絮,超過64很正常,應當用fds[i].fd(也就是rfd)來判斷洁仗。另外這部分的check我認為是多余的层皱,也可以直接刪除,因為在667~670行已經(jīng)判斷過了京痢。
在redis中rfd和OS類似奶甘,0~2也是被占用的,新的fd從3開始祭椰,因此實際上的合法socket數(shù)量不超過62臭家。 - 吐槽:我覺得select部分和poll部分肯定不是同一個人寫的,所以寫select的人真的很不認真方淤,把fds和pollCopy的涵義完全搞反了并且還沒有進行測試钉赁。或者寫poll的人也許應該把映射部分放在分支內(nèi)進行携茂?就是多了一部分重復代碼你踩。
p.s. 這個PR是不可能merge了,提個PR做提醒也好讳苦,另外這個人好像有在維護這個坑带膜,鏈接在這里,但是沒有修復這個bug(2018.05.14)鸳谜,我也懶得再提一次PR了:https://github.com/tporadowski/redis