Prev: Call for papers (Deadline Extended): AIPR-10, USA, July 2010
Next: Reading *standard-input* as a byte stream
From: Peter Keller on 25 Mar 2010 20:15 Hello, I'm using bordeaux-threads 0.8.0-dev and SBCL 1.0.36.29 on an x86 ubuntu box. I'm writing a threaded server which forkes off a blocking i/o thread for each client and there could be dozens or hundreds of threads working at the same time. Now, suppose I run this program in the toplevel. If I exit the original server form via CTRl-C or some other means, the threads are left to do their own thing, the worst thing being that the network connections to the client are not closed and so the clients hang around. In books like "Unix Network Programming: Networking APIs: Sockets and XTI 2nd Edition" by W. Richard Stevens, the codes are written in C and so the threads all exit when the main thread calls exit(). This means that all of the clients have the potential to immediately know the server went away. In the lisp toplevel, there is no such exiting of the entire process. I just go back to toplevel. So any threads left over are leaked (maybe even beyond the sight of the gc?) along with their resources. Given the above reasoning, I had "solved" this problem by protecting the thread creation loop with an unwind-protect whose cleanup form went through (ALL-THREADS) and called (DESTROY-THREAD ...) on each one. This "works", but the problem is that the manual for bordeaux-theads specifically states that I should not use those calls for this type of thing and other commentors (in an offline discussion I'm having) have mentioned the same thing. So, how do I destroy the threads so their cleanup forms get called properly when the server code wants to finish computing but I don't want to leave toplevel? If I understand Lisp properly (I'm new to it), there isn't a concept like UNIX signals to ansychronously alter the continuation of a thread. C "solves" this issue by having the entire program exit, but this situation I've found myself in seems singular to languages with a toplevel. One way that I could solve it is by SAVE-LISP-AND-DIE. When I start the executable it starts up the threads, and when the server exits, by definition all of the threads go away too (if I understand it in the lisp context correctly). But this is something that I really don't want to do since my code is meant to be explanatory instead of production and wedging in all that possibly non-portable code and semantic load is a afront to understandability. What is the best way to solve this problem? Thank you. -pete
From: RG on 25 Mar 2010 21:26 In article <4babfc91$0$9897$80265adb(a)spool.cs.wisc.edu>, Peter Keller <psilord(a)merlin.cs.wisc.edu> wrote: > So, how do I destroy the threads so their cleanup forms get called > properly when the server code wants to finish computing but I don't > want to leave toplevel? If I understand Lisp properly (I'm new to it), > there isn't a concept like UNIX signals to ansychronously alter the > continuation of a thread. Well, threads are an extension to the standard. So anything having to do with threads is necessarily implementation-specific. Some implementations provide this functionality (CCL for example, via the PROCESS-INTERRUPT function), others don't. > C "solves" this issue by having the entire program exit, Lisp can avail itself of this "solution" as well. > but this situation > I've found myself in seems singular to languages with a toplevel. No, it's an problem inherent to threads. Any language with threads will have this problem. The "right way" to solve this is to have the top-level thread keep track of all the threads it spawns, and then kill them all in an unwind-protect form. This isn't foolproof -- you can still lose if, for example, you hit CTRL-C twice. But it's a reasonable first cut. rg
From: Peter Keller on 25 Mar 2010 20:51 RG <rNOSPAMon(a)flownet.com> wrote: > In article <4babfc91$0$9897$80265adb(a)spool.cs.wisc.edu>, > Peter Keller <psilord(a)merlin.cs.wisc.edu> wrote: > Well, threads are an extension to the standard. So anything having to > do with threads is necessarily implementation-specific. Some > implementations provide this functionality (CCL for example, via the > PROCESS-INTERRUPT function), others don't. [snip] > The "right way" to solve this is to have the top-level thread keep track > of all the threads it spawns, and then kill them all in an > unwind-protect form. This isn't foolproof -- you can still lose if, for > example, you hit CTRL-C twice. But it's a reasonable first cut. In my server codes (which are for a tutorial), I have written exactly this use of unwind-protect. Maybe what I'll do is leave it like that and simply write a couple of paragraphs of text explaining the situation. I must admit I don't really know the right thing to do. One of the problems I want to avoid are readers of the tutorial believing the threading idiom (it has the appearance of an idiom at any rate) I wrote is totally valid and then going off and writing code based upon it. Of course, I would like this to be exactly the case--that it is valid. :) But it seems not and I want to mitigate the effect of my decision in the pedagogical explanation of the code. Thank you. -pete
From: Johan Ur Riise on 25 Mar 2010 21:50 Peter Keller <psilord(a)merlin.cs.wisc.edu> writes: > Hello, > > I'm using bordeaux-threads 0.8.0-dev and SBCL 1.0.36.29 on an x86 ubuntu box. > > I'm writing a threaded server which forkes off a blocking i/o thread for > each client and there could be dozens or hundreds of threads working at the > same time. > > Now, suppose I run this program in the toplevel. If I exit the original > server form via CTRl-C or some other means, the threads are left to do > their own thing, the worst thing being that the network connections to > the client are not closed and so the clients hang around. Note: *stop* is the word stop in earmuffs. Sometimes presented as bold typeface in newsreaders. I usually use a global variable *stop*, and test this in each thread function. If your main function runs in the REPL (I don't in these cases, I use a function called start that starts another thread, then returns), just open a new lisp source file in slime, and execute (setf *stop* t) by placing the cursor after the form and C-x C-e, then it is executed in a thread different from the REPL.(You can also create a separate REPL buffer). It can also be a signal file in the filesystem, in that case I can stop the system without connecting to the REPL. (defun stop-p () (probe-file #P"stop.txt")) (defun worker-thread-func () (loop until *stop* do ... )) If I listen on a socket, I would wrap a with-timeout around the accept call, else I would have to wait for the next connect before the thread stops. Then you can close your sockets and files. You can only open a few thousand sockets or files without closing them. (It is a while since I did this, maybe it works...) (loop until *stop* do (let ((connected-socket (with-timeout (.5) (sb-bsd-sockets:socket-accept listening-socket)))) (when-connected-socket ... ; maybe start a new thread here ))) If I have a long running thread that should wake up rather seldom, I use something like this to speed up the shutdown: (defun long-running () (let ((previous-execution-time 0)) (loop until *stop* do (sleep .5) (when (> (- (get-universal-time) previous-execution-time) (* 60 60 24)) (do-whatever-needs-to-be-done) (setf previous-execution-time (get-universal-time))) ) )) In my main function, I usually have (defun start () (setf *stop* nil) ; or delete the signal file (start-threads) ) Then stopping the threads is setting *stop* to t. On the listening socket, I set the reuse-address parameter, which ensures that I can listen on the same port without waiting for the 15 minutes or so while the system's network stack waits for stray packets from the internet after close of the socket. > > In books like "Unix Network Programming: Networking APIs: Sockets and > XTI 2nd Edition" by W. Richard Stevens, the codes are written in C and > so the threads all exit when the main thread calls exit(). This means > that all of the clients have the potential to immediately know the server > went away. If you exit the image, the same would happen in CL. > > In the lisp toplevel, there is no such exiting of the entire process. I > just go back to toplevel. So any threads left over are leaked (maybe > even beyond the sight of the gc?) along with their resources. > > Given the above reasoning, I had "solved" this problem by protecting > the thread creation loop with an unwind-protect whose cleanup form went > through (ALL-THREADS) and called (DESTROY-THREAD ...) on each one. If you destroy threads, you can not close the files and sockets. Killing threads from the outside is often not recommended, it is better to let each thread complete its thread function. > > This "works", but the problem is that the manual for bordeaux-theads > specifically states that I should not use those calls for this type of > thing and other commentors (in an offline discussion I'm having) have > mentioned the same thing. > > So, how do I destroy the threads so their cleanup forms get called > properly when the server code wants to finish computing but I don't > want to leave toplevel? If I understand Lisp properly (I'm new to it), > there isn't a concept like UNIX signals to ansychronously alter the > continuation of a thread. > > C "solves" this issue by having the entire program exit, but this situation > I've found myself in seems singular to languages with a toplevel. > > One way that I could solve it is by SAVE-LISP-AND-DIE. When I start > the executable it starts up the threads, and when the server exits, > by definition all of the threads go away too (if I understand it in the > lisp context correctly). No, this is mentioned only to underscore the fact that a saved image can not possibly save the threads and then have them running again when you restart the saved image. The same for network connections and open files. (Well, threads could theoretically be restored in some future lisp implementation, but not network connections). Even if you save an image, you must provide a function to start threads and open files and connections. SAVE-LISP-AND-DIE is not a tool to stop threads. >[...]
From: Johan Ur Riise on 25 Mar 2010 22:06 Johan Ur Riise <johan(a)riise-data.no> writes: > Peter Keller <psilord(a)merlin.cs.wisc.edu> writes: > >> Hello, > > (loop until *stop* do > (let ((connected-socket (with-timeout (.5) (sb-bsd-sockets:socket-accept listening-socket)))) > (when-connected-socket > ... ; maybe start a new thread here > ))) By the way, a trick to supply data to a thread-function, for instance the connected-socet above, is to use a closure as a thread function. (A thread function normally does not take parameters) (when connected-socket (let ((connected-socket connected-socket)) (bordeaux-threads:make-thread (lambda () (do-something-with connected-socket) ...) :name "connected-socket-handler")))
|
Next
|
Last
Pages: 1 2 3 4 Prev: Call for papers (Deadline Extended): AIPR-10, USA, July 2010 Next: Reading *standard-input* as a byte stream |