proc NS_read {fd len timeout} {
 global NS_read_state

 set NS_read_state($fd,error) 0
 set NS_read_state($fd,remaining) $len
 set NS_read_state($fd,timeout) [after $timeout [list NS_read.abort $fd]]
 set NS_read_state($fd,pending_data) ""
 set NS_read_state($fd,data) ""

 fconfigure $fd -blocking 0

 fileevent $fd readable [list NS_read.callback $fd]

 vwait NS_read_state($fd,data)

 #
 # Copy the data object so that we can cleanse the NS_read_state array.
 #
 set data $NS_read_state($fd,data)

 #
 # Copy the error object for the same reason as above.
 #

 set error $NS_read_state($fd,error)

 #
 # Cleanup the state, because we are about to return.
 #
 array unset NS_read_state $fd,*

 #
 # Remove the fileevent, because we are done and don't want it to
 # continue firing after we return.
 #
 fileevent $fd readable {}

 if {$error} {
  #
  # A timeout error occured during the read.
  #
  return -code error $data
 }

 return $data
}

proc NS_read.callback fd {
 global NS_read_state

 append NS_read_state($fd,pending_data) \
   [set chunk [read $fd $NS_read_state($fd,remaining)]]

 #
 # We could use incr var -[string length ...] but that results in shimmering.
 # This is a little faster normally.
 #
 set NS_read_state($fd,remaining) \
   [expr {$NS_read_state($fd,remaining) - [string length $chunk]}]


 if {$NS_read_state($fd,remaining) <= 0} {
  set NS_read_state($fd,data) $NS_read_state($fd,pending_data)
 }
}

proc NS_read.abort fd {
 global NS_read_state

 set NS_read_state($fd,error) 1
 set NS_read_state($fd,data) "NS_read timeout"
}

proc main { } {

 NS_read stdin 10 3000

}
main