You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@mina.apache.org by Emmanuel Lecharny <el...@gmail.com> on 2008/11/07 02:59:59 UTC

[Chain refactoring] Some fedback and ideas

Hi guys,

here are some thoughts and ideas I have had those last few days, 
thoughts that I have experimented in branches. It's not finished yet, 
but I think I have something likely to work well and be easier to use 
than the current implementation.

So here are the gathered thoughts, please feel free to give me feedback :


Refactoring the current chain : possible implementations
---------------------------------------------------------

So, guys, after having spent a few days thinking and experiencing, here 
are the different possibilities I have thought about. I will try to list 
all of them, with their pros and cons.

1) The chain, and the chain builder
-----------------------------------

We use a chain of filters to process events. An event, once processed by 
either the Acceptor/Connector or the Processor, is pushed through a 
chain of filters, up to a handler, and a tail filter which does nothing 
but terminate the chain. This chain is associated witheach new session, 
and copied from a template, which is created before we start the 
client/server (depending on what you are iomplementing).

The chain template is created using a chain builder. The idea is to 
first create a chain template, which will be copied when a new session 
is created, so that we will be able to dynamically modify the copied 
chain within the session without modifying the template (for instance, 
you can dynamically add a logging filter during a session)

So we have two different vision for the chain, a template, and N 
instance of this template (one per session).

1.1) Operations on a chain
------------------------
We want to be able to walk the chain, jumping from one filter to the 
next one. We also want to add or remove filters, dynamically. Both 
chains (template and instances) dn't support all of these operations : 
it's obviously useless to process event on the template, we just want to 
be able to manage the filters into the chain while building it. Also, 
the copy should be fast and cost as less as memory as possible.

When an event is processed, it goes through events following this logic :

processEventXXX() {
  doPreOperation();
  callNextFilter();
  doPostOperation()
}

the callNexrFilter() is the important part of the problem : how do we 
jump to the next filter, allow a dynamic modification of the chain, and 
protect the chain against concurrent modifications ?

It also has to be fast, and easy to debug... Challenging !

2) Proposed solutions
---------------------

2.1) Using List<T>
---------------------------------

We can imagine that filters are stored into a list, and that we process 
an event through all the filters this way :

for (Filter filter:listOfFilters ) {
  filter.processEventXXX();
}

It will obviously work, be easy to debug, be fast, but we will have two 
problems here :
- we have to use a synchronized list, in order to guarantee that we 
can't modify the list while jumping from one filter to another
- we can't any more call pre and post processing.

The first point is a bit problematic, because we have to lock all the 
list. Performance can suffer, but not that much, considering that we 
don't have so much chain modifications (hopefully)
The real issue is more serious. One improvement would be to have such a 
loop :

for (Filter filter:listOfFilters ) {
  filter.doPreXXX()
  filter.processEventXXX();
  filter.doPostXXX()
}

but this is far from being enough.

How do we handle the list modification ? As it's a list, we have all the 
LIST API to do that : add, add(index), etc. Easy, as we can access this 
list from the session object.


2.2) Using a linked list
------------------------

Instead of using a list, we can also link the filters. Passing from one 
filter to the other will then be just a matter of doing :

F1.processEventXXX()
  pre processing
  nextFilter.processEventXXX()
  post processing

The nextFilter is a field pointing to the next filter in the chain.

This solution has the big advantage of being very simple to implement, 
allows pre and post processing, whatever it can be, and fast and easy to 
debug.

The main issue is that it's difficult to protect this chain from 
concurrent modification, unless you synchronize the nextFilter field :
F1.processEventXXX()
  pre processing
  synchronized( nextFilter ) {
    nextFilter.processEventXXX()
  }
  post processing

This may be acceptable, though. The cost of checking if this 
synchronization might be not null, as we can see that if the chain 
contains many filters,
we will have as many synchronized sections as we have filter, plus the 
time during which the section is synchronized is the global execution time.

Modifying the list is quite simple. As each filter has a link to the 
next filter, it's just a matter of manipulating this list to add or 
remove a filter. Obviously, adding or removing a filter back in the 
chain is useless.

2.3 Using a double level structure
----------------------------------
The idea is to separate the filters from the chain. We will store the 
chain into a list, and each element fo this list is a filter. This list 
is handled by the session.
Here is the code :

// First invocation
processEvent(XXX) {
  session.getFilter(0).processEventXXX( session, 0 );
}

// Nth invocation
Fx.processEventXXX(session, pos) {
  pre processing
  pos++
  session.getFilter(pos).processEventXXX(session, pos)
  post processing
}

// And of course, the TailFilter won't call the next filter :)

How do we modifiy the list ?

Fx.processEventXXX(session, pos) {
  pre processing
  pos++
  // Add a new filter to the chain
  session.addFiler(pos, filter)
  session.getFilter(pos).processEventXXX(session, pos)
  post processing
}

The session.addFilter() method will just update the list, and then the 
next call will be done. If we use a CopyOnWriteArrayList, we are thread 
safe.

So far, using this method, we have all the advantages and none of the 
inconvenients we have seen in the other methods.

