From: knutaf@... Date: 2009-06-10T16:45:06+09:00 Subject: inheriting socket in child process on Windows 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