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

org.apache.sling.discovery.commons.providers.base.AsyncEventSender Maven / Gradle / Ivy

There is a newer version: 6.5.21
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.sling.discovery.commons.providers.base;

import java.util.LinkedList;
import java.util.List;

import org.apache.sling.discovery.TopologyEvent;
import org.apache.sling.discovery.TopologyEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** 
 * SLING-4755 : background runnable that takes care of asynchronously sending events.
 * 

* API is: enqueue() puts a listener-event tuple onto the internal Q, which * is processed in a loop in run that does so (uninterruptably, even catching * Throwables to be 'very safe', but sleeps 5sec if an Error happens) until * flushThenStop() is called - which puts the sender in a state where any pending * events are still sent (flush) but then stops automatically. The argument of * using flush before stop is that the event was originally meant to be sent * before the bundle was stopped - thus just because the bundle is stopped * doesn't undo the event and it still has to be sent. That obviously can * mean that listeners can receive a topology event after deactivate. But I * guess that was already the case before the change to become asynchronous. */ final class AsyncEventSender implements Runnable { static final Logger logger = LoggerFactory.getLogger(AsyncEventSender.class); /** stopped is always false until flushThenStop is called **/ private boolean stopped = false; /** eventQ contains all AsyncEvent objects that have yet to be sent - in order to be sent **/ private final List eventQ = new LinkedList(); /** flag to track whether or not an event is currently being sent (but already taken off the Q **/ private boolean isSending = false; /** Enqueues a particular event for asynchronous sending to a particular listener **/ void enqueue(TopologyEventListener listener, TopologyEvent event) { final AsyncTopologyEvent asyncEvent = new AsyncTopologyEvent(listener, event); enqueue(asyncEvent); } /** Enqueues an AsyncEvent for later in-order execution **/ void enqueue(final AsyncEvent asyncEvent) { synchronized(eventQ) { eventQ.add(asyncEvent); if (logger.isDebugEnabled()) { logger.debug("enqueue: enqueued event {} for async sending (Q size: {})", asyncEvent, eventQ.size()); } eventQ.notifyAll(); } } /** * Stops the AsyncEventSender as soon as the queue is empty */ void flushThenStop() { synchronized(eventQ) { logger.info("AsyncEventSender.flushThenStop: flushing (size: {}) & stopping...", eventQ.size()); stopped = true; eventQ.notifyAll(); } } /** Main worker loop that dequeues from the eventQ and calls sendTopologyEvent with each **/ public void run() { logger.info("AsyncEventSender.run: started."); try{ while(true) { try{ final AsyncEvent asyncEvent; synchronized(eventQ) { isSending = false; while(!stopped && eventQ.isEmpty()) { try { eventQ.wait(); } catch (InterruptedException e) { // issue a log debug but otherwise continue logger.debug("AsyncEventSender.run: interrupted while waiting for async events"); } } if (stopped) { if (eventQ.isEmpty()) { // then we have flushed, so we can now finally stop logger.info("AsyncEventSender.run: flush finished. stopped."); return; } else { // otherwise the eventQ is not yet empty, so we are still in flush mode logger.info("AsyncEventSender.run: flushing another event. (pending {})", eventQ.size()); } } asyncEvent = eventQ.remove(0); if (logger.isDebugEnabled()) { logger.debug("AsyncEventSender.run: dequeued event {}, remaining: {}", asyncEvent, eventQ.size()); } isSending = asyncEvent!=null; } if (asyncEvent!=null) { asyncEvent.trigger(); } } catch(Throwable th) { // Even though we should never catch Error or RuntimeException // here's the thinking about doing it anyway: // * in case of a RuntimeException that would be less dramatic // and catching it is less of an issue - we rather want // the background thread to be able to continue than // having it finished just because of a RuntimeException // * catching an Error is of course not so nice. // however, should we really give up this thread even in // case of an Error? It could be an OOM or some other // nasty one, for sure. But even if. Chances are that // other parts of the system would also get that Error // if it is very dramatic. If not, then catching it // sounds feasible. // My two cents.. // the goal is to avoid quitting the AsyncEventSender thread logger.error("AsyncEventSender.run: Throwable occurred. Sleeping 5sec. Throwable: "+th, th); try { Thread.sleep(5000); } catch (InterruptedException e) { logger.warn("AsyncEventSender.run: interrupted while sleeping"); } } } } finally { logger.info("AsyncEventSender.run: quits (finally)."); } } /** for testing only: checks whether there are any events being queued or sent **/ boolean hasInFlightEvent() { synchronized(eventQ) { return isSending || !eventQ.isEmpty(); } } public int getInFlightEventCnt() { synchronized(eventQ) { int cnt = eventQ.size(); if (isSending) { cnt++; } return cnt; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy