content: /*
   * Copyright 1999-2018 Alibaba Group Holding Ltd.
   *
   * 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
   *
   *      http://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.alibaba.nacos.common.notify;
  
  import com.alibaba.nacos.common.notify.listener.Subscriber;
  import com.alibaba.nacos.common.utils.CollectionUtils;
  import com.alibaba.nacos.common.utils.ConcurrentHashSet;
  import com.alibaba.nacos.common.utils.ThreadUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.util.concurrent.ArrayBlockingQueue;
  import java.util.concurrent.BlockingQueue;
  import java.util.concurrent.Executor;
  import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
  
  import static com.alibaba.nacos.common.notify.NotifyCenter.ringBufferSize;
  
  /**
   * The default event publisher implementation.
   *
   * <p>Internally, use {@link ArrayBlockingQueue <Event/>} as a message staging queue.
   *
   * @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
   * @author zongtanghu
   */
  public class DefaultPublisher extends Thread implements EventPublisher {
      
      protected static final Logger LOGGER = LoggerFactory.getLogger(NotifyCenter.class);
      
      private volatile boolean initialized = false;
      
      private volatile boolean shutdown = false;
      
      private Class<? extends Event> eventType;
      
      protected final ConcurrentHashSet<Subscriber> subscribers = new ConcurrentHashSet<>();
      
      private int queueMaxSize = -1;
      
      private BlockingQueue<Event> queue;
      
      protected volatile Long lastEventSequence = -1L;
      
      private static final AtomicReferenceFieldUpdater<DefaultPublisher, Long> UPDATER = AtomicReferenceFieldUpdater
              .newUpdater(DefaultPublisher.class, Long.class, "lastEventSequence");
      
      @Override
      public void init(Class<? extends Event> type, int bufferSize) {
          setDaemon(true);
          setName("nacos.publisher-" + type.getName());
          this.eventType = type;
          this.queueMaxSize = bufferSize;
          if (this.queueMaxSize == -1) {
              this.queueMaxSize = ringBufferSize;
          }
          this.queue = new ArrayBlockingQueue<>(this.queueMaxSize);
          start();
      }
      
      public ConcurrentHashSet<Subscriber> getSubscribers() {
          return subscribers;
      }
      
      @Override
      public synchronized void start() {
          if (!initialized) {
              // start just called once
              super.start();
              initialized = true;
          }
      }
      
      @Override
      public long currentEventSize() {
          return queue.size();
      }
      
      @Override
      public void run() {
          openEventHandler();
      }
      
      void openEventHandler() {
          try {
              
              // This variable is defined to resolve the problem which message overstock in the queue.
              int waitTimes = 60;
              // To ensure that messages are not lost, enable EventHandler when
              // waiting for the first Subscriber to register
              while (!shutdown && !hasSubscriber() && waitTimes > 0) {
                  ThreadUtils.sleep(1000L);
                  waitTimes--;
              }
  
              while (!shutdown) {
                  final Event event = queue.take();
                  receiveEvent(event);
                  UPDATER.compareAndSet(this, lastEventSequence, Math.max(lastEventSequence, event.sequence()));
              }
          } catch (InterruptedException e) {
              // [issue #13752] ignore stack log
          } catch (Throwable ex) {
              LOGGER.error("Event listener exception : ", ex);
          }
      }
      
      private boolean hasSubscriber() {
          return CollectionUtils.isNotEmpty(subscribers);
      }
      
      @Override
      public void addSubscriber(Subscriber subscriber) {
          subscribers.add(subscriber);
      }
      
      @Override
      public void removeSubscriber(Subscriber subscriber) {
          subscribers.remove(subscriber);
      }
      
      @Override
      public boolean publish(Event event) {
          checkIsStart();
          boolean success = this.queue.offer(event);
          if (!success) {
              LOGGER.warn("Unable to plug in due to interruption, synchronize sending time, event : {}", event);
              receiveEvent(event);
              return true;
          }
          return true;
      }
      
      void checkIsStart() {
          if (!initialized) {
              throw new IllegalStateException("Publisher does not start");
          }
      }
      
      @Override
      public void shutdown() {
          this.shutdown = true;
          this.queue.clear();
          // Interrupt the thread to stop processing events: queue.take().
          this.interrupt();
      }
      
      public boolean isInitialized() {
          return initialized;
      }
      
      /**
       * Receive and notifySubscriber to process the event.
       *
       * @param event {@link Event}.
       */
      void receiveEvent(Event event) {
          final long currentEventSequence = event.sequence();
          
          if (!hasSubscriber()) {
              LOGGER.warn("[NotifyCenter] the {} is lost, because there is no subscriber.", event);
              return;
          }
          
          // Notification single event listener
          for (Subscriber subscriber : subscribers) {
              if (!subscriber.scopeMatches(event)) {
                  continue;
              }
              
              // Whether to ignore expiration events
              if (subscriber.ignoreExpireEvent() && lastEventSequence > currentEventSequence) {
                  LOGGER.debug("[NotifyCenter] the {} is unacceptable to this subscriber, because had expire",
                          event.getClass());
                  continue;
              }
              
              // Because unifying smartSubscriber and subscriber, so here need to think of compatibility.
              // Remove original judge part of codes.
              notifySubscriber(subscriber, event);
          }
      }
      
      @Override
      public void notifySubscriber(final Subscriber subscriber, final Event event) {
          
          LOGGER.debug("[NotifyCenter] the {} will received by {}", event, subscriber);
          
          final Runnable job = () -> subscriber.onEvent(event);
          final Executor executor = subscriber.executor();
          
          if (executor != null) {
              executor.execute(job);
          } else {
              try {
                  job.run();
              } catch (Throwable e) {
                  LOGGER.error("Event callback exception: ", e);
              }
          }
      }
  }
  