3) Choice
---------

If we cant to keep all the existing functionalites MINA has, solution 
2.3 is obviously the way to go. Well implemented, it will also be fast 
and easy to debug, and, last, not least, it's close to what we curently 
have, but with less code and a better interface.

-- 
--
cordialement, regards,
Emmanuel Lécharny
www.iktek.com
directory.apache.org



Re: [Chain refactoring] Some fedback and ideas

Posted by Julien Vermillard <jv...@archean.fr>.
Hi you know how much I prefer 2.1 but the need of post() method force
us reworking all the filters, so 2.3 is the way to go.

Keep 2.1 for the big 3.0 API breakup ;)
Julien

On Fri, 07 Nov 2008 02:59:59 +0100
Emmanuel Lecharny <el...@gmail.com> wrote:

> Hi guys,
> 
> here are some thoughts and ideas I have had those last few days, 
> thoughts that I have experimented in branches. It's not finished yet, 
> but I think I have something likely to work well and be easier to use 
> than the current implementation.
> 
> So here are the gathered thoughts, please feel free to give me
> feedback :
> 
> 
> Refactoring the current chain : possible implementations
> ---------------------------------------------------------
> 
> So, guys, after having spent a few days thinking and experiencing,
> here are the different possibilities I have thought about. I will try
> to list all of them, with their pros and cons.
> 
> 1) The chain, and the chain builder
> -----------------------------------
> 
> We use a chain of filters to process events. An event, once processed
> by either the Acceptor/Connector or the Processor, is pushed through
> a chain of filters, up to a handler, and a tail filter which does
> nothing but terminate the chain. This chain is associated witheach
> new session, and copied from a template, which is created before we
> start the client/server (depending on what you are iomplementing).
> 
> The chain template is created using a chain builder. The idea is to 
> first create a chain template, which will be copied when a new
> session is created, so that we will be able to dynamically modify the
> copied chain within the session without modifying the template (for
> instance, you can dynamically add a logging filter during a session)
> 
> So we have two different vision for the chain, a template, and N 
> instance of this template (one per session).
> 
> 1.1) Operations on a chain
> ------------------------
> We want to be able to walk the chain, jumping from one filter to the 
> next one. We also want to add or remove filters, dynamically. Both 
> chains (template and instances) dn't support all of these
> operations : it's obviously useless to process event on the template,
> we just want to be able to manage the filters into the chain while
> building it. Also, the copy should be fast and cost as less as memory
> as possible.
> 
> When an event is processed, it goes through events following this
> logic :
> 
> processEventXXX() {
>   doPreOperation();
>   callNextFilter();
>   doPostOperation()
> }
> 
> the callNexrFilter() is the important part of the problem : how do we 
> jump to the next filter, allow a dynamic modification of the chain,
> and protect the chain against concurrent modifications ?
> 
> It also has to be fast, and easy to debug... Challenging !
> 
> 2) Proposed solutions
> ---------------------
> 
> 2.1) Using List<T>
> ---------------------------------
> 
> We can imagine that filters are stored into a list, and that we
> process an event through all the filters this way :
> 
> for (Filter filter:listOfFilters ) {
>   filter.processEventXXX();
> }
> 
> It will obviously work, be easy to debug, be fast, but we will have
> two problems here :
> - we have to use a synchronized list, in order to guarantee that we 
> can't modify the list while jumping from one filter to another
> - we can't any more call pre and post processing.
> 
> The first point is a bit problematic, because we have to lock all the 
> list. Performance can suffer, but not that much, considering that we 
> don't have so much chain modifications (hopefully)
> The real issue is more serious. One improvement would be to have such
> a loop :
> 
> for (Filter filter:listOfFilters ) {
>   filter.doPreXXX()
>   filter.processEventXXX();
>   filter.doPostXXX()
> }
> 
> but this is far from being enough.
> 
> How do we handle the list modification ? As it's a list, we have all
> the LIST API to do that : add, add(index), etc. Easy, as we can
> access this list from the session object.
> 
> 
> 2.2) Using a linked list
> ------------------------
> 
> Instead of using a list, we can also link the filters. Passing from
> one filter to the other will then be just a matter of doing :
> 
> F1.processEventXXX()
>   pre processing
>   nextFilter.processEventXXX()
>   post processing
> 
> The nextFilter is a field pointing to the next filter in the chain.
> 
> This solution has the big advantage of being very simple to
> implement, allows pre and post processing, whatever it can be, and
> fast and easy to debug.
> 
> The main issue is that it's difficult to protect this chain from 
> concurrent modification, unless you synchronize the nextFilter field :
> F1.processEventXXX()
>   pre processing
>   synchronized( nextFilter ) {
>     nextFilter.processEventXXX()
>   }
>   post processing
> 
> This may be acceptable, though. The cost of checking if this 
> synchronization might be not null, as we can see that if the chain 
> contains many filters,
> we will have as many synchronized sections as we have filter, plus
> the time during which the section is synchronized is the global
> execution time.
> 
> Modifying the list is quite simple. As each filter has a link to the 
> next filter, it's just a matter of manipulating this list to add or 
> remove a filter. Obviously, adding or removing a filter back in the 
> chain is useless.
> 
> 2.3 Using a double level structure
> ----------------------------------
> The idea is to separate the filters from the chain. We will store the 
> chain into a list, and each element fo this list is a filter. This
> list is handled by the session.
> Here is the code :
> 
> // First invocation
> processEvent(XXX) {
>   session.getFilter(0).processEventXXX( session, 0 );
> }
> 
> // Nth invocation
> Fx.processEventXXX(session, pos) {
>   pre processing
>   pos++
>   session.getFilter(pos).processEventXXX(session, pos)
>   post processing
> }
> 
> // And of course, the TailFilter won't call the next filter :)
> 
> How do we modifiy the list ?
> 
> Fx.processEventXXX(session, pos) {
>   pre processing
>   pos++
>   // Add a new filter to the chain
>   session.addFiler(pos, filter)
>   session.getFilter(pos).processEventXXX(session, pos)
>   post processing
> }
> 
> The session.addFilter() method will just update the list, and then
> the next call will be done. If we use a CopyOnWriteArrayList, we are
> thread safe.
> 
> So far, using this method, we have all the advantages and none of the 
> inconvenients we have seen in the other methods.
> 
> 3) Choice
> ---------
> 
> If we cant to keep all the existing functionalites MINA has, solution 
> 2.3 is obviously the way to go. Well implemented, it will also be
> fast and easy to debug, and, last, not least, it's close to what we
> curently have, but with less code and a better interface.
> 

