[concurrency-interest] thread queueing qn

Tim Peierls tim at peierls.net
Sun Jan 20 17:48:46 EST 2008


On Jan 18, 2008 5:37 PM, Dhanji R. Prasanna <dhanji at gmail.com> wrote:

> Each conversation is modeled as a ConversationContext object that may be
> stored in a permanent storage medium (custom impl by a user). My solution is
> to obtain the ConversationContext from a store (creating a new one as
> necessary) and then synchronizing on it while processing the request:
>
> doFilter( ...) {
>    ConversationContext con = readFromStoreById(..);
>    synchronized(con) {
>          filterChain.doFilter(...);     //process request normally
>    }
> }
>

This is fine if readFromStoreById really always returns the identical
ConversationContext object for the same conversation. But your exchange with
Rémi suggests that this is not likely.


This gives me queueing so that no two requests belonging to the *same* user
> conversation are processed concurrently. However I see a couple of
> drawbacks:
>
> - synchronizing on a public object from a store is potentially dangerous,
> as poorly written user impls can deadlock or starve request threads
>

They can do this whether or not you synchronize on the object that they
return. readFromStoreById needs to be thread-safe, regardless of your
strategy for preventing two requests from the same conversation from being
processed concurrently.


One alternative I thought of was to keep a private registry of active
> conversation keys (j.u.c.l.Locks in a hashtable) and lock the keys
> instead. These work for the majority of cases, but there are times when a
> conversation needs to be "merged" into a prior context, say from the
> permanent store (key "collapsing", if you will). In this case the locks need
> to be merged too--what is the best way to do this?
>

You could have a ConcurrentMap<Key, Object> and synchronize on the *value*
Objects, not the Keys. Use putIfAbsent to add key/object pairs atomically.
Merge is less common, easier to make it synchronized to avoid dealing with
simultaneous merges of different pairs. Sample code:

  interface KeyedLock<K> {
      Object lockFor(K key);
      void mergeKeys(K key1, K key2);
  }

  class KeyedLockImpl<K> implements KeyedLock<K> {

      public Object lockFor(K key) {
          Object lock = map.get(key);
          if (lock == null) {
              lock = new Object();
              Object prev = map.putIfAbsent(key, lock);
              if (prev != null) lock = prev;
          }
          return lock;
      }

      public synchronized void mergeKeys(K key1, K key2) {
          map.replace(key2, lockFor(key2), lockFor(key1));
      }

      private final ConcurrentMap<K, Object> map =
          new ConcurrentHashMap<K, Object>();
  }

Then you can write:

    void doFilter(Request req, Response rsp, FilterChain filterChain) {
        String key = conversationKeyFrom(req);
        synchronized (conversationLocks.lockFor(key)) {
            filterChain.doFilter(req, rsp);
        }
    }

    final KeyedLock<String> conversationLocks = new KeyedLockImpl<String>();

If you need to wait for handling on a conversation to complete before
merging it into another conversation, you could use Lock instead of Object,
and do things like tryLock during a merge attempt to see whether it's OK to
merge. Or you could wait on a Condition -- but this is starting to get
complicated. Depends on how frequent merges are, and how critical it is that
processing be completed on a conversation before the merge happens.

--tim
-------------- next part --------------
An HTML attachment was scrubbed...
URL: /pipermail/attachments/20080120/0259f48e/attachment.html 


More information about the Concurrency-interest mailing list