Posted to tcl by aspect at Tue Jul 08 05:26:25 GMT 2014view raw

  1. The bad combination is tls::socket + [fconfigure -blocking 0] + [chan push]. The result is [flush] raising EINVAL on first attempt to write to the socket.
  2.  
  3. Here's a minimal test case:
  4.  
  5. puts Tcl:[info patchlevel]
  6. puts tls:[package require tls]
  7.  
  8. namespace eval nopchan {
  9. proc initialize args {info procs}
  10. proc finalize args {}
  11. proc clear args {}
  12. proc write {h data} {
  13. puts [list ::nopchan::write $data]
  14. return $data
  15. }
  16. namespace export *
  17. namespace ensemble create
  18. }
  19.  
  20. set sock [tls::socket google.com 443]
  21. chan push $sock nopchan
  22. chan configure $sock -blocking 0
  23. puts -nonewline $sock "GET"
  24. puts "Calling flush"
  25. flush $sock
  26. # flush: error writing "sockb41ef0": invalid argument
  27.  
  28. Reproduced with tcl trunk + tls trunk, both with and without dgp's patches at http://core.tcl.tk/tcl/info/c31ca233cacd7d6184877c54182f4b3a6ccb30f1
  29.  
  30.  
  31. Building tls with -NDEBUG yields interesting results. All failing cases reduce to this:
  32.  
  33. Calling flush
  34. ::nopchan::write GET
  35. BIO_write(0xdc11b0, 15)
  36. WaitForConnect(0xdc11b0)
  37. BioWrite(0xdc1b30, <buf>, 150) [0xdc13b0]
  38. [0xdc13b0] BioWrite(150) -> 150 [0.0]
  39. BioCtrl(0xdc1b30, 0x6, 0x0, 0xdc1830)
  40. BioRead(0xdc1b30, <buf>, 5) [0xdc13b0]
  41. [0xdc13b0] BioRead(5) -> -1 [0.11]ERR(5, 11)
  42. Output(15) -> -1
  43. error writing "sockdc1330": invalid argument
  44.  
  45.  
  46. Trying to decouple WaitForConnect, I made a test case using [socket -async].
  47.  
  48. # using same nopchan from above:
  49. proc test {} {
  50. set sock [tls::socket -async google.com 443]
  51. chan push $sock nopchan
  52. fconfigure $sock -blocking 0
  53. fileevent $sock writable [info coroutine]
  54. yield
  55. puts "CONNECTED"
  56. fileevent $sock writable {}
  57. puts -nonewline $sock GET
  58. puts "Calling flush"
  59. flush $sock
  60. }
  61.  
  62. fconfigure stdout -buffering line
  63. coroutine coro test
  64. vwait forever
  65.  
  66. This exhibits exactly the same behaviour on tls trunk - including WaitForConnect only being called after [write]:
  67.  
  68. Tcl:8.6.1
  69. tls:1.6.3
  70. TlsWatchProc(0x4)
  71. CONNECTED
  72. TlsWatchProc(0x0)
  73. Calling flush
  74. nopchan::write GET
  75.  
  76. BIO_write(0x28c8c30, 3)
  77. WaitForConnect(0x28c8c30)
  78. BioWrite(0x28c94b0, <buf>, 150) [0x28c8d30]
  79. [0x28c8d30] BioWrite(150) -> 150 [0.0]
  80. BioCtrl(0x28c94b0, 0x6, 0x0, 0x28c91b0)
  81. BioRead(0x28c94b0, <buf>, 5) [0x28c8d30]
  82. [0x28c8d30] BioRead(5) -> -1 [0.11]ERR(5, 11)
  83. Output(3) -> -1TlsWatchProc(0x0)
  84. error flushing "sock28c8cb0": invalid argument
  85.  
  86.  
  87. But with dgp's patches, it succeeds:
  88.  
  89. Tcl:8.6.1
  90. tls:1.6.3
  91. TlsWatchProc(0x4)
  92.  
  93. WaitForConnect(0x28ccc10)
  94. BioWrite(0x28cd490, <buf>, 150) [0x28ccd10]
  95. [0x28ccd10] BioWrite(150) -> 150 [0.0]
  96. BioCtrl(0x28cd490, 0x6, 0x0, 0x28cd190)
  97. BioRead(0x28cd490, <buf>, 5) [0x28ccd10]
  98. [0x28ccd10] BioRead(5) -> -1 [0.11]ERR(5, 11)
  99. : WaitForConnect(0x28ccc10)
  100. : BioRead(0x28cd490, <buf>, 5) [0x28ccd10]
  101. : [0x28ccd10] BioRead(5) -> -1 [0.11]ERR(5, 11)
  102.  
  103. (lines marked : repeated several hundred times)
  104.  
  105. WaitForConnect(0x28ccc10)
  106. BioRead(0x28cd490, <buf>, 5) [0x28ccd10]
  107. [0x28ccd10] BioRead(5) -> 5 [0.0]
  108. BioRead(0x28cd490, <buf>, 1) [0x28ccd10]
  109. [0x28ccd10] BioRead(1) -> 1 [0.0]
  110. BioRead(0x28cd490, <buf>, 5) [0x28ccd10]
  111. [0x28ccd10] BioRead(5) -> 5 [0.0]
  112. BioRead(0x28cd490, <buf>, 60) [0x28ccd10]
  113. [0x28ccd10] BioRead(60) -> 60 [0.0]
  114. BioCtrl(0x28cd490, 0x7, 0x0, 0x28cd190)
  115. BioCtrl(0x28cd490, 0xb, 0x0, 0x0)BIO_CTRL_FLUSH
  116. R0! CONNECTED
  117. TlsWatchProc(0x0)
  118. Calling flush
  119. nopchan::write GET
  120.  
  121. BIO_write(0x28ccc10, 3)
  122. BioWrite(0x28cd490, <buf>, 28) [0x28ccd10]
  123. [0x28ccd10] BioWrite(28) -> 28 [0.0]
  124. BIO_write(0x28ccc10, 3) -> [3]
  125. Output(3) -> TlsWatchProc(0x0)
  126.  
  127.  
  128.  
  129. .. possibly another clue, if it's not clear from the above, is that it's only the first attempt to flush on a newly-connected, async tls channel that fails. The following code which connects before making the socket async, succeeds:
  130.  
  131. set sock [tls::socket google.com 443]
  132. puts -nonewline $sock G
  133. flush $sock ;# succeeds - socket is not async
  134. fconfigure $sock -blocking 0
  135. puts -nonewline $sock ET
  136. flush $sock ;# succeeds - socket is fully connected
  137.  
  138.