[#4766] Wiki — "Glen Stampoultzis" <trinexus@...>

21 messages 2000/09/04
[#4768] RE: Wiki — "NAKAMURA, Hiroshi" <nahi@...> 2000/09/04

Hi, Glen,

[#4783] Re: Wiki — Masatoshi SEKI <m_seki@...> 2000/09/04

[#4785] Re: Wiki — "NAKAMURA, Hiroshi" <nakahiro@...> 2000/09/05

Howdy,

[#4883] Re-binding a block — Dave Thomas <Dave@...>

16 messages 2000/09/12

[#4930] Perl 6 rumblings -- RFC 225 (v1) Data: Superpositions — Conrad Schneiker <schneik@...>

Hi,

11 messages 2000/09/15

[#4936] Ruby Book Eng. translation editor's questions — Jon Babcock <jon@...>

20 messages 2000/09/16

[#5045] Proposal: Add constants to Math — Robert Feldt <feldt@...>

15 messages 2000/09/21

[#5077] Crazy idea? infix method calls — hal9000@...

This is a generalization of the "in" operator idea which I

17 messages 2000/09/22

[#5157] Compile Problem with 1.6.1 — Scott Billings <aerogems@...>

When I try to compile Ruby 1.6.1, I get the following error:

15 messages 2000/09/27

[ruby-talk:5006] Working server

From: Aleksi Niemel<aleksi.niemela@...>
Date: 2000-09-19 11:56:34 UTC
List: ruby-talk #5006
I've tried to get some base for server framework in place, but I shouldn't
have been in such hurry - I just ended up with chunk of code which does not
work. Then I've tried few things, but I'm all the time recurring to the same
problem. Here's a little bit of discussion about the problem using standard
sample shipped with Ruby, and few surprises. I hope you can understand
what's going on even while I try to confuse with my explanation :).

Example:
1)
Please fire up a sample/tsvr.rb server, and check the port it returns.
(I copy the source here, so people wanting just read don't have to look up
the file.)

  # socket example - server side using thread
  # usage: ruby tsvr.rb

  require "socket"

  gs = TCPserver.open(0)
  addr = gs.addr
  addr.shift
  printf("server is on %d\n", addr.join(":"))

  while TRUE
    Thread.start(gs.accept) do |s|
      print(s, " is accepted\n")
      while s.gets
        s.write($_)
      end
      print(s, " is gone\n")
      s.close
    end
  end


2)
Run the following client and specify the port as a command line argument.

  10.times {|i| 
    s=TCPSocket.new("localhost", ARGV[0])
    s.write("abcdefgh\n"*1)
    puts s.gets("\n")
    s.close
  }

3)
For me the server for the above client does not work. But it worked with my
home machine yesterday so I wonder what went wrong. (Here's a little bit of
debugging.) Ah, it seems that the Ruby has changed underneath.

The client opens up a socket fine (server says for example
"#<TCPSocket:0x40179e38> is accepted"), then it writes to the socket, and
stays reading the socket and never gets any response.

The server OTOH throws an exception (you can see it by adding
Thread.abort_on_exception=true), as there's no Array#gets. What?! It should
have been socket, what's this talk about Array.

4)

Let's replace the client and socket with something more easily followable.
The client gets few notification messages to tell where it's going. Let's
gather all the established sockets too (so they won't finalize yet).

  sockets = []
  10.times {|i| 
    s=TCPSocket.new("localhost", ARGV[0])
    puts "#{i} writing"
    s.write("abcdefgh\n"*1)
    puts "#{i} reading"
    puts s.gets("\n")
    puts "#{i} closing"
    s.close
    sockets << s
  }

And then the new server. The major change to the server is the "for socket
in s" loop (as I don't know if the array s could hold more than one accepted
connection at a time).

  require "socket"
  #Thread.abort_on_exception=true
  gs = TCPserver.open(0)
  addr = gs.addr
  addr.shift
  printf("server is on %d\n", addr.join(":"))

  while TRUE
    Thread.start(gs.accept) do |s|
      for socket in s
        print(socket, " is accepted\n")
        while socket.gets("\n")
          puts "got #{$_.length}"
          socket.write($_)
          puts "wrote #{$_.length}"
        end
        print(socket, " is gone\n")
        socket.close
      end
    end
  end

5)
Let's run the things again. Now everything goes well (at least for me) and
the dialogue is about like this:

        Server                           Client

#<TCPSocket:0x401795b4> is accepted
                                        4 writing
                                        4 reading
got 9
wrote 9
                                        abcdefgh
                                        4 closing
#<TCPSocket:0x401795b4> is gone


So it seems to work. Now we get to the real point, so far we have been
clearing out odd behaviour of examples (possibly due to the 1.4->1.6
change).

6)
Let's remove the socket closing code at the client side and check how things
go. 

And things go well. The client gets through it's 10 connections, writes,
gets response, and continues. The server doesn't report closed sockets
before the client program exists (as then Ruby closes automatically). When
the client dies normally, we get dozen of "gone" messages on the server
side. It seems the server connections "go away" even when the client dies
unexpectedly (dunno why, but it's good :).

7)
Ok, the let's make two more test. Let's remove the reading part from client.

  sockets = []
  10.times {|i| 
    s=TCPSocket.new("localhost", ARGV[0])
    puts "#{i} writing"
    s.write("abcdefgh\n"*1)
#    puts "#{i} reading"
#    puts s.gets("\n")
    puts "#{i} closing"
    s.close
    sockets << s
  }

Now the server reports connections, serve them, but *does not* print any
"gone" messages, even after the client process have died. My machine is
running Linux, and running netstat tells (only beginning, the first two
connections)

Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State

tcp        0      9 localhost:4284          localhost:4294          CLOSE

tcp        0      9 localhost:4284          localhost:4293          CLOSE

tcp        0      9 localhost:4284          localhost:4292          CLOSE

tcp        0      9 localhost:4284          localhost:4291          CLOSE 

So the sockets are closed, but Ruby doesn't know about it.

One can get the same effect by having the reading (remove comments on
earlier code), but writing more (changes "*1" to "*2" or something at
s.write -line) to the echo server.

=======

If you managed to get through here's my thoughts what's happening.

Ruby "blocks" on writing to a closed socket. With this I don't mean kernel
level blocking, but the writing Ruby thread gets blocked from execution.

One can detect when socket goes down by trying to read from it. I've tried
to create server classes where for each connection there's two threads - one
reading and the other writing, and when one of these notes the connection is
down, both are terminated. The problem is that if there's no reading going
on on the socket, there's no way to know when the socket did go down.

I'm not sure if my interpretation is correct, if there's a bug in current
implementation nor if everything is just as it should. So I thought it might
be better to discuss about the situation a little.

	- Aleksi

In This Thread

Prev Next