Thread Interrupt for Marble Principle

Keywords: Java jvm xml github

This chapter relies on Marble Use, so make sure you have a good understanding of Marble before reading it.
The interrupt feature is supported starting with Marble-Agent 2.0.5.

Thread interrupt use

  1. Introducing the marble-agent jar package xml <dependency> <groupId>com.github.jeff-dong</groupId> <artifactId>marble-agent</artifactId> <version>Latest version</version> </dependency>
  2. Add an interrupt flag where appropriate for JOB execution code, as shown below

    @Component("job1")
    public class Job1 extends MarbleJob {
    private ClogWrapper logger = ClogWrapperFactory.getClogWrapper(Job1.class);
    
    @Override
    public void execute(String param) throws Exception {
        logger.info("JOB1 Start execution ...");
        int i = 0;
        while (true) {
            i++;
            //1. Use interrupt status code to judge
            if (Thread.interrupted()) {
                logger.info("JOB1-[{}]-[{}]Interrupted", param, Thread.currentThread().getName());
                return;
            }
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                //2. End of return after capturing terminal exception
                return;
            }
            logger.info("JOB1-[{}]-[{}]-{}-------", param, Thread.currentThread().getName(), i);
        }
    }
    }
    
  3. Marble OFFLINE for thread interrupt

3.1 Manual dispatch thread interrupt

3.2 Select the server to interrupt for a terminal attempt

3.3 View interrupt log (synchronous JOB)

Interrupt Implementation and Principle

Java thread interrupt

Java's thread interrupt mechanism is a collaboration mechanism. Thread interrupts do not immediately stop thread execution; instead, they may never respond.
java's thread interrupt model simply modifies the thread's interrupt flag for interrupt notification, with no additional action, so whether or not the thread eventually interrupts depends on the thread's execution logic.Therefore, if you want the thread to break as it sees it, you need to "bury" the breaks in your code beforehand.

It may be thought that Thread's stop method was interrupted and has been discarded because of its potential unpredictable consequences

Marble for thread interrupt implementation

Needs Collection
  1. Thread interrupt in JOB dimension;
  2. Real-time response as much as possible;
  3. There are multiple machines in the cluster to support thread interruption in a specified machine.
  4. Allow multiple interruption attempts;
  5. Interrupt requests cannot depend on the current JOB state.JOBs that may have stopped dispatching also need to manually interrupt threads in execution;
  6. Transparent and extend interrupts for different JOB s (providing "post processing" extensions for user interrupts);
Requirement Analysis and Implementation

[Thread interrupt in JOB dimension]

Marble's JOB flag is composed of schedulerName-appId-jobName. At present, each JOB schedule time and frequency are personalized, and the current schedule is destroyed when it is finished.However, any thread interruption that occurs during execution requires:
1.1 The running thread that stores the JOB is ready to interrupt at any time;
1.2 Make a trade-off between the number/time and performance of JOB s cached, not too many or too few;
1.3 Develop a discard policy when the cache is full to prevent new threads from ever interrupting when the cache is full.
1.4 Synchronize JOB with asynchronous JOB transparency (no difference can be felt);

Realization:
Marble's thread pool defines a thread cache that supports concurrent MAP s for the JOB dimension, in addition to specifying the number of threads cached under each JOB.The following:

public class ThreadPool {
    ...
    private Multimap<String, Object> threadMultimap = Multimaps.synchronizedMultimap(HashMultimap.<String, Object>create());
    //Maximum capacity of a single key for multimap
    private static final int THREADMULTIMAP_SIZE = 50;
    ...
}

Marble-Agent places a MAP cache when synchronous/asynchronous JOB s generate new thread objects, and if the cache (50) is full, uses the following strategy:
1. Attempt to clean up inactive threads in the current map;
2. Try to clean up the completed threads in the current map (synchronous threads are valid);
3. If space has not been cleared, remove the longest thread;

public ThreadPool multimapPut(String key, Object value) {
        if (StringUtils.isNotBlank(key)) {
            Collection collection = threadMultimap.get(key);
            if (collection != null && collection.size() >= THREADMULTIMAP_SIZE) {
                //Longest replacement
                Iterator<Object> it = collection.iterator();
                //Inactive Thread Cleanup First
                while (it.hasNext()) {
                    Object tempObj = it.next();
                    if(tempObj instanceof MarbleThread){
                        MarbleThread mt = (MarbleThread)tempObj;
                        //Inactive Delete
                        if(!mt.isThreadAlive()){
                            it.remove();
                        }
                    }else if(tempObj instanceof MarbleThreadFeature){
                        MarbleThreadFeature mf = (MarbleThreadFeature) tempObj;
                        //Completed thread deletion
                        if(mf.isDone()){
                            it.remove();
                        }
                    }
                }
                //Still > Maximum, delete oldest unused
                if(collection.size() >= THREADMULTIMAP_SIZE){
                    while (it.hasNext()) {
                        it.next();
                        it.remove();
                        break;
                    }
                }
                threadMultimap.put(key, value);
                return this;
            }
        }
        threadMultimap.put(key, value);
        return this;
    }

