Unsatisfactory

Paul Brown @ 2007-11-22T01:07:00Z

It turns out that the temperature issues with my G5 were not merely due to a little accumulated dust.  The temperature sensor on CPU B has gone downhill to the point where it puts the machine to sleep after about 10 minutes from a cold boot, whether the machine is just idling or working hard, and then it's another couple of hours before its cool enough to boot again.  The interesting thing to me is that this isn't an isolated issue, see, for example, this post and this thread.  In fact, Apple changed the cooling design after my machine was shipped.

I had a "Genius" at the Apple Store and a local non-Apple repair shop take separate looks at the machine, and the diagnosis was the same — unknown root cause, but the standard course of action is to successively replace the logic board and one or both CPUs to see if that helps.  (I didn't ask the "Genius" to show me his Mensa card...)  The part shuffle isn't a surefire fix: it may help, but it may just cost money and not help at all.  The person-to-person street value for an equivalently configured machine, via eBay, is about $1,800, which is barely more than the total cost of parts and labor if both the logic board and one CPU are needed.  The machine is nice and heavy, so maybe it will make a good boat anchor...

For what it's worth, my experience at the Genius Bar was similar to Paul Boutin's.  A twenty-something woman with various metal bits in her lips and nose asked me what the G5 was and if I did "computer stuff".  I contemplated making up a story about how it was a really big iPod with a special headphone amplifier and personal subwoofer built-in, but I just replied "yes" and left it at that.  The local repair shop provided more information but took two weeks to do it.  Maybe people who use their Apple products to make money instead of spend it are no longer the company's core market?  His house is a lot bigger than mine, so I can hardly fault Real Steve Jobs for his choices.

Today, I spent nearly two hours on the phone with Apple support, working my way through offshore call centers to someone with actual knowledge of the machine, but the response was a resounding "You should have bought AppleCare."  I have a hard time trusting someone who can look me in the eye and tell me that I need to put money down on a bet that their product will fail, but that's the official line.

Assaf put his Twitter finger on one of my thoughts: maybe Apple is not the answer.

And maybe it is time to give Linux another shot as a primary operating system.  The company that I bought reliable workstations from back when I was an academic is still in business...  Apple is far from perfect, but up to now, they have been better enough to win my business.  Like other people, I have had issues with Apple operating system updates on my laptops (SuperDuper is well worth the money!), and I've had to install from scratch at least a half-dozen times in the past five years.  In fact, I've developed a ritual for migrating my settings and additional binaries (primarily MacPorts) to a new install.  Nonetheless, it would be hard to leave the Mac behind.  I use and love OmniGraffle, Keynote, VoodooPad, QuickSilver, TaskPaper, and a host of other Mac-only software, including more spendy stuff like Reaktor.

If the guts are sufficiently generic, I'll give the forthcoming Penryn machines a chance, and if not, I'll take my money elsewhere.

(comment bubbles) 0 comments

Ten Fingers, Ten Toes

Paul Brown @ 2007-11-20T19:44:51Z

Yesterday, we had the 20-week ultrasound for what, with luck, will be our second child. The news was good: fingers, toes, arms, legs, nose, heart, kidneys, etc., all present and accounted for in the usual quantities.

The first pregnancy was textbook normal, but we had a bit of a scare early on with this one in the form of a statistically abnormal nuchal translucency (NT). NT is computed as a ratio between measurements in an ultrasound, and large ratios are correlated to trisomic chromosomal abnormalities like Down Syndrome. We went ahead with a chorionic villus sampling (CVS), and the results showed no abnormalities. It was a long couple of weeks waiting for information after the NT result.

Looking at the prospect of testing, the amount of hard information available to the prospective parent is limited, even from a "genetic counselor" or physician. I asked about statistics and studies around NT and CVS, and the best that the caregiver could manage were the usual statistics — without even the false positive rate! I'm more interested in looking at the actual studies and statistics than most folks probably would be, but I don't think it's too much to ask people to give correct explanations and provide reference materials. Come to think of it, I'd like to see all scientific and medical research data available as open source (CC By-SA, perhaps?) and freely available on-line. (It looks like this is aligned with the mission of the Science Commons.)

(comment bubbles) 0 comments

HTTP 100 Tidbit

Paul Brown @ 2007-11-16T18:35:46Z

Before Dan's presentation, I hadn't heard of 100 (Continue), but then I ran into it today. A client had an issue with a .NET client talking to a Jetty instance, and the conversation went something like this:

POST /uri/foo HTTP/1.1
[...]
Expect: 100-continue
Connection: Close

100 Continue
[Jetty closes the connection.]

It turns out that this is an issue with the way that Jetty handles the Connection: Close header. Even though it seems reasonable to drop the connection according to 14.10 (if you think of the 100 Continue as a response), dropping the connection violates 8.2.3:

Upon receiving a request which includes an Expect request-header field with the "100-continue" expectation, an origin server MUST either respond with 100 (Continue) status and continue to read from the input stream, or respond with a final status code.

Experiences like this are the reason that I always smile when someone tells me that they've built their own HTTP client or server implementation; it's not that simple.

(comment bubbles) 0 comments

Tyranny of the Average

Paul Brown @ 2007-11-04T01:00:20Z

Steve Vinoski, building on comments that Steve Jones made about comments from Stefan Tilkov, says:

On the dynamic language front, the worst code I have seen in my career has always, always, always been in compiled imperative languages, most often Java and C++.

From my perspective, I've had the privilege of seeing some really awful code and design. The language has always been C/C++ or Java, since that's what virtually all large systems are built in and where there are tools to help people get things written and built. The root cause for the bad code and design has always been relative ignorance on the part of the developers. The use of Ruby or Python wouldn't have changed things for the better, while the use of Haskell or Erlang might, purely because I doubt that the developers in question would have been able to get the Haskell or Erlang programs to run...

It shouldn't surprise anyone who took statistics in college that once enough people start using a language, crappy software appears, independent of the language. With a few liberties taken with respect to hypotheses, it's a consequence of the central limit theorem, and this tyranny of the merely average is absolute and inescapable. It doesn't mean software is any more or less doomed than the rest of our society; rather, it is exactly as doomed as software's reach broadens.

(comment bubbles) 5 comments

Tuppence Tour of Haskell Concurrency Constructs

Paul Brown @ 2007-10-21T05:04:00Z

One of the little widgets that I need for an experiment is a sequence number generator, and it's a good way to get a tuppence (i.e., less than half a nickel) tour of Haskell's concurrency constructs with a little lesson on laziness thrown in for spice.

Requirements

The generator should produce unique Int values on demand and support concurrent access, and I want to try out a couple of methods, one that uses GHC's baseline concurrency capabilities and one that uses STM. (Also, I'd like a better feel for the concepts in practice, so this makes a good exercise!)

