package io.cattle.platform.eventing.impl;
import io.cattle.platform.eventing.EventListener;
import io.cattle.platform.eventing.PoolSpecificListener;
import io.cattle.platform.eventing.annotation.EventHandler;
import io.cattle.platform.eventing.model.Event;
import io.cattle.platform.eventing.model.EventVO;
import io.cattle.platform.lock.exception.FailedToAcquireLockException;
import io.cattle.platform.metrics.util.MetricsUtil;
import io.cattle.platform.util.concurrent.NamedExecutorService;
import io.cattle.platform.util.type.InitializationTask;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.apache.cloudstack.managed.context.NoExceptionRunnable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import com.codahale.metrics.Counter;
public abstract class AbstractThreadPoolingEventService extends AbstractEventService implements InitializationTask {
private static final Logger log = LoggerFactory.getLogger(AbstractThreadPoolingEventService.class);
@Inject @Named("CoreExecutorService")
ExecutorService runExecutorService;
String threadCountSetting = "eventing.pool.%s.count";
String defaultPoolName = EventHandler.DEFAULT_POOL_KEY;
@Inject
List<NamedExecutorService> namedExecutorServiceList;
Map<String, ExecutorService> executorServices;
Map<String, Counter> dropped = new ConcurrentHashMap<String, Counter>();
protected void onEvent(String listenerKey, String eventName, byte[] bytes) {
try {
EventVO<?> event = jsonMapper.readValue(bytes, EventVO.class);
if (eventName != null) {
event.setName(eventName);
}
event.setListenerKey(listenerKey);
onEvent(event);
} catch (IOException e) {
try {
log.warn("Failed to unmarshall event [{}]", new String(bytes, "UTF-8"), e);
} catch (UnsupportedEncodingException e1) {
log.warn("Failed to unmarshall event [*bytes*]", e);
}
}
}
protected void onEvent(String listenerKey, String eventName, String eventString) {
getEventLogIn().debug(eventString);
try {
EventVO<?> event = jsonMapper.readValue(eventString, EventVO.class);
if (eventName != null) {
event.setName(eventName);
}
event.setListenerKey(listenerKey);
onEvent(event);
} catch (IOException e) {
log.warn("Failed to unmarshall event [{}]", eventString, e);
}
}
protected void onEvent(final Event event) {
new ManagedContextRunnable() {
@Override
protected void runInContext() {
onEventInContext(event);
}
}.run();
}
protected void onEventInContext(Event event) {
String name = event.getName();
if (name == null) {
log.debug("null event name on event [{}]", event);
return;
}
List<EventListener> listeners = getEventListeners(event);
if (listeners == null || listeners.size() == 0) {
log.debug("No listeners found for [{}]", event.getName());
return;
}
for (EventListener listener : listeners) {
Executor executor = getExecutor(event, listener);
Runnable runnable = getRunnable(event, listener);
try {
executor.execute(runnable);
} catch (RejectedExecutionException e) {
dropped(event);
log.debug("Too busy to process [{}]", event);
}
}
}
protected void dropped(Event event) {
String metricName = metricName(event, "dropped");
if (metricName == null) {
return;
}
Counter counter = dropped.get(metricName);
if (counter == null) {
counter = MetricsUtil.getRegistry().counter(metricName);
dropped.put(metricName, counter);
}
counter.inc();
}
protected Runnable getRunnable(final Event event, final EventListener listener) {
return new NoExceptionRunnable() {
@Override
protected void doRun() throws Exception {
try {
Map<String, Object> context = event.getContext();
if (context != null) {
MDC.setContextMap(context);
}
listener.onEvent(event);
} catch (FailedToAcquireLockException e) {
log.trace("Failed to acquire lock on event [{}], this is probably normal", event, e);
}
}
};
}
protected Executor getExecutor(Event event, EventListener listener) {
Executor executor = null;
if (listener instanceof PoolSpecificListener) {
executor = executorServices.get(((PoolSpecificListener) listener).getPoolKey());
}
String eventName = event.getName();
if (executor == null && eventName != null) {
if (eventName.startsWith(Event.REPLY_PREFIX)) {
executor = executorServices.get("reply");
} else if (eventName.endsWith(Event.REPLY_SUFFIX)) {
executor = executorServices.get("reply");
}
}
if (executor == null) {
executor = getDefaultExecutor();
}
return executor;
}
protected Executor getDefaultExecutor() {
return runExecutorService;
}
@Override
public void start() {
executorServices = new HashMap<String, ExecutorService>();
for (NamedExecutorService named : namedExecutorServiceList) {
executorServices.put(named.getName(), named.getExecutorService());
}
}
public String getThreadCountSetting() {
return threadCountSetting;
}
public void setThreadCountSetting(String threadCountSetting) {
this.threadCountSetting = threadCountSetting;
}
public String getDefaultPoolName() {
return defaultPoolName;
}
public void setDefaultPoolName(String defaultPoolName) {
this.defaultPoolName = defaultPoolName;
}
}