db4o Developer Community

db4o open source object database, native to Java and .NET
Welcome to db4o Developer Community Sign in | Join
in Search
More Search Options

Product News from the Core Team

This blog features product news right from the core developer team, once new features and functions get checked into Subversion, available as Continuous Build every 2 hours.

Committed callbacks, pushed updates and read committed isolation revisited

Some time ago we presented Pushed Updates as a sample application for Committed Callbacks in this blog. Recently we put a similar mechanism in a slightly different context. Since we encountered some perceived ambiguity concerning db4o transaction semantics, and since we were in for a little surprise ourselves, I'd like to briefly rehash this topic.

Pushed Updates basically are a mechanism to provide READ COMMITTED isolation from the point of view of your application. Wait - doesn't db4o provide that out of the box?!? Well, it does. Any query is guaranteed to return objects in their most recently committed state - unless they are still in memory on the requesting client, that is. db4o cannot change the state of of a live object that may be involved in application level processes at the same time. Only the application can judge whether it is safe to modify a given object, and Committed Callbacks are the way to transfer this responsibility.

Note that "in memory" above does not mean "in use by the application", and not even necessarily "referenced by the application". An object may still survive in the reference system some time after it has been discarded by the application, until the GC eventually hits. There is no remotely performant way to check whether there exist hard references to a given object.

So much for the theory, let's look at the code. The core of the Pushed Updates example looked like this:
private EventListener4 createCommittedEventListener(final ObjectContainer objectContainer){
  return new EventListener4() {
    public void onEvent(Event4 e, EventArgs args) {
      ObjectInfoCollection updated = ((CommitEventArgs)args).updated();
      Iterator4 infos = updated.iterator();
      while(infos.moveNext()){
        ObjectInfo info = (ObjectInfo) infos.current();
        Object obj = info.getObject();
        objectContainer.ext().refresh(obj, 2);
      }
    }
  };
}
EventListener4 committedEventListener = createCommittedEventListener(client);
EventRegistry eventRegistry = EventRegistryFactory.forObjectContainer(client);
eventRegistry.committed().addListener(committedEventListener);
This code registers a callback for committed events. When invoked, it will traverse all the objects that have been updated during this commit and refresh them. (The magic depth of two is a reverence to collections, forcing them to refresh their elements, too.)

Now this works nicely - in networking C/S mode, where the communication layer between client and server takes care of the correct "translation" of object identities. In embedded C/S mode, however, there is no translation - clients run in the same "live object space" as the server, the only thing they don't share is the weak reference system that defines their notion of object identities. And so the ObjectInfo will contain the server's instance of this persisted object, while the client will have its own version that it won't be able to map to the "stranger".

The fix is easy (and works for networking C/S, too, of course): We have to take care of the translation ourselves via ID lookup. In code:
public void onEvent(Event4 e, EventArgs args) {
  Transaction trans = ((InternalObjectContainer)client).transaction();
  ObjectInfoCollection updated = ((CommitEventArgs)args).updated();
  Iterator4 infos = updated.iterator();
  while(infos.moveNext()){
    ObjectInfo info = (ObjectInfo) infos.current();
    Object obj = trans.objectForIdFromCache((int)info.getInternalID());
    if(obj == null) {
      continue;
    }

    client.refresh(obj, 2);
  }
}
Here we have to fall back on the InternalObjectContainer interface. Don't be too scared of this - it's not really under the hood stuff, but it's indeed intended to be used for 3rd party implementations of pluggable db4o features intended to be run inside the core, like type handlers, reflectors - and callbacks. Note that this variant also keeps the handler from further processing of objects that haven't been known to this client, anyway.

Bottom line: Transaction semantics and live in-memory objects are not entirely trivial to align, object identities can be tricky, and this is the recommended generic way for doing Pushed Updates. For networking C/S, the original Pushed Updates example is fine as it was.
Published Wednesday, June 18, 2008 12:34 AM by Patrick Roemer

Comments

 

db4o Newsletter said:

db4o 7.4 Development Release is available for immediate download ! A R T I C L E S db4o Announces its

June 28, 2008 11:39 PM
Anonymous comments are disabled

This Blog

Syndication RSS Feeds

News

Get the latest features every 2 hours with the Continuous Build!