Workaround for bugs in NetworkClientEvent()

Share your advanced PureBasic knowledge/code with the community.
DarkPlayer
Enthusiast
Enthusiast
Posts: 107
Joined: Thu May 06, 2010 11:36 pm

Workaround for bugs in NetworkClientEvent()

Post 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
moogle
Enthusiast
Enthusiast
Posts: 372
Joined: Tue Feb 14, 2006 9:27 pm
Location: London, UK

Re: Workaround for bugs in NetworkClientEvent()

Post 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:
Image
User avatar
Joakim Christiansen
Addict
Addict
Posts: 2452
Joined: Wed Dec 22, 2004 4:12 pm
Location: Norway
Contact:

Re: Workaround for bugs in NetworkClientEvent()

Post by Joakim Christiansen »

+1
You're a hero!
I like logic, hence I dislike humans but love computers.
kinglestat
Enthusiast
Enthusiast
Posts: 746
Joined: Fri Jul 14, 2006 8:53 pm
Location: Malta
Contact:

Re: Workaround for bugs in NetworkClientEvent()

Post by kinglestat »

I did! I did!
But nobody listened
And did not imagine it could be fixed using PB itself.
Holy blazes...well done
I may not help with your coding
Just ask about mental issues!

http://www.lulu.com/spotlight/kingwolf
http://www.sen3.net
coco2
Enthusiast
Enthusiast
Posts: 461
Joined: Mon Nov 25, 2013 5:38 am
Location: Australia

Re: Workaround for bugs in NetworkClientEvent()

Post by coco2 »

Thanks for this.
Fred
Administrator
Administrator
Posts: 18150
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: Workaround for bugs in NetworkClientEvent()

Post 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.
coco2
Enthusiast
Enthusiast
Posts: 461
Joined: Mon Nov 25, 2013 5:38 am
Location: Australia

Re: Workaround for bugs in NetworkClientEvent()

Post 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.
Post Reply