Additionally, in order to allow thread interrupts when the JVM closes, a JVM hook is added for interrupt call processing, including thread pool destruction.
In addition, there is a minor problem, since the thread pool uses bounded blocked queues, in which case possible threads exist in the blocked queue when the thread is interrupted, and simple interrupts are invalid. In such cases, it is important to first determine if there are any threads in the blocked queue to interrupt, and then remove the queue if there are any.

[Real-time response as much as possible]
By burying the user in specific thread logic only, Marble has no other means at the framework level than timely delivery of the user's interrupt request.

[There are multiple machines in the cluster to support thread interrupts in a specified machine]
The Marble OFFLINE interrupt page supports machine selection, after which Marble will send interrupt RPC s to the machine.

[Allow multiple interruption attempts]
OFFLINE does not limit the number of interrupts and currently supports multiple interrupt request sending.

[Interrupt request cannot depend on JOB current state]
Given the user's interrupt request for a historical thread, Marble does not bind the interrupt operation to the JOB state, and any JOB can make a terminal attempt.

[Transparently extend interrupts for different JOB s]
Marble currently supports both synchronous and asynchronous JOBs, and the interrupt handling of the two types of JOBs is inconsistent. For example, the interrupt of synchronous job is implemented through cancel of FeatureTask, and the asynchronous JOB is implemented through interrupt of Thread. In addition, Marble wants to provide a more unified "post-processing" operation to the user after the thread is interrupted, for example, the user may need to perform a unified "post-processing" operation after the thread isMake some subsequent log records, etc.

To achieve consistent, transparent code-level, and friendly "post-processing" encapsulation, Marble uses the proxy mode, adding a layer of "proxy classes" on Thread and FeatureTask to allow the proxy to perform specific interrupt operations.
Synchronization JOB proxy class:


/**
 * @author <a href="dongjianxing@aliyun.com">jeff</a>
 * @version 2017/4/19 16:31
 */
public class MarbleThreadFeature<V> implements RunnableFuture<V> {

    private ClogWrapper logger = ClogWrapperFactory.getClogWrapper(MarbleThreadFeature.class);
    private MarbleJob marbleJob;
    private String param;
    private FutureTask<Result> futureTask;


    public MarbleThreadFeature(final MarbleJob marbleJob, final String param) {
        super();
        this.marbleJob = marbleJob;
        this.param = param;
        futureTask = new FutureTask<>(new Callable<Result>() {
            @Override
            public Result call() throws Exception {
                return marbleJob.executeSync(param);
            }
        });
    }


    @Override
    public void run() {
        futureTask.run();
    }

    @Override
    public boolean cancel(boolean mayInterruptIfRunning) {
        return futureTask.cancel(mayInterruptIfRunning);
    }

    @Override
    public boolean isCancelled() {
        return futureTask.isCancelled();
    }

    @Override
    public boolean isDone() {
        return futureTask.isDone();
    }

    @Override
    public V get() throws InterruptedException, ExecutionException {
        return (V) futureTask.get();
    }

    @Override
    public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
        return (V) futureTask.get(timeout, unit);
    }

    public void stop(String operator) {
        if (futureTask != null && !futureTask.isCancelled()) {
            logger.info("Thread-feature[{}] is interrupted", futureTask.getClass().getName());
            futureTask.cancel(true);
        }else if(marbleJob != null){
            boolean removeResult = ((ThreadPoolExecutor) ThreadPool.getFixedInstance().getExecutorService()).getQueue().remove(marbleJob);
            logger.info("Hanging MarbleJob[{}] is removed from the queue success?{}", marbleJob.getClass().getSimpleName(),removeResult);
        }
        //Interrupt postprocessing
        if(marbleJob != null){
            marbleJob.afterInterruptTreatment();
        }
    }

}

Asynchronous JOB proxy class:


/**
 * @author <a href="dongjianxing@aliyun.com">jeff</a>
 * @version 2017/4/19 16:31
 */
public class MarbleThread implements Runnable {

    private ClogWrapper logger = ClogWrapperFactory.getClogWrapper(MarbleThread.class);
    private MarbleJob marbleJob;
    private String param;
    private Thread runThread;


    public MarbleThread(MarbleJob marbleJob, String param) {
        super();
        this.marbleJob = marbleJob;
        this.param = param;
    }

    @Override
    public void run() {
        runThread = Thread.currentThread();
        try {
            marbleJob.execute(param);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public boolean isThreadAlive() {
        return (runThread != null && runThread.isAlive());
    }

    public String getThreadName() {
        return runThread != null ? runThread.getName() : "";
    }

    public void stop() {
        //First try deleting in the blocked queue
        boolean removeResult = ((ThreadPoolExecutor) ThreadPool.getFixedInstance().getExecutorService()).getQueue().remove(this);
        logger.info("Hanging MarbleJob[{}] is removed from the queue success?{}", this.getClass().getSimpleName(), removeResult);
        if (runThread != null && !runThread.isInterrupted()) {
            logger.info("Thread[{}] is interrupted", runThread.getName());
            runThread.interrupt();
        }
        //Interrupt postprocessing
        if (marbleJob != null) {
            marbleJob.afterInterruptTreatment();
        }
    }
}

Posted by Ellen67 on Fri, 14 Jun 2019 13:23:19 -0700