inheriting socket in child process on Windows

K

knutaf

Hello, list

In my Ruby application, I accept TCP connections from several clients.
When
any client sends a particular command, I want to spawn a child process
in
place of the parent process that will continue to service the already-
open
TCP connections without the clients needing to reconnect.

I originally wrote this code on Linux, and it works in the most
obvious way:

When the command is received, make note of all of the client's file
descriptors (IO#fileno). Spawn the child process and communicate the
list of
file descriptors to it. In the child process, call TCPSocket.for_fd
for each
file descriptor, and it is ready to read and write data.

I suppose it's not too surprising that this doesn't "just work" on
Windows,
mainly because Ruby does a lot of special handling around the fact
that on
Windows, you can have both file descriptors and handles, and can only
pass
one or the other into various functions. I've done enough debugging to
know
specifically why the above method doesn't work on Windows, but I don't
think
it's necessary to explain it unless someone is curious.

After a good amount of debugging and examining the Ruby 1.9 source, I
have
this working to the point where my child process can write to the
client
sockets, but cannot read from them. Here's what I did, and what
doesn't
work.

In the parent process, I now wrap _get_osfhandle and _open_osfhandle.
Instead of just calling IO#fileno, I call _get_osfhandle(IO#fileno).
This
fetches the real SOCKET (i.e. HANDLE) value. I pass these to the
child
process.

In the child process, I now call TCPSocket.for_fd(_open_osfhandle
(handle)).
The other flags to _open_osfhandle don't actually matter. This step is
only
important to set up a mapping between the fd and the HANDLE value in
the
child process, so that later when I call TCPSocket#send or recv,
which
internally calls _get_osfhandle on its fd, it will get the right
SOCKET
value to pass into winsock functions.

By doing this, as I mentioned above, TCPSocket#send works, but recv
doesn't.
As far as I can tell, this is entirely due to some extra checks that
Ruby
does before actually calling ws2_32!select.

In rb_w32_select:

rb_fd_init(&else_rd);
nonsock += extract_fd(&else_rd, rd, is_not_socket);

This removes entries from the "rd" fd set that is passed in that match
the
criterion "is not a socket." I assumed that since I had passed in a
socket,
my fd would remain on this list. However...

From is_socket:

if (st_lookup(socklist, (st_data_t)sock, NULL)) // then true - it is
a
socket

Only sockets produced by winsock function wrappers in win32.c here
are
admitted to the special socklist. Outsiders aren't allowed in, even if
they
actually are sockets. I also verified using a bit of debugger hacking
that
if I invert the return value of is_not_socket for the fd I care about,
then
ws2_32!select does get called, which in turn does lead to recv
getting
called successfully.

Is there something special I can call to get my socket added to this
list? I
didn't see anything with a cursory glance of win32.c, which appears to
be
the only place socklist is used. Is there an entirely different
approach I
should be taking?

Thanks
-hargo
 
R

Robert Klemme

2009/6/10 said:
In my Ruby application, I accept TCP connections from several clients.
When
any client sends a particular command, I want to spawn a child process
in
place of the parent process that will continue to service the already-
open
TCP connections without the clients needing to reconnect.

I originally wrote this code on Linux, and it works in the most
obvious way:

When the command is received, make note of all of the client's file
descriptors (IO#fileno). Spawn the child process and communicate the
list of
file descriptors to it. In the child process, call TCPSocket.for_fd
for each
file descriptor, and it is ready to read and write data.

I would find this more obvious:

server = TCPServer.new port

while ( client = server.accept )
printf "Got client %p\n", client.peeraddr

fork do
client.each do |line|
client.printf "ECHO: %p\n", line
client.flush
end

client.close_write
end

client.close
end
Is there an entirely different
approach I should be taking?

I do not know whether you _should_ take this approach but using cygwin
might ease your pain considerably. The approach noted above does work
on cygwin.

Kind regards

robert
 
K

knutaf

2009/6/10  <[email protected]>:








I would find this more obvious:

server = TCPServer.new port

while ( client = server.accept )
  printf "Got client %p\n", client.peeraddr

  fork do
    client.each do |line|
      client.printf "ECHO: %p\n", line
      client.flush
    end

    client.close_write
  end

  client.close
end


I do not know whether you _should_ take this approach but using cygwin
might ease your pain considerably.  The approach noted above does work
on cygwin.

Kind regards

robert

True, I did realize after I posted that perhaps my way isn't the "most
obvious." In any case, I'd very much like to avoid requiring cygwin
for this to work. Plus, with cygwin, I would guess my original Linux
approach would probably work without modification.

Finally, after testing it out briefly, I don't think fork will work in
my situation. I didn't give enough context originally, but I'm
spawning the new child process in order for it to be the same process
as the parent, but having picked up any code changes that have been
made. A "live reboot", if you will. Fork doesn't seem to re-interpret
the source.
 
R

Robert Klemme

2009/6/10 said:
True, I did realize after I posted that perhaps my way isn't the "most
obvious." In any case, I'd very much like to avoid requiring cygwin
for this to work. Plus, with cygwin, I would guess my original Linux
approach would probably work without modification.

I don't understand the "plus". Why is that an additional reason to
not want to work with cygwin?
Finally, after testing it out briefly, I don't think fork will work in
my situation. I didn't give enough context originally,
Aha!

but I'm
spawning the new child process in order for it to be the same process
as the parent, but having picked up any code changes that have been
made. A "live reboot", if you will. Fork doesn't seem to re-interpret
the source.

fork() just copies the process - with all open file descriptors etc.
My understanding of Windows internals is limited but I do believe that
there is no direct equivalent function. It may be that the pattern
does not work on "plain" Windows.

Kind regards

robert
 
K

knutaf

I don't understand the "plus".  Why is that an additional reason to
not want to work with cygwin?

You caught me. That was bad wording on my part. s/Plus//.
fork() just copies the process - with all open file descriptors etc.
My understanding of Windows internals is limited but I do believe that
there is no direct equivalent function. It may be that the pattern
does not work on "plain" Windows.

Kind regards

robert

You're right. In fact, calling fork() on plain Windows throws a "not
supported by this platform" exception.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,919
Messages
2,570,037
Members
46,444
Latest member
MadeleineH

Latest Threads

Top