All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.likavn.eventbus.provider.redis.support.XStreamPollTask Maven / Gradle / Ivy

There is a newer version: 2.5.0-RC3
Show newest version
/*
 * Copyright 2018-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.likavn.eventbus.provider.redis.support;

import com.github.likavn.eventbus.core.utils.GroupedThreadPoolExecutor;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.data.redis.connection.stream.Consumer;
import org.springframework.data.redis.connection.stream.ReadOffset;
import org.springframework.data.redis.connection.stream.Record;
import org.springframework.data.redis.connection.stream.StreamOffset;
import org.springframework.data.redis.stream.StreamListener;
import org.springframework.data.redis.stream.StreamMessageListenerContainer.ConsumerStreamReadRequest;
import org.springframework.data.redis.stream.StreamMessageListenerContainer.StreamReadRequest;
import org.springframework.data.redis.stream.Task;
import org.springframework.util.ErrorHandler;

import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Predicate;

/**
 * {@link Task} that invokes a {@link BiFunction read function} to poll on a Redis Stream.
 *
 * @author Mark Paluch
 * @see 2.2
 */
@SuppressWarnings("all")
class XStreamPollTask> implements Task {
    private final GroupedThreadPoolExecutor deliverExecutor;
    private final StreamReadRequest request;
    private final StreamListener listener;
    private final ErrorHandler errorHandler;
    private final Predicate cancelSubscriptionOnError;
    private final BiFunction> readFunction;

    private final XStreamPollTask.PollState pollState;

    private GroupedThreadPoolExecutor.GTask task;
    private volatile boolean isInEventLoop = false;

    private long lastExecuteTime = 0;

    XStreamPollTask(StreamReadRequest streamRequest, StreamListener listener, ErrorHandler errorHandler,
                    BiFunction> readFunction, GroupedThreadPoolExecutor deliverExecutor, RedisListener redisListener) {
        this.deliverExecutor = deliverExecutor;
        this.request = streamRequest;
        this.listener = listener;
        this.errorHandler = Optional.ofNullable(streamRequest.getErrorHandler()).orElse(errorHandler);
        this.cancelSubscriptionOnError = streamRequest.getCancelSubscriptionOnError();
        this.readFunction = readFunction;
        this.pollState = createPollState(streamRequest);

        this.task = new GroupedThreadPoolExecutor.GTask();
        String groupName = null == redisListener.getTrigger() ? redisListener.getServiceId() : redisListener.getTrigger().getDeliverId();
        this.task.setName(groupName);
        this.task.setConcurrency(redisListener.getConcurrency());
        this.task.setData(this);
    }

