Page 1 of 1

Workaround for bugs in NetworkClientEvent()

Posted: Thu Apr 11, 2013 2:00 pm
by DarkPlayer
Hi,

recently I wrote a small HTTP library only with PureBasic commands, but unfortunately I discovered several bugs in the implementations of NetworkClientEvent on all platforms when trying to detect the disconnect event. I had no time to track them down all, but in the meantime you can use the following workaround code, which should fix the issues and is based on http://www.purebasic.fr/english/viewtop ... 12&t=42559. This code overwrites the internal PureBasic NetworkClientEvent command with a working implementation only based on API calls...

Tested on the following platforms:

- Windows 7, 64 Bit OS, Program 32 Bit: The PureBasic internal implementation detects disconnect events, even if the connection is still alive? You can test it by running my HTTP library code and download a large file. My implementation doesnt have this issue.

- Linux 64 Bit: The PureBasic internal implementation doesn't detect disconnect events at all? At least it is not working on my PC. My own implementation works perfectly.

- MacOS X Snow Leopard 32 Bit: Same bug as Linux 64 Bit, my own implementation works.

I am wondering why noone else has discovered this critical issue yet.

Here is the workaround code, which should be included before all other network commands:

Code: Select all

; Client Disconnect Event workaround fix by Darkplayer, PureFan

;EnableExplicit

