On 21/10/2015 04:49, Al Viro wrote: Firstly, thank you for the comprehensive and considered reply.
Refcount is an implementation detail, of course. However, in any Unix I know of, there are two separate notions - descriptor losing connection to opened file (be it from close(), exit(), execve(), dup2(), etc.) and opened file getting closed.
Yep, it's an implementation detail inside the kernel - Solaris also has a refcount inside its vnodes. However that's really only dimly visible at the process level, where all you have is an integer file ID.
The latter cannot happen while there are descriptors connected to the file in question, of course. However, that is not the only thing that might prevent an opened file from getting closed - e.g. sending an SCM_RIGHTS datagram with attached descriptor connected to the opened file in question *at* *the* *moment* *of* *sendmsg(2)* will carry said opened file until it is successfully received or discarded (in the former case recepient will get a new descriptor refering to that opened file, of course). Having the original descriptor closed right after sendmsg(2) does *not* do anything to opened file. On any Unix that implements descriptor-passing.
I believe async IO data is another way that a file can remain live after a close(), from the close() section of IEEE Std 1003.1:
"An I/O operation that is not canceled completes as if the close() operation had not yet occurred"
There's going to be a notion of "last close"; that's what this refcount is about and _that_ is more than implementation detail.
Yes, POSIX distinguishes between "file descriptor" and "file description" (ugh!) and the close() page says:
"When all file descriptors associated with an open file description have been closed, the open file description shall be freed."
In the context of this discussion I believe it's the behaviour of the integer file descriptor that's the issue. Once it's had close() called on it then it's invalid, and any IO on it should fail, even if the underlying file description is still 'live'.
In other words, is that destruction of * any descriptor refering to this socket [utterly insane for obvious reasons] * the last descriptor refering to this socket (modulo descriptor passing, etc.) [a bitch to implement, unless we treat a syscall in progress as keeping the opened file open], or * _the_ descriptor used to issue accept(2) [a bitch to implement, with a lot of fun races in an already race-prone area]?
From reading the POSIX close() page I believe the second option is the correct one.
Additional question is whether it's * just a magical behaviour of close(2) [ugly], or * something that happens when descriptor gets dissociated from opened file [obviously more consistent]?
The second, I believe.
BTW, for real fun, consider this: 7) // fd is a socket fd2 = dup(fd); in thread A: accept(fd); in thread B: accept(fd); in thread C: accept(fd2); in thread D: close(fd); Which threads (if any), should get hit where it hurts?
A & B should return from the accept with an error. C should continue. Which is what happens on Solaris.
I have no idea what semantics does Solaris have in that area and how racy their descriptor table handling is. And no, I'm not going to RTFS their kernel, CDDL being what it is.
I can answer that for you :-) I've looked through the appropriate bits of the Solaris kernel code and my colleague Casper has written an excellent summary of what happens, so with his permission I've just copied it verbatim below:
---------- Since at least Solaris 7 (1998), a thread which is sleeping on a file descriptor which is being closed by another thread, will be woken up. To this end each thread keeps a list of file descriptors in use by the current active system call. When a file descriptor is closed and this file descriptor is marked as being in use by other threads, the kernel will search all threads to see which have this file descriptor listed as in use. For each such thread, the kernel tells the thread that its active fds list is now stale and, if possible, makes the thread run. While this algorithm is pretty expensive, it is not often invoked. The thread running close() will NOT return until all other threads using that filedescriptor have released it. When run, the thread will return from its syscall and will in most cases return EBADF. A second thread trying to close this same file descriptor may return earlier with close() returning EBADF. ---------- -- Alan Burlison -- -- To unsubscribe from this list: send the line "unsubscribe netdev" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html