April 18, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

A Generic Connection Framework for BREW

  • July 30, 2003
  • By Radu Braniste
  • Send Email »
  • More Articles »

Preamble

The article, structured in two parts, tries to establish a common ground for I/O and network operations on BREW and encapsulate this commonality in a framework. Such a framework is used in J2ME to insulate the developer of protocol implementation details, to offer the needed flexibility in supporting new devices and protocols, as well as to define a uniform way of accessing different resources. These reasons are compelling enough to investigate a similar approach for BREW—with an additional challenge: BREW is asynchronous.

First, we will briefly examine the intricacies of the asynchronous programming model and develop some tools to help in our endeavor. As (unfortunately!) there is nothing for free, we'll easily discover that compilers might cruelly dictate the rules of the game. Finding our way in the maze, putting the pieces together, and creating the framework are the fun of the day.

As the final goal is a framework for I/O and network resources handling, we will introduce two simple BREW-specific encapsulations—one for the file manipulation and another one for HTTP—to be used in conjunction with the framework. This is a good opportunity to review some implementations aspects of these interfaces.

Laying the Foundation

BREW is asynchronous by nature—a necessity derived from its design (single threaded, event based, with a watchdog checking the healthy of the system periodically approximately every 60 seconds). This means that otherwise blocking calls are delayed and executed in a non-blocking manner at a later time, control being relinquished after that to a callback function registered at call time (see Command pattern, BeginXXX/EndXXX idiom in .NET, some Observer pattern variants, and so forth).

One example might be derived from the execution of a long, blocking task.

void Caller::executeLengthyOp()
{
  Resource r;
  r.executeTask();
}

This is strictly forbidden in BREW and the famous watchdog might reset the phone. That's why developers have to split the code exceeding the watchdog interval in small chunks, like this:

void Resource::execute()
{
  doOneTimeOperation();
  giveBackControlAsynchronously (&Resource::execute);
  return;    //to main event loop
}

I purposely emphasized the fact that myTaskExecute() has to return control to the main loop (HandleEvent). The interesting part here is giveBackControlAsynchronously (). This method registers (using BREW specific functionality) a callback (in this example re-registers) for later execution or posts an event and quits—an example of non-blocking, asynchronous execution.

Please note this is perfectly valid, too [3]:

void Resource::execute()
{
  giveBackControlAsynchronously (&Resource::execute);
  doOneTimeOperation();
  return;    //to main event loop
}

The above is a user-driven, non-forcing usage pattern (for a complete discussion see [1]).

There is another, system-driven, idiom involving the handling of system resources. ISOCKET_Connect is a good example of a prepackaged asynchronous call. Behind the scenes, it does lots of work and when it finishes it passes you back the result.

ISOCKET_Read works in pair with ISOCKET_Readable like:

int32 rv = ISOCKET_Read(piSock_, buf_, ds_->bufSize);
if  (rv == AEE_NET_WOULDBLOCK) 
{
  ISOCKET_Readable(piSock_, (PFNNOTIFY)P::callbackHandler, &p_);
  doSometingElse();
  return;
}
else ....

The system informs us that a blocking operation is in effect and the only way to continue the reading operation is to register for later processing. What these two last examples have in common is that the system is in control. All tasks involving possible lengthy system resources manipulation should obey this idiom—but regrettably we don't live in a perfect world and exceptions are always possible.

Now, let's concentrate on the mechanics of an async call (forgetting for one moment the infamous watchdog) and make a brief comparison with a synchronous one:

void Resource::executeAsync()
{
  connect();
  return;    //to main event loop
}
void Resource:: connect ()
{
  String data = connectAndGetData ();
  if (data.isEmpty()
  {
    register (&Resource::connect) ;
  }
  else
  {
    useData(data);
  }
  return;    //to main event loop
}

versus:

void Resource::executeSync()
{
  String data = connectAndGetData ();
  useData(data);
  return;    //to main event loop
}

What we can immediately observe is the fragmentation of the code logic in the async version—scattered now in two functions. The second observation is that useData() has nothing to do with Resource class and it should be injected by the caller in the Resource space. And, finally, the third observation is that conectAndGetData() has to preserve the state between calls—and as in BREW static or global data are absolutely forbidden the only way to do it is to encapsulate the data in MyClass. The RAII—Resource Acquisition Is Initialization [2]—idiom seems to be the natural choice:

typedef void (Caller::*FND)(  );
class ResourceHandle
{
  ResourceHandle (FND f) : f_(f)
  {connect();}
  ~ ResourceHandle ()
  { releaseResources();  }

private:
  ResourceHandle (const ResourceHandle &);
  void operator=(const ResourceHandle &);
  String connectAndGetData();
  FND f_;
  void releaseResources;    //cancels the callback
  //"static" data used by connectAndGetData(), for example:
  String buffer_;
};

Usage:

void Caller::someFunction()
{
  ResourceHandle rh(&Caller::useData);
}

Nice, but pointless—rh is destroyed at the end of the someFunction() body and the callback is never called. A better method would be:

void Caller::someFunction()
{
  ResourceHandle * rh = new ResourceHandle(&Caller::useData);
}

We have now a behavior similar to the synchronous case, but with an extra problem—finding a way to delete rh to avoid a memory leak. There are two options:

  1. Make rh a private member of Caller—with the disadvantage that Caller has to keep track of ResourceHandle (and maybe other handles of other types).
  2. Create a central Handle repository that keeps track of the myriad of resources that might be used in the lifespan of the application. This looks more promising because all we have to keep track of in this case is the repository itself, once per application.

Now that we know how to deal with resources, let's go a little bit deeper—the callback registration mechanism level, namely. Because BREW is a C-based environment, it uses C functions for callbacks, passing the state through an opaque void* pointer. This usually translates into the following C++ idiom:

void Resource:: connectImpl ()
{
  register (&Resource::connect, this) ;
}

static void Resource:: connect (Resource * r)
{
  r->connectImpl();
}

There is an elegant way to get rid of the static method and use C++ callbacks:

void Resource:: connect ()
{
  p_.setCallback((&Resource::connect, this);
  register (&P::callbackHandler, &p_) ;
}

The mysterious P is actually an adapter, like:

template <class Y, class F>
struct pkCBK0
{
  void setCallback(Y* y, F fx)
  {
    y_ = y;
    f_ = fx;
  }
  static void callbackHandler(void* v)
  {
    typedef pkCBK0<Y,F> pk;
    pk* z = static_cast<pk*>(v);
    F fx = z->f_;
    Y* y = z->y_;
    (*y.*fx)();
  }
  Y* y_;
  F f_;
};

The implementation takes advantage of the void* pointer—passing both the real instance (the original "this") as well as the method address in the same void*. There is a downside, of course; p_ has to be a member of Resource as p_ is now the "state keeper." For a resource, this is not a problem—because a resource is essentially an encapsulation. The interesting case is when a P callback has to be used by a Caller. The solution at rescue might be again the central repository, as we'll see shortly.





Page 1 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel