On Smalltalk

thoughts on Smalltalk and programming in general…
  • Home
  • About
  • Good Books
  • My Squeak Image
  • Popular Posts

Ajax: Polling for Long Running Processes in Seaside with Scriptaculous

By Ramon Leon - September 25, 2006 under Programming, Seaside, Smalltalk

A Seaside session is inherently single threaded, so I can’t use Ajax calls to do long running processes without tying up the main session and hanging the app from the users point of view. In order to get around this, I had to change my approach. Rather than launch a process and wait for the result, I fork the process, return immediately, and poll for the result to show up in a dictionary that is shared via the lexical environment the process was forked from.

I’m using Scriptaculous and its periodical callback mechanism to poll for updates. On the periodical, I set a high decay rate, say 1000. This will stop the polling if the same answer comes back twice. I then return a unique answer, a hidden input, for each poll, preventing the decay from kicking in. When I want to stop polling, I stop rendering the unique hidden value, and polling will stop after the next hit.

I’ve added a couple small extension methods to WATagBrush, but they seem to be working pretty well for me so far.

WATagBrush>>with: initialBlock waitMax: aDuration
    forWork: aBlock thenRender: aRenderBlock

    self attributeAt: #default put: initialBlock value.
    self with: initialBlock.
    self waitMax: aDuration forWork: aBlock thenRender: aRenderBlock

WATagBrush>>waitMax: aDuration
    forWork: aBlock thenRender: aRenderBlock

    "poll for results, stop when found or timeout expires"

    | result |
    result := self forkWaitFor: aDuration longRunningProcess: aBlock.
    canvas script: ((canvas periodical)
        id: (self attributes at: #id);
        decay: 1000;
        asynchronous: true;
        evalScripts: true;
        frequency: 2;
        callback: [:r |
            (result at: #result) ifNil:
                    [r text: (self attributeAt: #default).
                    r hiddenInput text: r nextId]
                ifNotNil: [aRenderBlock value: r value: (result at: #result)]])

WATagBrush>>forkWaitFor: aDuration longRunningProcess: aBlock
    | result |
    result := (Dictionary new)
                at: #result put: nil;
                yourself.

    [[[(result at: #result) isNil] whileTrue:
            [result at: #result
                put: (aBlock valueWithin: aDuration onTimeout: ['timeout'])]]
            on: Error
            do: [:error | result at: #result put: error messageText]]
            forkNamed: ‘ajax async:’.
    ^result

I use it like so…

html div
    with: 'Searching...'
    waitMax: 30 seconds
    forWork: [self getDataFromLongRunningProcess]
    thenRender: [:r :value | r text: value]

I’ll have to fix this up in the near future, to make #with: work with blocks like it should, but it’s serving me well at the moment.

Tags: Seaside

Related posts
    at: "Smalltalk In Action";
    at: "Scaling Seaside";
    at: "Performance Profiling in Squeak Smalltalk";

5 Comments so far

  1. … of many things … » Polling for long running processes in Seaside on December 1st, 2006

    [...] Another good post from Ramon Léon. [...]

  2. Giles Bowkett on December 23rd, 2006

    Does this single-thread thing present a real problem? The most I’ve done in Seaside so far is following along with your blog screencast, but in one of the podcasts I’ve heard him on, Avi Bryant said Dabble uses Ajax mostly for pre-loading data, rather than UI stuff, so it must be feasible. Is changing the single-threaded nature of Seaside possible? Have you found this to be tricky only in unusual situations? I know most uses of Ajax don’t generally involve long-running processes, although polling for the status of such processes is a pretty well-accepted method of handling them. (Sorry if the questions are overkill.)

  3. Ramon Leon on December 23rd, 2006

    Actually, its single threaded nature is a feature, a necessary one, when you consider that requests come in on different threads, but the server contains long lived objects that must serve them all. Rather than making you think about such things, and do the locking yourself, Seaside simply forces all access to a session through a lock allowing one request at a time to access the session. So it’s not something you want to change, just something you need to be aware of, and how know how to work around, should the need arise.

    This isn’t normally a problem in other frameworks because they don’t keep things like pages and controls alive between multiple requests like Seaside does, but, it is something you have to be aware of if you try and do something like I did in this article, because otherwise you’ll see all your processes that you expect to run asynchronously running serially and you won’t know why.

    When not doing Ajax, you’d never notice this anyway, but Ajax allows a page to kick off multiple threads on the server and do work asyncronously, which personally, I find highly useful, for much more than loading data.

    I used this because I had some local data I could show right away, and then some other related data from remote systems that may or may not come back, and take several seconds to fetch. This allowed me to show the user what I had instantly and inform them, in the page widgets, that more data was being fetched, and when it comes in it’ll display. I consider this little piece of code critical to the user experience in my applications. Consider how useful this might be in a mashup where you get data from many sources you might not control, or want to wait for.

  4. Nick Alexander on January 26th, 2008

    For future users of this snippet: if the id of the element displaying the output is not set, the updater will silently do nothing. Try:

    html div
    id: ’someid’;
    with: ‘Searching…’
    waitMax: 30 seconds
    forWork: [self getDataFromLongRunningProcess]
    thenRender: [:r :value | r text: value]

    Thanks Ramon, for the best seaside blog of them all!

  5. Ramon Leon on January 27th, 2008

    No problem, it’s a rather small pond.

Posting your comment.


  • Sponsors

  • Tags

    Databases General Linux Lisp Magritte Performance Profiling Programming Ruby Seaside Smalltalk Sql Squeak Updates
  • Categories

    • .Net (5)
    • Databases (9)
    • General (5)
    • Linux (2)
    • Lisp (3)
    • Magritte (2)
    • Programming (62)
    • Ruby (6)
    • Seaside (42)
    • Smalltalk (72)
    • Sql (2)
    • Stuff I Just Like (6)
    • Updates (7)
  • Blogs

    • (gem)Stone Soup
    • Avi Bryant
    • Boris Popov
    • defmacro
    • Giles Bowkett
    • Goran Krampe
    • James Robertson
    • Lukas Renggli
    • Martin Fowler
    • Paul Graham
    • Ralph Johnson
    • Randal Schwartz
    • Vassili Bykov
    • Weekly Squeak
  • Favorite Tools

    • Apache
    • Cygwin
    • FireFox
    • Scriptaculous
    • Seaside
    • Squeak
    • Squeak Dev Image
    • Ubuntu Linux
    • WordPress
  • Meta

    • Log in
    • Entries RSS
    • Comments RSS
    • WordPress.org

Copyright © 2008 On Smalltalk