CompilerIf #PB_Compiler_OS = #PB_OS_Linux ;{
  #FIONREAD     = $541B
  
  #__FD_SETSIZE = 1024
  #__NFDBITS    = 8 * SizeOf(INTEGER)
  
  Macro __FDELT(d)
    ((d) / #__NFDBITS)
  EndMacro
  
  Macro __FDMASK(d)
    (1 << ((d) % #__NFDBITS))
  EndMacro
  
  Structure FD_SET
    fds_bits.i[#__FD_SETSIZE / #__NFDBITS]
  EndStructure
  
  Procedure.i __FD_SET(d.i, *set.FD_SET)
    If d >= 0 And d < #__FD_SETSIZE
      *set\fds_bits[__FDELT(d)] | __FDMASK(d)
    EndIf
  EndProcedure
  
  Procedure.i __FD_ISSET(d.i, *set.FD_SET)
    If d >= 0 And d < #__FD_SETSIZE
      ProcedureReturn *set\fds_bits[__FDELT(d)] & __FDMASK(d)
    EndIf
  EndProcedure
  
  Procedure.i __FD_ZERO(*set.FD_SET)
    FillMemory(*set, SizeOf(FD_SET), 0, #PB_Byte)
  EndProcedure
  
  
  #FD_SETSIZE = #__FD_SETSIZE
  #NFDBITS    = #__NFDBITS
  
  Macro FD_SET(fd, fdsetp)
    __FD_SET(fd, fdsetp)
  EndMacro
  
  Macro FD_ISSET(fd, fdsetp)
    __FD_ISSET(fd, fdsetp)
  EndMacro
  
  Macro FD_ZERO(fdsetp)
    __FD_ZERO(fdsetp)
  EndMacro 
  
  ; Returns the minimum value for NFDS
  Procedure.i _NFDS(*set.FD_SET)
    Protected I.i, J.i
    
    For I = SizeOf(FD_SET)/SizeOf(INTEGER) - 1 To 0 Step -1
      If *set\fds_bits[I]
        
        For J = (#__NFDBITS - 1) To 0 Step -1
          If *set\fds_bits[I] & (1 << J)
            ProcedureReturn I * #__NFDBITS + J + 1
          EndIf
        Next
        
      EndIf
    Next
    
    ProcedureReturn 0
  EndProcedure
  ;}
CompilerEndIf

CompilerIf #PB_Compiler_OS = #PB_OS_MacOS ;{
  #IOC_OUT  = $40000000 ;(__uint32_t)
  Macro _IOR(g,n,t)
    _IOC(#IOC_OUT, (g), (n), (t))
  EndMacro
  #IOCPARM_MASK = $1fff
  Macro _IOC(inout,group,num,len)
    ((inout) | (((len) & #IOCPARM_MASK) << 16) | ((group) << 8) | (num))
  EndMacro
  #FIONREAD = _IOR('f', 127, SizeOf(LONG))
  
  #__DARWIN_FD_SETSIZE = 1024
  #__DARWIN_NBBY       = 8
  #__DARWIN_NFDBITS    = SizeOf(LONG) * #__DARWIN_NBBY
  
  Structure FD_SET
    fds_bits.l[ (#__DARWIN_FD_SETSIZE + #__DARWIN_NFDBITS - 1) / #__DARWIN_NFDBITS ]
  EndStructure
  
  Procedure.i __DARWIN_FD_SET(fd.i, *p.FD_SET)
    If fd >= 0 And fd < #__DARWIN_FD_SETSIZE
      *p\fds_bits[fd / #__DARWIN_NFDBITS] | (1 << (fd % #__DARWIN_NFDBITS))
    EndIf
  EndProcedure
  
  Procedure.i __DARWIN_FD_ISSET(fd.i, *p.FD_SET)
    If fd >= 0 And fd < #__DARWIN_FD_SETSIZE
      ProcedureReturn *p\fds_bits[fd / #__DARWIN_NFDBITS] & (1 << (fd % #__DARWIN_NFDBITS))
    EndIf
  EndProcedure
  
  Procedure.i __DARWIN_FD_ZERO(*p.FD_SET)
    FillMemory(*p, SizeOf(FD_SET), 0, #PB_Byte)
  EndProcedure
  
  #FD_SETSIZE = #__DARWIN_FD_SETSIZE
  
  Macro FD_SET(n, p)
    __DARWIN_FD_SET(n, p)
  EndMacro
  
  Macro FD_ISSET(n, p)
    __DARWIN_FD_ISSET(n, p)
  EndMacro
  
  Macro FD_ZERO(p)
    __DARWIN_FD_ZERO(p)
  EndMacro
  
  ; Returns the minimum value for NFDS
  Procedure.i _NFDS(*p.FD_SET)
    Protected I.i, J.i
    
    For I = SizeOf(FD_SET)/SizeOf(LONG) - 1 To 0 Step -1
      If *p\fds_bits[I]
        
        For J = (#__DARWIN_NFDBITS - 1) To 0 Step -1
          If *p\fds_bits[I] & (1 << J)
            ProcedureReturn I * #__DARWIN_NFDBITS + J + 1
          EndIf
        Next
        
      EndIf
    Next
    
    ProcedureReturn 0
  EndProcedure
  ;}
CompilerEndIf

CompilerIf #PB_Compiler_OS = #PB_OS_Windows ;{
  ; #FIONREAD is already defined
  ; FD_SET is already defined
  
  Macro FD_ZERO(set)
    set\fd_count = 0
  EndMacro
  
  Procedure.i FD_SET(fd.i, *set.FD_SET)
    If *set\fd_count < #FD_SETSIZE
      *set\fd_array[ *set\fd_count ] = fd
      *set\fd_count + 1
    EndIf
  EndProcedure
  
  Procedure.i FD_ISSET(fd.i, *set.FD_SET)
    Protected I.i
    For I = *set\fd_count - 1 To 0 Step -1
      If *set\fd_array[I] = fd
        ProcedureReturn #True
      EndIf
    Next
    
    ProcedureReturn #False
  EndProcedure
  
  Procedure.i _NFDS(*set.FD_SET)
    ProcedureReturn *set\fd_count
  EndProcedure
  ;}
CompilerEndIf
  
CompilerIf Defined(TIMEVAL, #PB_Structure) = #False ;{
  Structure TIMEVAL
    tv_sec.l
    CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
      padding.l
   CompilerEndIf
    tv_usec.l
  EndStructure ;}
CompilerEndIf

Procedure.i Hook_NetworkClientEvent(Connection.i)
  Protected hSocket.i = ConnectionID(Connection)
  Protected tv.timeval, readfds.fd_set, result.i, length.i
  tv\tv_sec  = 0 ; Dont even wait, just query status
  tv\tv_usec = 0
  
  FD_ZERO(readfds)
  FD_SET(hSocket, readfds)
  
  ; Check if there is something new
  result = select_(_NFDS(readfds), @readfds, #Null, #Null, @tv)
  If result < 0 ; Seems to be an error
    ProcedureReturn #PB_NetworkEvent_Disconnect
  ElseIf result = 0 Or Not FD_ISSET(hSocket, readfds) ; No data available
    ProcedureReturn 0
  EndIf
  
  ; Check if data is available?
  CompilerIf #PB_Compiler_OS = #PB_OS_Windows
    result = ioctlsocket_(hSocket, #FIONREAD, @length)
  CompilerElse
    result = ioctl_(hSocket, #FIONREAD, @length)
  CompilerEndIf
  If result Or length = 0 ; Not successful to query for data available OR no data available ? This seems to be an error!
    ProcedureReturn #PB_NetworkEvent_Disconnect
  EndIf
  
  ProcedureReturn #PB_NetworkEvent_Data
EndProcedure

Macro NetworkClientEvent(Connection)
  Hook_NetworkClientEvent(Connection)
EndMacro
DarkPlayer

Re: Workaround for bugs in NetworkClientEvent()

Posted: Thu Apr 11, 2013 6:11 pm
by moogle
Thanks for your code DarkPlayer, I've mentioned this code before in a feature request somewhere so Fred could use it in the PB code and fix detection of disconnects but even though you've done the work and it's all there they haven't included it, maybe 10 more years and it'll be fixed? :lol:

Re: Workaround for bugs in NetworkClientEvent()

Posted: Fri Apr 12, 2013 10:29 pm
by Joakim Christiansen
+1
You're a hero!

Re: Workaround for bugs in NetworkClientEvent()

Posted: Tue May 07, 2013 6:41 am
by kinglestat
I did! I did!
But nobody listened
And did not imagine it could be fixed using PB itself.
Holy blazes...well done

Re: Workaround for bugs in NetworkClientEvent()

Posted: Fri Jun 13, 2014 6:47 am
by coco2
Thanks for this.

Re: Workaround for bugs in NetworkClientEvent()

Posted: Fri Jun 13, 2014 10:00 am
by Fred
coco2 wrote:Thanks for this.
Please don't put a quote like this on every old subject, it doesn't brings anything new. Thank you.

Re: Workaround for bugs in NetworkClientEvent()

Posted: Fri Jun 13, 2014 3:02 pm
by coco2
Appologies if I've caused any inconvenience but I will be fully testing this code and the supposed bug and reporting any findings. I'm currently working on a TLS module which is already thousands of lines of code and months of work invested so I need to know about anything like this.