Prev: Call for papers (Deadline Extended): AIPR-10, USA, July 2010
Next: Reading *standard-input* as a byte stream
From: Peter Keller on 26 Mar 2010 01:19 Johan Ur Riise <johan(a)riise-data.no> wrote: > 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). I had thought about doing the method you propose. Suppose a thread looks like this (this isn't the real code, just the flow, I'm also leaving out condition handling and other things for this snippet): (defun worker-thread () (let ((client *the-thread-specific-binding*)) (loop until *stop* do (format client (read-line client)) (finish-output client)))) What happens is that if I'm blocked on the read-line from the client, then even if I set the global *stop* binding, the thread waits around until the client decides to send it data. This leaves threads possibly piling up in the server process (here the REPL) allowing for denial of service or latent threads mucking around with stuff if one decides to start the server function again. In the above case, setting *stop* to t, and restarting the server, which sets *stop* to nil could have all happened while blocked on read-line. I can't force the close on the server side, which is what I'd like to 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. For my purpose, the main thread performs the listen and the accept, and then the connected socket is given to the thread for communication purposes. > 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))) > ) > )) I don't feel very good about polling like this when one has 10,000+ threads. you end up consuming a lot of CPU even though you may sleep 1 second before polling again. > If you exit the image, the same would happen in CL. Ah good, at least I have that going for me. > 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. It turns out that it "works" in SBCL and bordeaux-threads in that the unwind-protect forms in the thread seem to get called. I realize that might be implementation defined behavior though, which is why I'm looking for something better. > 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. Yes, I understand. What I meant was I SAVE-LISP-AND-DIE with the :toplevel function being set to the server start entry point. Then when I rerun the executable the server starts, listens, binds, etc. Then, if the server exits, all threads die with it immediately. You have already confirmed for me that this is in fact what would happen. Thank you. -pete
From: Johan Ur Riise on 26 Mar 2010 03:06 Peter Keller <psilord(a)merlin.cs.wisc.edu> writes: > Johan Ur Riise <johan(a)riise-data.no> wrote: >> 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). > > I had thought about doing the method you propose. Suppose a thread looks > like this (this isn't the real code, just the flow, I'm also leaving > out condition handling and other things for this snippet): > > (defun worker-thread () > (let ((client *the-thread-specific-binding*)) > (loop until *stop* do > (format client (read-line client)) > (finish-output client)))) > > What happens is that if I'm blocked on the read-line from the client, > then even if I set the global *stop* binding, the thread waits around > until the client decides to send it data. > This leaves threads possibly > piling up in the server process (here the REPL) allowing for denial > of service or latent threads mucking around with stuff if one decides > to start the server function again. In the above case, setting *stop* > to t, and restarting the server, which sets *stop* to nil could have > all happened while blocked on read-line. I can't force the close on the > server side, which is what I'd like to do. The WITH-TIMEOUT method could work also here, but it could make it difficult to achieve some specific functionality. > For my purpose, the main thread performs the listen and the accept, and > then the connected socket is given to the thread for communication purposes. > >> 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))) >> ) >> )) > > I don't feel very good about polling like this when one has 10,000+ threads. > you end up consuming a lot of CPU even though you may sleep 1 second before > polling again. For the occasional wake-up, you really need only one thread. The 0.5 sec wake-ups are really peanuts. Trust me. But if you want to handle this many clients, you certainly can not do it with this one-thread-per-connection style anyway. This can work for say 30 threads, no more. You need to explore the asynchronous sockets libraries of your operating system and use a single thread for it all. With asynchrous sockets you can also stop your application quicker than 0.5 secs. >> If you exit the image, the same would happen in CL. > > Ah good, at least I have that going for me. > >> 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. > > It turns out that it "works" in SBCL and bordeaux-threads in that the > unwind-protect forms in the thread seem to get called. I forgot. You need to wrap it in a handler case also, to catch the TIMEOUT condition, else the condition will flow up. > I realize that might > be implementation defined behavior though, which is why I'm looking for > something better. It is somewhat implementation dependent. With-timeout could be in bordeaux threads, but I suspect it is implemented only for sbcl. Both threads and sockets would be implementation dependent. There is no implementation independent way. > >> 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. > > Yes, I understand. What I meant was I SAVE-LISP-AND-DIE with the :toplevel > function being set to the server start entry point. Then when I rerun the > executable the server starts, listens, binds, etc. Then, if the server > exits, all threads die with it immediately. You have already confirmed > for me that this is in fact what would happen. Restarting the image could be useful in this case. Of course, to make the start quick afterwards. An image (made with save-lisp-and-die) which has all the libraries and your application, is also useful. Exiting an image is also implementation dependant, by the way.
From: Johan Ur Riise on 26 Mar 2010 03:13 Johan Ur Riise <johan(a)riise-data.no> writes: > Of course, to make > the start quick afterwards. An image (made with save-lisp-and-die) which > has all the libraries and your application, is also useful. I wanted to say that the exiting of the image is what you need, save-lisp-and-die solves another problem. > Exiting an image is also implementation dependant, by the way.
From: Alex Mizrahi on 26 Mar 2010 03:49 PK> In the lisp toplevel, there is no such exiting of the entire process. Well, I'm pretty sure you can exit from the whole process as well... PK> This "works", but the problem is that the manual for bordeaux-theads PK> specifically states that I should not use those calls for this type of PK> thing and other commentors (in an offline discussion I'm having) have PK> mentioned the same thing. It is because bordeaux-threads is just an compatibility wrapper and different CL implementations implement it in different ways -- some implement cleanup, other do not. Of course it is better to implement shutdown on application level -- notifying threads via flags or something like that -- but you can't do that if threads get blocked in I/O or are doing some computations w/o checking flag for a long time. PK> want to leave toplevel? If I understand Lisp properly (I'm new to it), PK> there isn't a concept like UNIX signals to ansychronously alter the PK> continuation of a thread. Common Lisp standard has no relationship whatsoever with threads. Bordeaux-threads library has interrupt-thread function, but what it does exactly is implementation-dependent. (Probably depends on OS too.) If it can interrupt I/O operations, it might help you, otherwise it won't. So here's a solution: if implementation implements destroy-thread in clean fashion or has interrupt-thread which interrupts I/O, you can use them to take down your threads. Some operating systems allow to cancel I/O operations outside the thread which issued them, it might be a solution too. (Albeit complex and OS-dependent.) Otherwise you just CANNOT use indefinitely blocking I/O safely in that implementation if you need ability to cancel it. There are some options you have in this case, but they will all be implementation-dependent. (E.g. you can provide timeout for I/O operations.) Now about particular implementation, SBCL manual says: ---- Function: sb-thread:terminate-thread thread Terminate the thread identified by thread, by causing it to run sb-ext:quit - the usual cleanup forms will be evaluated ---- SO I guess in SBCL you can just use terminate-thread (or destroy-thread bordeaux-threads wrapper) and it will be ok. For other implementations you'll need to consult manuals. Multithreading is not standardized in CL and it heavily depends on OS anyway, so you shouldn't assume portability for granted. Things like this need to be carefuly verified and possibly adjusted for each concrete implementation.
From: Johan Ur Riise on 26 Mar 2010 04:09 Peter Keller <psilord(a)merlin.cs.wisc.edu> writes: > It turns out that it "works" I wonder what is the difference of ""works"", as opposed to "works" here. May be it just looks to good to be true, to you. I can understand that, because I have not seen the likes of the WITH-TIMEOUT abstraction in any other popular language.
First
|
Prev
|
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 |