Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lightweight wrapper - is this a common problem and if yes, what is its name?

I have to use a library that makes database calls which are not thread-safe. Also I occasionally have to load larger amounts of data in a background thread.
It is hard to say which library functions actually access the DB, so I think the safest approach for me is to protect every library call with a lock.

Let's say I have a library object:

dbLib::SomeObject someObject;

Right now I can do something like this:

dbLib::ErrorCode errorCode = 0;
std::list<dbLib::Item> items;
{
    DbLock dbLock;
    errorCode = someObject.someFunction(&items);
} // dbLock goes out of scope

I would like to simplify that to something like this (or even simpler):

dbLib::ErrorCode errorCode =
    protectedCall(someObject, &dbLib::SomeObject::someFunction(&items));

The main advantage of this would be that I won't have to duplicate the interface of dbLib::SomeObject in order to protect each call with a lock.

I'm pretty sure that this is a common pattern/idiom but I don't know its name or what keywords to search for. (Looking at http://www.vincehuston.org/dp/gof_intents.html I think, it's more an idiom than a pattern).

Where do I have to look for more information?

like image 575
foraidt Avatar asked Jul 11 '11 10:07

foraidt


5 Answers

You could make protectedCall a template function that takes a functor without arguments (meaning you'd bind the arguments at the call-site), and then creates a scoped lock, calls the functor, and returns its value. For example something like:

template <typename Ret>
Ret protectedCall(boost::function<Ret ()> func)
{
    DbLock lock;
    return func();
}

You'd then call it like this:

dbLib::ErrorCode errorCode = protectedCall(boost::bind(&dbLib::SomeObject::someFunction, &items));

EDIT. In case you're using C++0x, you can use std::function and std::bind instead of the boost equivalents.

like image 66
reko_t Avatar answered Nov 05 '22 13:11

reko_t


In C++0x, you can implement some form of decorators:

template <typename F>
auto protect(F&& f) -> decltype(f())
{
    DbLock lock;
    return f();
}

usage:

dbLib::ErrorCode errorCode = protect([&]() 
{
    return someObject.someFunction(&items); 
});
like image 21
Alexandre C. Avatar answered Nov 05 '22 14:11

Alexandre C.


From your description this would seem a job for Decorator Pattern.

However, especially in the case of resources, I wouldn't recommend using it.

The reason is that in general these functions tend to scale badly, require higher level (less finegrained) locking for consistency, or return references to internal structures that require the lock to stay locked until all information is read.

Think, e.g. about a DB function that calls a stored procedure that returns a BLOB (stream) or a ref cursor: the streams should not be read outside of the lock.

What to do?

I recommend instead to use the Facade Pattern. Instead of composing your operations directly in terms of DB calls, implement a facade that uses the DB layer; This layer could then manage the locking at exactly the required level (and optimize where needed: you could have the facade be implemented as a thread-local Singleton, and use separate resources, obviating the need for locks, e.g.)

like image 4
sehe Avatar answered Nov 05 '22 13:11

sehe


The simplest (and still straightforward) solution might be to write a function which returns a proxy for the object. The proxy does the locking and overloads -> to allow calling the object. Here is an example:

#include <cstdio>

template<class T>
 class call_proxy 
 {
  T &item; 
  public:

  call_proxy(T &t) : item(t) { puts("LOCK"); }
  T *operator -> () { return &item; }
  ~call_proxy() { puts("UNLOCK"); }
 };

template<class T>
call_proxy<T> protect(T &t) 
{    
 return call_proxy<T>(t);
}

Here's how to use it:

class Intf
{
 public:
  void function() 
 {
  puts("foo");
 }
};

int main()
{
 Intf a; 
 protect(a)->function(); 
}

The output should be:

LOCK
foo
UNLOCK

If you want the lock to happen before the evaluation of the arguments, then can use this macro:

#define PCALL(X,APPL) (protect(X), (X).APPL)
PCALL(x,x.function());

This evaluates x twice though.

like image 1
Nordic Mainframe Avatar answered Nov 05 '22 14:11

Nordic Mainframe


This article by Andrei Alexandrescu has a pretty interesting article how to create this kind of thin wrapper and combine it with dreaded volatile keyword for thread safety.

like image 1
Tomek Avatar answered Nov 05 '22 14:11

Tomek