Concurrency, asynchronous calls, Symbian

This is a combined clearing my head/presenting some common idioms/rant. Feel free to skip it  :-) . All these things are probably very well known
at Symbian, but not necessarily discussed so much out-in-the-world.

In Symbian you are not supposed to do multithreading, instead you are
supposed to use asynchronous requests and the activescheduler. The
reasoning is that this costs a lot less than threads and additionally
non-reentrant code should be easier to write. It's not quite that
simple though...

1: Sequential flow/state machines

Think of making a simple network request. In a sequential program
you'd write something like:

reply DoRequest(aName, aRequest):
    addr=lookup(aName)
    socket.connect(addr)
    socket.write(aRequest)
    socket.receive(reply)
    socket.close()
    return reply

In symbian you write

    void DoRequest(aName, aRequest, aReply*, aRequestStatus&):
        if (iState!=EIdle)
            CompleteRequest(aRequest, KErrServBusy)
        else
            iName=aName
            iRequest=aRequest
            iClientStatus=aRequestStatus
            Lookup()
    RunL:
        if (iStatus!=KErrNone && iState!=EClose)
            iState=EIdle
            CompleteRequest(iClientRequest, iStatus)
        else
        switch(iState)
            ELookUp
                Connect()
            EConnect
                Write()
            EWrite
                Receive()
            EReceive
                Close()
            EClose
                iState=EIdle
                CompleteRequest(iClientRequest,
                    KErrNone)
           

    Lookup:
        iState=ELookUp
        lookup(iName, iAddr, iStatus)
    Connect:
        iState=EConnect
        iSocket.Connect(iAddr)
    Write:
        iState=EWrite
        iSocket.Write(iRequest)
    Receive:
        iState=EReceive
        iSocket.Receive(iReply)
    Close:
        iState=EClose
        iSocket.Close()
    DoCancel:
        // switch statement for cancelling omitted

Hmm. It doesn't look that simple anymore. So we gained the
responsiveness without multithreading and re-entrancy, but
on the other hand the code became a lot more complicated:
    - all of the state necessary for DoRequest()
    has to be in class scope instead of function
    scope. It gets much harder to keep track of the
    variables since they are declared much further away
    from the code that uses them.
    - you have to explicitly code a state machine
    - the logically sequential chain of statements
    gets broken up

(Of course in a realistic application, there is complexity
somewhere else to handle the posting of requests to other
threads and getting results back - so on some level there
are always asynchronous requests and state machines).

And this is the funny part: threads are supposed to be
too expensive largely because they need stack space. Put
since we had to lift all of the local variables from DoRequest
to class scope, we need all that memory anyway. And instead
of being allocated on the stack, which cannot fragment, it's
now on the heap which can. (It'll get released when the object
is deleted, but you could argue that you can also reuse/destroy
threads when the service is not in use).

2: Callbacks or User::RequestComplete

All(?) the old pure client-server requests use TRequestStatus
and active objects for notification of completed requests
(think RFs, RFile, RSocket etc.). A lot of newer modules
(e.g. the Media bits) use callbacks, and of course UI events
come as callbacks through the UI framework code.

Active objects are simple, clean and easy (ish) to get right.
You post a request, when it completes your RunL gets called.
If you need to cancel it, there's supposed to be a Cancel method
on the object you made the request to.

There's one big problem with the pure active object paradigm though:
you can only have one outstanding request at a time. So even
simple things like having a timeout for an async request aren't
possible without something else - a callback for the timer so
that the object can cancel the request.

The other thing with pure active objects is that you get into
trouble if the service that completes those requests runs in
the same thread. Consider first code like:

    RX x; TRequestStatus s;
    x.DoStuff(s);
    User::WaitForRequest(s)

the last line blocks this thread. So if RX runs in the same thread,
the request will never complete. This bites people from time to time.
In this case you can say 'don't do that then'. But there's is
a case where you can't avoid it: cancelling:

CActive::Cancel
    if (IsActive())
        DoCancel()
        User::WaitForRequest(iStatus)
CX::DoCancel
    iX.CancelStuff()

This means that the CancelStuff() has to make absolutely sure
that it finishes the cancelling before returning. No biggie, as
long as the implementor realizes that.

3: Lack of Cancel functions

There are numerous async functions without a corresponding cancel
(RFile::Write, CObexClient::Connect, RConnection::Start). Since
some of these requests might take an indefinite time to complete
it's really painful - you might get a ViewSrv 11 panic just by
trying to cancel an action. Some of them can be worked around,
but none of those workarounds are documented.

4: No timeout on RMutex/RSemaphore ::Wait

If you believe that all code running on the device is perfect,
that's alright. And if you believe that, I've got a bridge
to sell...

In Real Life (tm), you get situations like:
thread 1: RMutex::Wait()
thread 2: RMutex::Wait()
thread 1: crash
thread 2: hang

(esp. since the example server code in Symbian C++ programming
does
    RMutex::Wait()
    if (err) Panic()
    RMutex::Signal()
)

5: (Really) Bad example code

Since the complete state machine in point 1 is completely
useless when trying exemplify how an API is to be used,
the examples do a lot of User::WaitForRequest()s. Which
of course cannot be used in real code. So esp. novice
Symbian coders have a real struggle in going from the example
code to actual code. (Not that this is unusual in any
environment ...)

Quite a few of at least the Nokia examples completely ignore
Cancelling: empty DoCancel() functions in classes that do
async requests.

Since the callback style isn't 'natural' in Symbian, the
rules are not as clear. E.g., when can you delete an object
from its callback? The examples and documentation don't
give you any clues.

In the end, you often do need to do your own client-server
and multithreading in any complex system. But there are hardly
any examples, and even the ones that exist tend to get things
wrong.