Re: [Haskell-cafe] scheduling an alarm

Brian Denheyer
On Mon, 25 Jan 2010 23:19:53 -0800 Thomas DuBuisson
wrote: 1) Don't use System.Posix.Signals It isn't necessary and makes your code less portable
2) The POSIX SIGALRM is used/caught by the RTS and that is why you are seeing strange behavior.
3) Consider using Haskell exceptions from Control.Concurrent (throwTo). Not sure what you want to do but you can always "myThreadId >>= \tid -> forkIO $ threadDelay someDelayTime >> (throwTo tid someExceptionVal)"
I just want a routine to run every 15 min.
You can still use Control.Concurrent:
import Control.Concurrent
doEvent f usDelay = forkIO $ threadDelay usDelay doEvent f usDelay f
Or a wrapper around Control.Concurrent - Ex: The Control-Event package.
I'm not clear on why the throwTo is in you example.
It will give you an exception just like you were expecting from SIGALRM. Thomas

On Tue, 26 Jan 2010 10:54:03 -0800
Thomas DuBuisson
doEvent f usDelay = forkIO $ threadDelay usDelay doEvent f usDelay f
Are you sure that's right ? It seems to be a memory-gobbling infinite loop... How about this: f = putStrLn "foo" doEvent f usDelay = do forkIO f threadDelay usDelay doEvent f 1000000 main = doEvent f 1000000 which seems to work. That makes me suspicious :-| Brian

Brian Denheyer
On Tue, 26 Jan 2010 10:54:03 -0800 Thomas DuBuisson
wrote: doEvent f usDelay = forkIO $ threadDelay usDelay doEvent f usDelay f
Are you sure that's right ? It seems to be a memory-gobbling infinite loop...
Infinite loop? yes, that is what you wanted. Memory gobbling? Why would you think that? Are you assuming no TCO and a full stack push on every function call? Haskell compilers don't work that way. How about this:
f = putStrLn "foo"
doEvent f usDelay = do forkIO f threadDelay usDelay doEvent f 1000000
main = doEvent f 1000000
which seems to work. That makes me suspicious :-|
1) There are a million ways to do this - why does one working make you suspicious of another? You can always test the other - it does work so long as you fix the missing 'do'. 2) It strikes me as funny you suspect the first way when there is zero fundamental difference between that and the way you posted except that: a) My version maintains the correct delay. b) My version forks the doEvent call and runs the action in the older thread while yours forks the action thread and keeps the doEvent in the older thread. I suppose keeping the doEvent as the old thread is good so you can kill it with the original ThreadID that would be returned to the caller. Some people miss the fact that threadDelay is a us value and an Int type - this limits the maximum delay to something like 35 minutes (assume a 32 bit Int) or even just 134 seconds if you go by Haskell 98 minimum of 27 bit Ints. Just making sure you realize this seeing as we are talking about delays in that order of magnitude. I advise the overly-complex but functional Control-Event package if you want longer delays. Cheers, Thomas

On Tue, 26 Jan 2010 22:41:44 -0800
Thomas DuBuisson
Brian Denheyer
wrote: On Tue, 26 Jan 2010 10:54:03 -0800 Thomas DuBuisson
wrote: doEvent f usDelay = forkIO $ threadDelay usDelay doEvent f usDelay f
Are you sure that's right ? It seems to be a memory-gobbling infinite loop...
Infinite loop? yes, that is what you wanted. Memory gobbling? Why would you think that? Are you assuming no TCO and a full stack push on every function call? Haskell compilers don't work that way.
Why would I think that ? I think that because the following code: import Control.Concurrent f = putStrLn "foo" doEvent f usDelay = do forkIO $ threadDelay usDelay doEvent f usDelay f _really_ _does_ start to consume all of the memory on my system, that's why. I don't know why, but that's what it does on my system. It's not obvious to me that it should do that. So maybe ghci is not doing TCO.
1) There are a million ways to do this - why does one working make you suspicious of another? You can always test the other - it does work so long as you fix the missing 'do'.
The "suspicious" part, that was a joke. Having something that works is nice, making sure I understand it is better. I haven't used the Concurrent stuff before, so I'm just being tentative.
2) It strikes me as funny you suspect the first way when there is zero fundamental difference between that and the way you posted except that: a) My version maintains the correct delay. b) My version forks the doEvent call and runs the action in the older thread while yours forks the action thread and keeps the doEvent in the older thread. I suppose keeping the doEvent as the old thread is good so you can kill it with the original ThreadID that would be returned to the caller.
Thanks for the explanation, as I said I'm a little fuzzy on what constitutes a thread, so the two versions will help. One interesting thing I noticed in the docs (which is not important for what I am trying to do, just interesting): There is no guarantee that the thread will be rescheduled promptly when the delay has expired, but the thread will never continue to run earlier than specified. I expect that this means "promptly" in the sense of not accurately, however I wonder what the upper bound on the (potential) delay is ?
Some people miss the fact that threadDelay is a us value and an Int type - this limits the maximum delay to something like 35 minutes (assume a 32 bit Int) or even just 134 seconds if you go by Haskell 98 minimum of 27 bit Ints. Just making sure you realize this seeing as we are talking about delays in that order of magnitude. I advise the overly-complex but functional Control-Event package if you want longer delays.
I did notice that the delay is in us. 35 minutes is plenty of time. I can always delay twice ;-) Thanks for pointing me to control event, that looks useful ! Brian