    private static XStreamPollTask.PollState createPollState(StreamReadRequest streamRequest) {

        StreamOffset streamOffset = streamRequest.getStreamOffset();

        if (streamRequest instanceof ConsumerStreamReadRequest) {
            return XStreamPollTask.PollState.consumer(((ConsumerStreamReadRequest) streamRequest).getConsumer(), streamOffset.getOffset());
        }

        return XStreamPollTask.PollState.standalone(streamOffset.getOffset());
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.redis.stream.Cancelable#cancel()
     */
    @Override
    public void cancel() throws DataAccessResourceFailureException {
        this.pollState.cancel();
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.redis.stream.Task#getState()
     */
    @Override
    public State getState() {
        return pollState.getState();
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.redis.stream.Task#awaitStart(java.time.Duration)
     */
    @Override
    public boolean awaitStart(Duration timeout) throws InterruptedException {
        return pollState.awaitStart(timeout.toNanos(), TimeUnit.NANOSECONDS);
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.scheduling.SchedulingAwareRunnable#isLongLived()
     */
    @Override
    public boolean isLongLived() {
        return true;
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Runnable#run()
     */
    @Override
    public void run() {

        pollState.starting();

        try {

            isInEventLoop = true;
            pollState.running();
            doLoop(request.getStreamOffset().getKey());
        } finally {
            isInEventLoop = false;
        }
    }

    public boolean pull() {
        pollState.starting();

        try {

            isInEventLoop = true;
            pollState.running();
            return doLoop(request.getStreamOffset().getKey());
        } finally {
            isInEventLoop = false;
        }
    }

    private boolean doLoop(K key) {

        //  do {
        try {

            // allow interruption
            Thread.sleep(0);
        //    long l = System.currentTimeMillis();
            List read = readFunction.apply(key, ReadOffset.lastConsumed());
          //  System.out.println("拉取数据操作,耗时:" + (System.currentTimeMillis() - l));
            if (read.isEmpty()) {
                return true;
            }
            this.deliverExecutor.execute(task.target(() -> {
                for (V message : read) {

                    listener.onMessage(message);
                    pollState.updateReadOffset(message.getId().getValue());
                }
            }));
        } catch (InterruptedException e) {

            cancel();
            Thread.currentThread().interrupt();
        } catch (RuntimeException e) {

            if (cancelSubscriptionOnError.test(e)) {
                cancel();
            }

            errorHandler.handleError(e);
        }
        cancel();
        return false;
        //    } while (pollState.isSubscriptionActive());
    }

    public void setLastExecuteTime(long lastExecuteTime) {
        this.lastExecuteTime = lastExecuteTime;
    }

    public long getLastExecuteTime() {
        return lastExecuteTime;
    }

    @Override
    public boolean isActive() {
        return State.RUNNING.equals(getState()) || isInEventLoop;
    }

    /**
     * Object representing the current polling state for a particular stream subscription.
     */
    static class PollState {

        private final XReadOffsetStrategy readOffsetStrategy;
        private final Optional consumer;
        private volatile ReadOffset currentOffset;
        private volatile State state = State.CREATED;
        private volatile CountDownLatch awaitStart = new CountDownLatch(1);

        private PollState(Optional consumer, XReadOffsetStrategy readOffsetStrategy, ReadOffset currentOffset) {

            this.readOffsetStrategy = readOffsetStrategy;
            this.currentOffset = currentOffset;
            this.consumer = consumer;
        }

        /**
         * Create a new state object for standalone-read.
         *
         * @param offset the {@link ReadOffset} to use.
         * @return new instance of {@link XStreamPollTask.PollState}.
         */
        static XStreamPollTask.PollState standalone(ReadOffset offset) {

            XReadOffsetStrategy strategy = XReadOffsetStrategy.getStrategy(offset);
            return new XStreamPollTask.PollState(Optional.empty(), strategy, strategy.getFirst(offset, Optional.empty()));
        }

        /**
         * Create a new state object for consumergroup-read.
         *
         * @param consumer the {@link Consumer} to use.
         * @param offset   the {@link ReadOffset} to apply.
         * @return new instance of {@link XStreamPollTask.PollState}.
         */
        static XStreamPollTask.PollState consumer(Consumer consumer, ReadOffset offset) {

            XReadOffsetStrategy strategy = XReadOffsetStrategy.getStrategy(offset);
            Optional optionalConsumer = Optional.of(consumer);
            return new XStreamPollTask.PollState(optionalConsumer, strategy, strategy.getFirst(offset, optionalConsumer));
        }

        boolean awaitStart(long timeout, TimeUnit unit) throws InterruptedException {
            return awaitStart.await(timeout, unit);
        }

        public State getState() {
            return state;
        }

        /**
         * @return {@literal true} if the subscription is active.
         */
        boolean isSubscriptionActive() {
            return state == State.STARTING || state == State.RUNNING;
        }

        /**
         * Set the state to {@link State#STARTING}.
         */
        void starting() {
            state = State.STARTING;
        }

        /**
         * Switch the state to {@link State#RUNNING}.
         */
        void running() {

            state = State.RUNNING;

            CountDownLatch awaitStart = this.awaitStart;

            if (awaitStart.getCount() == 1) {
                awaitStart.countDown();
            }
        }

        /**
         * Set the state to {@link State#CANCELLED} and re-arm the
         * {@link #awaitStart(long, TimeUnit) await synchronizer}.
         */
        void cancel() {

            awaitStart = new CountDownLatch(1);
            state = State.CANCELLED;
        }

        /**
         * Advance the {@link ReadOffset}.
         */
        void updateReadOffset(String messageId) {
            currentOffset = readOffsetStrategy.getNext(getCurrentReadOffset(), consumer, messageId);
        }

        ReadOffset getCurrentReadOffset() {
            return currentOffset;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy