Files
spring-net/doc/reference/src/pooling-example.xml

268 lines
10 KiB
XML

<sect1>
<title>Pooling example</title>
<para>
The idea is to build an executor backed by a pool of
<literal>QueuedExecutor</literal>: this will show how Spring.NET
provides some useful low-level/high-quality reusable threading and
pooling abstractions.
This executor will provide parallel executions (in our case
<literal>grep</literal>-like file scans). <emphasis>Note: This example
is not in the 1.0.0 release to its use of classes in the Spring.Threading
namespace scheduled for release in Spring 1.1. To access ths example
please get the code from CVS <ulink url="http://opensource.atlassian.com/confluence/spring/display/NET/Project+Structure">(instructions)</ulink> or from the download section of the
Spring.NET website that contains an .zip with the full CVS tree.</emphasis>
</para>
<para>
Some information on <literal>QueuedExecutor</literal> is helpful to
better understand the implementation and to possibly disagree with it.
Keep in mind that the point is to show how to develop your own
object-pool.
</para>
<para>
A <literal>QueuedExecutor</literal> is an executor where
<literal>IRunnable</literal> instances are run serialy by a worker
thread. When you <literal>Execute</literal> with a
<literal>QueuedExecutor</literal>, your request is queued; at some
point in the future your request will be taken and executed by the
worker thread: in case of error the thread is terminated.
However
this executor recreates its worker thread as needed.
</para>
<para>Last but not least, this executor can be shut down in
a few different ways (please refer to the Spring.NET SDK documentation).
Given its simplicity, it is very powerful.
</para>
<para>
The example project <literal>Spring.Examples.Pool</literal> provides
an implementation of a pooled executor, backed by n instances of
<literal>Spring.Threading.QueuedExecutor</literal>: please ignore
the fact that <literal>Spring.Threading</literal> includes already a
very different implementation of a <literal>PooledExecutor</literal>:
here we wanto to use a pool of <literal>QueuedExecutor</literal>s.
</para>
<para>
This executor will be used to implement a parallel
recursive <literal>grep</literal>-like console executable.
</para>
<sect2>
<title>Implementing <literal>Spring.Pool.IPoolableObjectFactory</literal></title>
<para>
In order to use the <literal>SimplePool</literal> implementation,
the first thing to do is to implement the <literal>IPoolableObjectFactory</literal>
interface. This interface is intended to be implemented by objects
that can create the type of objects that should be pooled.
The <literal>SimplePool</literal>
will call the lifecycle methods on <literal>IPoolableObjectFactory</literal> interface
(<literal>MakeObject, ActivateObject, ValidateObject, PassivateObject, and DestroyObject</literal>)
as appropriate when the pool is created, objects are borrowed and returned to the pool, and when
the pool is destroyed.
</para>
<para>
In our case, as already said, we want to to implement a pool
of <literal>QueuedExecutor</literal>. Ok, here the declaration:
<programlisting language="csharp">public class QueuedExecutorPoolableFactory : IPoolableObjectFactory
{</programlisting>
the first task a factory should do is to create objects:
<programlisting language="csharp">object IPoolableObjectFactory.MakeObject()
{
// to actually make this work as a pooled executor
// use a bounded queue of capacity 1.
// If we don't do this one of the queued executors
// will accept all the queued IRunnables as, by default
// its queue is unbounded, and the PooledExecutor
// will happen to always run only one thread ...
return new QueuedExecutor(new BoundedBuffer(1));
}</programlisting>
and should be also able to destroy them:
<programlisting language="csharp">void IPoolableObjectFactory.DestroyObject(object o)
{
// ah, self documenting code:
// Here you can see that we decided to let the
// executor process all the currently queued tasks.
QueuedExecutor executor = o as QueuedExecutor;
executor.ShutdownAfterProcessingCurrentlyQueuedTasks();
}</programlisting>
</para>
<para>
When an object is taken from the pool, to satisfy a client request,
may be the object should be activated. We can possibly implement the
activation like this:
<programlisting language="csharp">void IPoolableObjectFactory.ActivateObject(object o)
{
QueuedExecutor executor = o as QueuedExecutor;
executor.Restart();
}</programlisting>
even if a <literal>QueuedExecutor</literal> restarts itself as
needed and so a valid implementation could leave this method empty.
</para>
<para>
After activation, and before the pooled object can be succesfully
returned to the client, it is validated (should the object be
invalid, it will be discarded: this can lead to an empty unusable
pool
<footnote>
<para>You may think that we can provide a smarter
implementation and you are probably right. However, it is not so
difficult to create a new pool in case the old one became unusable.
It could not be your preferred choice but surely it leverages
simplicity and object immutability
</para>
</footnote>).
Here we check that the worker thread exists:
<programlisting language="csharp">bool IPoolableObjectFactory.ValidateObject(object o)
{
QueuedExecutor executor = o as QueuedExecutor;
return executor.Thread != null;
}</programlisting>
</para>
<para>
Passivation, symmetrical to activation, is the process a pooled
object is subject to when the object is returned to the pool. In our
case we simply do nothing:
<programlisting language="csharp">void IPoolableObjectFactory.PassivateObject(object o)
{
}</programlisting>
</para>
<para>
At this point, creating a pool is simply a matter of creating an
<literal>SimplePool</literal> as in:
<programlisting language="csharp">pool = new SimplePool(new QueuedExecutorPoolableFactory(), size);</programlisting>
</para>
</sect2>
<sect2>
<title>Being smart using pooled objects</title>
<para>
Taking advantage of the <literal>using</literal> keyword seems
to be very important in these <literal>c#</literal> days, so we
implement a very simple helper (<literal>PooledObjectHolder</literal>)
that can allow us to do things like:
<programlisting language="csharp">using (PooledObjectHolder holder = PooledObjectHolder.UseFrom(pool))
{
QueuedExecutor executor = (QueuedExecutor) holder.Pooled;
executor.Execute(runnable);
}</programlisting>
without worrying about obtaining and returning an object from/to the
pool.
</para>
<para>
Here is the implementation:
<programlisting language="csharp">public class PooledObjectHolder : IDisposable
{
IObjectPool pool;
object pooled;
/// &lt;summary&gt;
/// Builds a new &lt;see cref=&quot;PooledObjectHolder&quot;/&gt;
/// trying to borrow an object form it
/// &lt;/summary&gt;
/// &lt;param name=&quot;pool&quot;&gt;&lt;/param&gt;
private PooledObjectHolder(IObjectPool pool)
{
this.pool = pool;
this.pooled = pool.BorrowObject();
}
/// &lt;summary&gt;
/// Allow to access the borrowed pooled object
/// &lt;/summary&gt;
public object Pooled
{
get
{
return pooled;
}
}
/// &lt;summary&gt;
/// Returns the borrowed object to the pool
/// &lt;/summary&gt;
public void Dispose()
{
pool.ReturnObject(pooled);
}
/// &lt;summary&gt;
/// Creates a new &lt;see cref=&quot;PooledObjectHolder&quot;/&gt; for the
/// given pool.
/// &lt;/summary&gt;
public static PooledObjectHolder UseFrom(IObjectPool pool)
{
return new PooledObjectHolder(pool);
}
}</programlisting>
</para>
<para>
Please don't forget to destroy all the pooled istances once you have
finished! How? Well using something like this in
<literal>PooledQueuedExecutor</literal>:
<programlisting language="csharp">public void Stop ()
{
// waits for all the grep-task to have been queued ...
foreach (ISync sync in syncs)
{
sync.Acquire();
}
pool.Close();
}</programlisting>
</para>
</sect2>
<sect2>
<title>Using the executor to do a parallel <literal>grep</literal></title>
<para>
The use of the just built executor is quite straigtforward but a
little tricky if we want to really exploit the pool.
<programlisting language="csharp">private PooledQueuedExecutor executor;
public ParallelGrep(int size)
{
executor = new PooledQueuedExecutor(size);
}
public void Recurse(string startPath, string filePattern, string regexPattern)
{
foreach (string file in Directory.GetFiles(startPath, filePattern))
{
executor.Execute(new Grep(file, regexPattern));
}
foreach (string directory in Directory.GetDirectories(startPath))
{
Recurse(directory, filePattern, regexPattern);
}
}
public void Stop()
{
executor.Stop();
}</programlisting>
</para>
<para>
<programlisting language="csharp">public static void Main(string[] args)
{
if (args.Length &lt; 3)
{
Console.Out.WriteLine(&quot;usage: {0} regex directory file-pattern [pool-size]&quot;, Assembly.GetEntryAssembly().CodeBase);
Environment.Exit(1);
}
string regexPattern = args[0];
string startPath = args[1];
string filePattern = args[2];
int size = 10;
try
{
size = Int32.Parse(args[3]);
}
catch
{
}
Console.Out.WriteLine (&quot;pool size {0}&quot;, size);
ParallelGrep grep = new ParallelGrep(size);
grep.Recurse(startPath, filePattern, regexPattern);
grep.Stop();
}</programlisting>
</para>
</sect2>
</sect1>