STM help
John Meacham
john at repetae.net
Sun Jul 3 15:16:30 EDT 2005
I am attempting to use the STM to rewrite the screen handling code in
ginsu, which frankly, I am a little embarassed of :)
The basic needs are:
- fully concurrent
- all curses calls must go through a single thread (otherwise it gets
real complicated)
- atomic updates to multiple parts of the screen at once.
- efficient update mechanism, screen _must not_ be needlessly redrawn.
So, my idea was basically,
have a screen update thread, this is the only one that will make curses
calls.
everything else communicates with the screen by modifying TVars holding
data values like so
data Widget = HBox { subWidgets :: [TVar Widget] } | Text { body :: TVar String }
and the screen is represented by a top level 'TVar Widget'
This gives the first three requirements very nicely, you can atomically
update several widgets or tex boxes, and everything is very concurrent
in a good way.
The problem comes in when trying to implement an efficient update
mechanism, the problem is that I can't figure out how to wait on only
the relevant changed TVars.
The structure of my curses thread would look like (essentially):
cursesThread head = do
atomically $ do
tv <- readTVar head
doit <- render tv -- returns an IO action
which draws the screen,
calling readTVar on subwidgets.
return doit
doit
-- What to put here to block on relevant changes?
cursesThread head
The problem is that many TVars may contribute to the final screen, one
can change a TVar several widgets down and the screen should be updated.
In addition, the screen should not be redrawn if a TVar for an offscreen
or hidden widget changes. other threads should be able to update state
without worrying about the UI.
Now, i can think of a couple things, neither of which I like very much.
- create a wrapper monad around STM that accumulates a list of TVars
read. This is just unappealing for a variety of reasons.
- have render return some sort of keys to compare values too and retry
if they are all equal to their old values. this would
double the work, one pass to draw it, another pass to figure out we
need to block. it would also really complicate the code.
Now, the other reason these options are undesirable, is that the STM
mechanism collects the exact information I need. AFAIK there is just no
way to get at it. the STM must keep a log of all TVars read in order to
know what to wait on when retrying. What I need is a way to somehow
atomically commit (assuming the transaction succeeds) and then be able
to block on the depended on TVars
This could be done with the addition of a simple primitive:
- | behave exactly like 'atomically' except after the transaction
succeeeds, run the action on the result, and then block until
one of the TVars read by the transaction changes.
atomicallyBlock :: STM a -> (a -> IO b) -> IO b
I think this could be very useful in general, but perhaps there is
something I am not seeing? I am still figuring out idioms to use with
STM.
John
--
John Meacham - ⑆repetae.net⑆john⑈
More information about the Libraries
mailing list