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
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