Pipeline-Filter Pattern variant tail Loop

Source: Internet
Author: User

As a data processing mode (see [POSA] Volume 4), pipeline-filter divides application tasks into several self-complete data processing steps and connects them to a data pipeline. This article introduces a less common pipeline-filter variant-the pipeline-filter of the tail loop. Of course, this is only available in specific scenarios.

First, let's look at a common pipeline-Filter pattern:


The method is simple and clear, that is, to split the data processing steps. Then they are chained together with a unified programming interface and a recursive method.

This mode is also used to split the message processing steps when a message broker is recently written to process message communication. This mode is used smoothly when sending messages, so it is natural to adopt this mode when receiving messages. Let's take a look at the whole pipeline when a message is sent as follows:


Description of terms: Handler mentioned in this article can be compared to filter. The Handler-chain mentioned can be compared to pipeline, but it is called differently.

If you don't think much about it, the pipeline that receives the message should be similar to sending the message, but the produce-handler is changed to consume-handler. However, in code implementation, the implementation of rabbitmq-Java-client makes the use of this mode somewhat blocked. Under normal circumstances, we understand that the processing of a message or the processing of a message set passes through a pipeline, however, the official Java-client implements socket-blocking to receive messages and waits for the messages to be pushed to the client. Because of this implementation method of the official client, it is best to migrate socket-blocking to an independent eventloop-thread for external encapsulation. After obtaining the message, the client implements its own processing logic in the event, that is, it is an Asynchronous Method of receiving messages, think about this push method.

It can be seen that the pipeline when receiving a message is very different from that when sending a message. For message receiving, the filter is divided into two parts. The multiple filters in the first part only execute once (mainly to process some pre-tasks, such as permission check, parameter verification); multiple filters in the second part must be repeatedly executed on another eventloop-thread (because these filters involve processing received messages ). Therefore, when receiving a message:


The two handler shown below are cyclically executed on eventloop-thread.

Apparently, the pipeline-filter used for produce cannot cope with this change (neither cross-thread nor loop on several of them ). At this time, it is impossible to independently implement a new set of pipeline-filter for consume (because on the infrastructure of pipeline-filter, we have already added produce, consume, request, response, publish, subscribe and so on are abstracted as message transmission (carry )). Therefore, we can only find a way to meet all the message transmission methods on the same operating mechanism.

Our practice is to implement a "transitional handler" (here the handler is a filter, but it is just named differently) and implement the handle method. The handler is used to transition the subsequent logic to an independent eventloop-thread and start eventloop-thread (transfer the context passed to the current handler and the chain object to the event processing thread ), all subsequent Handler are executed cyclically in this thread.

The implementation code segment is as follows:

public void handle(@NotNull MessageContext context,                       @NotNull IHandlerChain chain) {        if (!context.isSync()) {            ReceiveEventLoop eventLoop = new ReceiveEventLoop();            eventLoop.setChain(chain);            eventLoop.setContext(context);            eventLoop.setChannelDestroyer(context.getDestroyer());            eventLoop.setCurrentConsumer((QueueingConsumer) context.getOtherParams().get("consumer"));            context.setReceiveEventLoop(eventLoop);            //repeat current handler            ((MessageCarryHandlerChain) chain).setEnableRepeatBeforeNextHandler(true);            eventLoop.startEventLoop();        } else {            chain.handle(context);        }    }

As shown above, receiveeventloop is an independent eventloop-thread. After it is started, the call chain (recursive call of the handle method) initiated by pipeline has ended. Therefore, the main thread will continue to execute from the trigger point of the call.

The subsequent filters run independently on the eventloop thread:

public void run() {    try {        while (true) {            QueueingConsumer.Delivery delivery = this.currentConsumer.nextDelivery();            AMQP.BasicProperties properties = delivery.getProperties();            byte[] msgBody = delivery.getBody();            context.getChannel().basicAck(delivery.getEnvelope().getDeliveryTag(), false);            //.....            this.context.setConsumedMsg(msg);            this.chain.handle(this.context);        }    } catch (InterruptedException e) {        logger.info("[run] close the consumer‘s message handler!");    } catch (IOException e) {        logger.error("[run] occurs a IOException : " + e.getMessage());        this.shutdown();    } catch (ConsumerCancelledException e) {        logger.info("[run] the consumer has been canceled ");        this.shutdown();    } catch (Exception e) {        logger.error("[run] occurs a Exception : " + e.getMessage());        this.shutdown();    }    logger.info("******** thread id " + this.getThreadID() + " quit from message receiver ********");}

There is a problem: we must get control of eventloop-thread (the external can close this eventloop at any time), and the key code to get control is in the previous Code:

context.setReceiveEventLoop(eventLoop);

We wrap the current eventloop instance into a thread-type attribute, and open the corresponding On/Off Methods for it, throwing its control to the external:

public void startEventLoop() {      this.currentThread.start();}public void shutdown() {      this.channelDestroyer.destroy(context.getChannel());      this.currentThread.interrupt();}

Then, when the main thread initiates a method to receive messages, an instance of the ireceivercloser interface is returned, and shutdown is called in the method close to close the interface:

//launch pipeline        carry(ctx);        return new IReceiverCloser() {            @Override            public void close() {                synchronized (this) {                    if (ctx.getReceiveEventLoop().isAlive()) {                        ctx.getReceiveEventLoop().shutdown();                    }                }            }        };

Another problem is: how does Handler-chain know that the process of transferring from a handler to an eventloop thread will start to run cyclically? How is it implemented? It comes from the following code in the first code:

((MessageCarryHandlerChain) chain).setEnableRepeatBeforeNextHandler(true);
This Code sets a "loop execution" sign before entering the next handler. Let's take a look at how we transform handlerchain to achieve this purpose. In the implementation of messagecarryhandlerchain (which implements the ihandlerchain Interface), there are four variables:

    private List<AbstractHandler> handlerChain;    private int     pos          = 0;    private boolean enableRepeat = false;    private int     repeatPos    = -1;

  • Handlerchain: parses and stores each handler in sequence
  • POs: used to record the handler currently executed
  • Enablerepeat: used to identify whether repeated handler execution is enabled
  • Repeatpos: used to record the starting position of the handler for repeated execution
When the enablerepeat attribute is enabled, the current location status is recorded:
public void setEnableRepeatBeforeNextHandler(boolean enableRepeat) {        this.enableRepeat = enableRepeat;        if (this.enableRepeat) {            this.repeatPos = this.pos;        } else {            this.repeatPos = Integer.MIN_VALUE;        }    }

The handle implementation of messagecarrychain is also the core of handler string connection:
public void handle(MessageContext context) {        if (this.repeatPos != Integer.MIN_VALUE) {            if (this.pos < handlerChain.size()) {                AbstractHandler currentHandler = handlerChain.get(pos++);                currentHandler.handle(context, this);            } else if (this.enableRepeat) {                this.pos = this.repeatPos;            }        }    }
When processing the first incoming message, it corresponds to the last sentence in the while (true) above:
this.chain.handle(this.context);

Call the handle method of messagecarrychain and execute:
if (this.pos < handlerChain.size()) {}

Determine the branch, where the handle method of the next handler will be triggered, and the else logic will be executed until the judgment condition is not true, set the handler location of the previously saved start loop to the new handler location. Therefore, when an event loop receives a new message in the while (true) above, it will execute the above if judgment branch again (because when receiving the previous message, the POS has been reset, so the if judgment condition is re-established) and starts with the handler of the loop position until the last one in the handlerchain is reached, reset the POS at the current position to the repeatpos storage location (note that the repeatpos will not be changed after the first setting. On the surface, an ending loop is formed. Why is it on the surface. Because in the expression, I intentionally "Ignore" such a reality-the pipeline-filter mode is basically implemented using recursion, and Recursion has a Restore Point, it is used to "Protect the site" and then "restore the site ". Therefore, in the handle code segment in messagecarrychain above:
currentHandler.handle(context, this);

Each handler to be executed is a restore point. When the first round of handler execution is completed (this. pos = This. repeatpos;) will be rolled back at the Restore Point layer (the remaining code in the method after the Restore point is executed ). Therefore, when receiving the second message, it actually triggers a new round of Handler-chain execution process, just because POS was previously set to the starting position of the cycle in the chain, instead of starting from 0. But for handler of the last loop, the nature of their recursive calls has not changed, so it just seems to be "loop" at the end.
Note: If you review the essence of the pipeline-filter mode, they are used to process data. Here, both sending and receiving messages process logic other than messages. If you only process messages here, you do not need to use cross-thread or tail loop. I just used this mode to split, combine, and reuse all aspects of message communication, so that this problem cannot be avoided. If I return to the pure pipeline-filter mode, this is not required. If I have not made it clear, check the code: messagebus-consume.

Pipeline-Filter Pattern variant tail Loop

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.