We have problem with the internal circular references within the Stdio.File objects. The problem is that not only are the objects as garbage until gc, but the associated file descriptors as well. This becomes a problem with massive amounts of connections.
Our short term solution is to sprinkle the code with destruct(file->fd), but I would like to ask what can be done within Pike, both short term (if anything) and as long term solution.
hi,
does that mean that Stdio.File()->close() does not free the filedescriptor?
greetings, martin.
does that mean that Stdio.File()->close() does not free the filedescriptor?
At least the asynchronous callback does not (as in, when it's called you have to wait for a gc() to get rid of the file, even if you thow away the object, close() only causes an error).
I have not actually tried close, though. But this:
for(int i=0; i<10000; i++) Stdio.File(".")->close();
indicates that it is actually closed (since my FD-max is 65k, not 100k fd:s).
However:
start backend for(int i=0; i<300; i++) {object q=Stdio.File();q->connect("127.0.0.1", 8080);q->write("GET / HTTP/1.0\r\n\r\n");q->set_nonblocking(lambda(){werror("data");},lambda(){},lambda(){werror("closed\n");});}
Ok. <insert data and closed * 100 here>
sizeof(Stdio.get_all_active_fd());
(5) Result: 311
gc();
(6) Result: 607
sizeof(Stdio.get_all_active_fd());
(7) Result: 11
However, this isn't really a good test. Since the file objects created are not part of any circular structure, they are freed by the refcounting. Thus it doesn't matter if close() frees the fd or not, the object will be deallocated immediately afterwards. No gc() is needed.
Right. The circular references are created when you call set_nonblocking(), as this registers the object with the backend.
Wouldn't a solution then be to set the Stdio.File object back to non-blocking in the close callback?
Modified Per example 2:
for(int i=0; i<300; i++) { object q=Stdio.File(); q->connect("www.liu.se", 80); q->set_id(q); q->write("GET / HTTP/1.0\r\n\r\n"); q->set_nonblocking(lambda(){werror("data");}, lambda(){}, lambda(mixed x) { werror("closed\n"); x->set_nonblocking(); }); }
<lots of data/closed>
sizeof(Stdio.get_all_active_fd());
(4) Result: 11
Maybe we even want to introduce some way of letting the Stdio.File object go back to nonblocking upon close? Either as a default or as a flag in set_nonblocking or by some other method.
doesn't matter, i tested it with 100000 and 1000000 iterations as well...
greetings, martin.
The docs is nowadays fairly clear on the close callback:
//! @item //! When an error or an end-of-stream in the read direction //! occurs, @[close_cb] will be called. @[errno] will return the //! error, or zero in the case of an end-of-stream. //! //! The name of this callback is rather unfortunate since it //! really has nothing to do with a close: The stream is still //! open when @[close_cb] is called (you might not be able to read //! and/or write to it, but you can still use things like //! @[query_address], and the underlying file descriptor is still //! allocated). Also, this callback will not be called for a local //! close, neither by a call to @[close] or by destructing this //! object. //! //! Also, @[close_cb] will not be called if a remote close only //! occurs in the write direction; that is handled by @[write_cb] //! (or possibly @[write_oob_cb]). //! //! Events to @[read_cb] and @[close_cb] will be automatically //! deregistered if an end-of-stream occurs, and all events in the //! case of an error. I.e. there won't be any more calls to the //! callbacks unless they are reinstalled. This doesn't affect the //! callback settings - @[query_read_callback] et<A0>al will still //! return the installed callbacks.
Thus a call to Stdio.File.close is necessary to close the fd, i.e. this works:
for(int i=0; i<300; i++) {object q=Stdio.File();q->connect("127.0.0.1", 14741);q->write("GET / HTTP/1.0\r\n\r\n");q->set_id(q);q->set_nonblocking(lambda(){werror("data");},lambda(){},lambda(object q){werror("closed\n");q->close();});}
The ref from the backend is only there as long as async events are requested. As the doc says the read and close events are deregistered when the close callback is called, but in your example you still have the write event (if the remote end only has closed its write direction, you could possibly still write on the local end). So registering no write callback also works:
for(int i=0; i<300; i++) {object q=Stdio.File();q->connect("127.0.0.1", 14741);q->write("GET / HTTP/1.0\r\n\r\n");q->set_nonblocking(lambda(){werror("data");},0,lambda(){werror("closed\n");});}
pike-devel@lists.lysator.liu.se