On Wed, Jan 27, 2010 at 7:31 AM, Brian Denheyer
On Tue, 26 Jan 2010 22:41:44 -0800 Thomas DuBuisson
wrote: Brian Denheyer
wrote: On Tue, 26 Jan 2010 10:54:03 -0800 Thomas DuBuisson
wrote: doEvent f usDelay = forkIO $ threadDelay usDelay doEvent f usDelay f
Are you sure that's right ? It seems to be a memory-gobbling infinite loop...
Infinite loop? yes, that is what you wanted. Memory gobbling? Why would you think that? Are you assuming no TCO and a full stack push on every function call? Haskell compilers don't work that way.
Why would I think that ? I think that because the following code:
import Control.Concurrent
f = putStrLn "foo"
doEvent f usDelay = do forkIO $ threadDelay usDelay doEvent f usDelay f
_really_ _does_ start to consume all of the memory on my system, that's why. I don't know why, but that's what it does on my system. It's not obvious to me that it should do that. So maybe ghci is not doing TCO.
That would be a bug! I'm using GHC 6.12.1 i386 and both interpreted using GHCi CLI and compiled (even without optimization) there is no memory growth using either of the two versions. If you are using the latest GHC then consider filing a report at haskell.org/ghc
2) It strikes me as funny you suspect the first way when there is zero
fundamental difference between that and the way you posted except that: a) My version maintains the correct delay. b) My version forks the doEvent call and runs the action in the older thread while yours forks the action thread and keeps the doEvent in the older thread. I suppose keeping the doEvent as the old thread is good so you can kill it with the original ThreadID that would be returned to the caller.
Thanks for the explanation, as I said I'm a little fuzzy on what constitutes a thread, so the two versions will help.
Well Concurrent Haskell has a version of green threads [1] which are scheduled (via the GHC RTS) on some number of OS threads (typically either 1 or equal to the number of cores on the machine). These light weight "green threads" are extremely cheap to create and destroy, costing a matter of 1K or less per thread, benchmarks can create/destory 100k threads in seconds. "forkIO" creates green threads while "forkOS" (which you generally should not use) creates OS threads. One interesting thing I noticed in the docs (which is not important
for what I am trying to do, just interesting):
There is no guarantee that the thread will be rescheduled promptly when the delay has expired, but the thread will never continue to run earlier than specified.
Right, and this is the same as any commodity OS - you can delay for a certain amount of time and once the delay is up you will get rescheduled but the computer is likely busy with other processes/interrupts etc at the exact microsecond you are done. I think the GHC RTS schedules threads in 50us slices by default. Also, GHC uses (or used to use) allocation as a scheduling point so if you have long-running tight loops that don't allocate (and don't explicitly call 'yield') then this could be quite long. In practice I've only once had a problem (GTK GUI didn't update during a long computation - still not sure this was the reason). Cheers, Thomas [1] http://en.wikipedia.org/wiki/Green_threads