Take One: Asynchronous Channels

The base GHC concurrency packages (Control.Concurrent and its descendents) include a great set of buildings blocks: one-place buffers (MVar), asynchronous channels (Chan) that can be combined into one-to-many broadcast channels, and quantity semaphores (QSem and QSemN).

The design I have in mind uses two asynchronous channels, one for requests and one for responses. All clients of the generator receive responses on the one channel, which means that one might get the number that another one requested, but that's no big deal.

First, one data structure for the requests and one for the client view of the generator:

import Control.Concurrent ( ThreadId, forkIO )
import Control.Concurrent.Chan ( Chan, newChan, writeChan, readChan )

data Request = Get
             | Set { initial_value :: Int }
             | Die

data Generator = Generator { thread_id :: ThreadId,
                             request_channel :: Chan Request, 
                             response_channel :: Chan Int }

Then some functions (whose signatures I'll match with the STM version below) to manipulate the generator:

reset :: Generator -> Int -> IO ()
reset g i = writeChan (request_channel g) (Set i)

get_next :: Generator -> IO Int
get_next g = (writeChan (request_channel g) (Get)) >> 
             readChan (response_channel g)

stop :: Generator -> IO ()
stop g = writeChan (request_channel g) Die

Each client function is implemented in terms of sending the request data structure to the generator on the request_channel and then, in the case of the Get operation, blocking to read a value from the response_channel.

Finally, the request-handling event loop in a separate lightweight thread:

new_counter :: IO Generator
new_counter = do { in_chan <- newChan
                 ; out_chan <- newChan
                 ; tid <- forkIO $ loop in_chan out_chan 0
                 ; return $ Generator tid in_chan out_chan }

loop :: Chan Request -> Chan Int -> Int -> IO ()
loop ic oc i = do { req <- readChan ic
                  ; case req of 
                      Die -> return ()
                      (Set n) ->
                          (loop ic oc n)
                      Get ->
                          do { writeChan oc i
                             ; loop ic oc $! (i+1) } }

A similar pattern (request channel, response channel or one-place buffer (MVar) either pre-set or passed with the request, tail-recursive event loop) works for a wide variety of problems and presents a reasonable API for clients.

The single-threadedness of the loop makes it intuitively easy to conclude that it does the right thing (returns unique values to clients), but it's easy enough to check with some experiments in ghci:

> :m + Control.Concurrent
> g <- new_counter
> get_next g
0
> forkIO $ sequence_ $ replicate 10000000 (get_next g)
ThreadId 111
> forkIO $ sequence_ $ replicate 10000000 (get_next g)
ThreadId 112
> forkIO $ sequence_ $ replicate 10000000 (get_next g)
ThreadId 113
[... wait a while ...]
> get_next g
300000001

Which is the right answer. Another experiment will show how fair the scheduler is in terms of multiple client threads:

> g <- new_counter
> :set -fno-print-bind-result -fglasgow-exts

By the way, [TAB]-completion in ghci means that the above can be obtained by typing:

:set -fno-pr[TAB] -fgl[TAB]

The -fno-print-bind-result prevents ghci from spoiling our attempts to be lazy by printing (and thus evaluating), and the -fglasgow-exts lets us use a type specification to specify what kind of channel we're creating. (Normally, the compiler would figure it out from context, but that won't work in ghci.)