Re: [Chain refactoring] Some fedback and ideas no--signature

Posted by Emmanuel Lecharny <el...@gmail.com>.
Steve Ulrich wrote:
>> 3) Choice
>> ---------
>>
>> If we cant to keep all the existing functionalites MINA has, solution
>> 2.3 is obviously the way to go. Well implemented, it will also be fast
>> and easy to debug, and, last, not least, it's close to what we curently
>> have, but with less code and a better interface.
>>     
>
> I think a combination of the three would be good:
>
> An Iterator like object is passed to the Filter:
>
> processEventXXX(message, chainIterator){
>   //preprocessing
>   chainIterator.next().processEventXXX()
>   //postprocessing
> }
>
> This way you don't need to deal witch an index.
>   
That's an excellent idea ! I will have to create a third branch to do 
that... I must admit that having to increment the index when switching 
from one filter to another is, well, ugly ?
> If the data the Iterator is based of is immutable, 
This is not the case. But the iterator may use internally a list which 
will be protected. Using a CoWAL, this is just fine.
> synchronization isn't needed. Just replace the data and the next created iterator will use it.
> Pro:
>   No need for ugly and unsage indices
>   No need to decide wich way to iterate the list, bottom-up or top-down are handled by different iterators
>   Synchronisation isn't needed
>   Pre and post processing
>   easy usage, easy implementation
> Con:
>   overhead for iterator creation ?
>
> regards
>   

Thanks Steve !

-- 
--
cordialement, regards,
Emmanuel Lécharny
www.iktek.com
directory.apache.org



RE: [Chain refactoring] Some fedback and ideas no--signature

Posted by Steve Ulrich <st...@proemion.com>.
> Emmanuel Lecharny [mailto:elecharny@gmail.com] wrote:
>
> Hi guys,

Hi!

> here are some thoughts and ideas I have had those last few days,
> thoughts that I have experimented in branches. It's not finished yet,
> but I think I have something likely to work well and be easier to use
> than the current implementation.
>
> So here are the gathered thoughts, please feel free to give me feedback
> :
>
>
> Refactoring the current chain : possible implementations
 [snip]
> ---------------------------------------------------------
>
> 2) Proposed solutions
> ---------------------
>
[snip]

In my opinion, the 2.1 and 2.2 solution is nearly the same, just the way you iterate the filter is different:
2.1: Iterations outside of the filter (Con: no pre/post processing)
2.3: Index based iteration inside the filter (Con: looks ugly to me ;-), indices aren't safe if you use ExecutorFilter)

2.2 is somewhat different: You're using a simple kind of LinkedList and pass the current filter the entry of the "next" element

>
> 3) Choice
> ---------
>
> If we cant to keep all the existing functionalites MINA has, solution
> 2.3 is obviously the way to go. Well implemented, it will also be fast
> and easy to debug, and, last, not least, it's close to what we curently
> have, but with less code and a better interface.

I think a combination of the three would be good:

An Iterator like object is passed to the Filter:

processEventXXX(message, chainIterator){
  //preprocessing
  chainIterator.next().processEventXXX()
  //postprocessing
}

This way you don't need to deal witch an index.
If the data the Iterator is based of is immutable, synchronization isn't needed. Just replace the data and the next created iterator will use it.
Pro:
  No need for ugly and unsage indices
  No need to decide wich way to iterate the list, bottom-up or top-down are handled by different iterators
  Synchronisation isn't needed
  Pre and post processing
  easy usage, easy implementation
Con:
  overhead for iterator creation ?

regards

Steve