Brian Denheyer
doEvent f usDelay = forkIO $ threadDelay usDelay doEvent f usDelay f
There's a missing 'do' here, right?
Infinite loop? yes, that is what you wanted. Memory gobbling? Why would you think that?
Why would I think that ?
doEvent f usDelay = do forkIO $ threadDelay usDelay doEvent f usDelay f
Are you sure this isn't interpreted as: doEvent f usDelay = do (forkIO $ threadDelay usDelay) doEvent f usDelay f I.e. just forking off processes that only does a delay, never even getting to 'f'? I interpreted Thomas's code to mean: doEvent f usDelay = forkIO $ (do threadDelay usDelay doEvent f usDelay f) Which appears to work nicely here. -k -- If I haven't seen further, it is by standing in the footprints of giants

Brian Denheyer
doEvent f usDelay = forkIO $ threadDelay usDelay doEvent f usDelay f
There's a missing 'do' here, right?
Yes - I said that in a later e-mail but it doesn't fix me violating my own peeve about non-functional code snippits on -cafe.
Infinite loop? yes, that is what you wanted. Memory gobbling? Why would you think that?
Why would I think that ?
doEvent f usDelay = do forkIO $ threadDelay usDelay doEvent f usDelay f
Are you sure this isn't interpreted as:
doEvent f usDelay = do (forkIO $ threadDelay usDelay) doEvent f usDelay f
The full code I ran and thought we were talking about (more or less) is inline here. For clarity - yes I know they are different in that one executes 'f' before the first sleep and the other does not. ------------- START CODE ----------------- import Control.Concurrent import Control.Monad (forever) import System.IO (hFlush, stdout) doEvent f usDelay = forkIO $ do threadDelay usDelay doEvent f usDelay f doEvent2 f usDelay = do forkIO f threadDelay usDelay doEvent2 f usDelay main = do doEvent func 1000000 >> forever (threadDelay maxBound) main2 = do doEvent2 func 1000000 >> forever (threadDelay maxBound) func = putStr "." >> hFlush stdout ----------------------- END CODE -------------------
I.e. just forking off processes that only does a delay, never even getting to 'f'?
The version you referenced is a little weird but so long as you fix the indentation it should be fine (drop "forkIO $ f" to the next line). Cheers, Thomas

Thomas DuBuisson
Yes - I said that in a later e-mail but it doesn't fix me violating my own peeve about non-functional code snippits on -cafe.
I guess we're spoiled by the type checker catching all our mistakes. Since I recently discovered the new and wonderful world of C-c C-l in haskell-mode, I wonder if it'd be hard (or if anybody already done so!) to incorporate this for literate-style Haskell in email? Wouldn't it be great if I could, just prior to sending a message, hit C-c C-l to load the *message* buffer in a Haskell process to check that it indeed works as expected, and if you, on reading it, could hit the same key combination to do the same? The birdstep-style might cause difficulties given the same mark's use as standard quoting indicator. And of course, most people use more pedestrian, Turing-inhibited MUAs these days... -k -- If I haven't seen further, it is by standing in the footprints of giants

Brian Denheyer wrote:
On Tue, 26 Jan 2010 22:41:44 -0800 Thomas DuBuisson
wrote: doEvent f usDelay = forkIO $ threadDelay usDelay doEvent f usDelay f
Are you sure that's right ? It seems to be a memory-gobbling infinite loop...
Why would I think that ? I think that because the following code:
import Control.Concurrent
f = putStrLn "foo"
doEvent f usDelay = do forkIO $ threadDelay usDelay doEvent f usDelay f
_really_ _does_ start to consume all of the memory on my system, that's why. I don't know why, but that's what it does on my system. It's not obvious to me that it should do that. So maybe ghci is not doing TCO.
The code you have presented at the bottom is an infinite loop that I would expect to consume all the memory on your system. That code spins at 100% busy, forking off threadDelay calls without ever stopping, and will never perform "f". Ouch -- infinite loop, and likely hugely memory-consuming (in fact, I'm not sure how the last poster didn't find that it consumed a lot of memory...). However, that code is slightly different to the original (that I have left in up the top) which had a crucial "do" missing (although you can infer it from the indentation). The original code should be: doEvent f usDelay = forkIO $ do threadDelay usDelay doEvent f usDelay f That is, the latter two lines should be inside the forkIO block, not after it. This code will wait for the given delay, then fork a new thread, then perform f. As long as f takes less time to complete than usDelay, this code should not eat memory and is quite reasonable. I hope that clears up the confusion. Neil.
participants (4)
-
Brian Denheyer
-
Ketil Malde
-
Neil Brown
-
Thomas DuBuisson