> tid::(Chan (Int,Int)) <- newChan
> mapM_ (\n -> (forkIO $ sequence_ $ replicate 1000 (get_next g >>= (writeChan tid).((,) n)))) [1..10]

The last expression looks dense, but it breaks down simply. In plain English, fork 10 threads numbered 1 through 10, and on each thread, do the following 1000 times: get a sequence number from the generator and then send the ordered pair of the threads number and the sequence number on the tid channel. (The (,) function makes ordered pairs out of its arguments.) To inspect the contents of the channel once the threads are done:

> x <- getChanContents tid

(Without the -fno-print-bind-result above, this would run forever.) And now a couple of quick checks:

> :m + List
> let y = take 10000 x
> length $ nub $ map snd y
10000
> [ length $ filter (((==) j).fst) y | j <- [1..10] ]
[1000,1000,1000,1000,1000,1000,1000,1000,1000,1000]
> let w = [ length $ nub (take 10 (drop k (map fst y))) | k <- [0..9990] ]
> [ length $ filter ((==) j) w | j <- [1..10] ]
[0,0,0,0,0,0,1,1,522,9467]

So, most of the time, in 10 turns, 10 client threads get a chance.

A Quick Word About Laziness

The $! is a piece of Haskell magic that ensures that the value on the right is evaluated. Without it, this happens:

> g <- new_counter
> get_next g
0
> sequence_ $ replicate 100000000 (get_next g)
> get_next g
*** Exception: stack overflow

The reason lies in Haskell's laziness. At the time that the second get_next is evaluated, there are 100,000,000 (i+1) queued-up and waiting to be evaluated because no one ever actually consumed the values passed back on the response channel. This is just the way that Haskell works: You can tell the runtime about a ridiculous computation, but it won't complain until you ask for the result. The $! ensures that the (i+1) value is evaluated each time Get is performed.

Take Two: TVar

Haskell's STM (software transactional memory; read/watch Simon Peyton Jones on the subject) implementation provides another set of building blocks in the form of atomically mutable cells (TVar), asynchronous channels (TChan), one-place buffers (TMVar), and arrays (TArray).

The sequence generator implementation with the same API but using a TVar to hold the current value is shorter and simpler (no backing thread) than the one above that uses channels:

import Control.Concurrent.STM
import Control.Concurrent.STM.TVar
import Control.Monad

data Generator = Generator { counter :: TVar Int }

new_counter :: Int -> IO Generator
new_counter i = (atomically $ newTVar i) >>= (return . Generator)

get_next :: Generator -> IO Int
get_next g = atomically $ do { n <- readTVar $ counter g
                             ; (writeTVar (counter g)) $! (n+1)
                             ; return n }

reset :: Generator -> Int -> IO ()
reset g n = atomically $ writeTVar (counter g) n

Note the $! that appears in get_next; it is there for the same reason as it appears in the Chan version.

The same set of basic verifications as above ends with:

> [ length $ filter ((==) j) w | j <- [1..10] ]
[9901,90,0,0,0,0,0,0,0,0]

Or, the 10 threads took blocks of 1000 numbers from the sequence because the scheduler had no reason to switch. For a slightly different spawning of clients with a yield added, we get more regular results:

> mapM_ (\n -> (forkIO $ sequence_ $ replicate 1000 (get_next g >>= (writeChan tid).((,) n) >> yield))) [1..10]
[... same steps ...]
[0,0,0,0,0,0,0,0,0,9991]
> (map fst y) == (concat $ replicate 1000 [1..10])
True

Conclusions

The STM version is probably the one that I'll use, but I also need some more complicated components where the channel-based design will work well.

(comment bubbles) 2 comments

The Real Lesson in the Sneetches

Paul Brown @ 2007-10-10T00:25:17Z

The Sneetches is the kid's superfavorite book right now, and I've read it at least twice per bedtime (excepting the story about the two Zaxes and the bit about 23 Daves) every night for the past week. The obvious message in the book is about superficial differences and tolerance.

Call me cynical or jaded from doing a tour of duty as an entrepreneur, but I see another message in the book:

Stars, schmars. You want to be Sylvester McMonkey McBean.
(comment bubbles) 0 comments

λ○λcats

Paul Brown @ 2007-10-09T14:21:57Z

This (seen via the Haskell cafe mailing list) nearly caused me to shoot coffee out my nose:

fillBelly = foldr (++) [] fridgeContents

(comment bubbles) 0 comments

All Posts contains 399 items in 57 pages of 7 items each:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57