/** * Copyright (C) Zhang,Yuexiang (xfeep) * */ package nginx.clojure.util; import java.io.IOException; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import nginx.clojure.AppEventListenerManager.Listener; import nginx.clojure.AppEventListenerManager.PostedEvent; import nginx.clojure.MiniConstants; import nginx.clojure.NginxClojureRT; import nginx.clojure.logger.LoggerService; public class NginxPubSubTopic { protected String topic; protected long topicId; public static class PubSubListenerData<T> { public NginxPubSubListener<T> listener; public T data; public PubSubListenerData() { } public PubSubListenerData(NginxPubSubListener<T> listener, T data) { super(); this.listener = listener; this.data = data; } } protected static ConcurrentHashMap<Long, Set<PubSubListenerData>> topicSubs = new ConcurrentHashMap<Long, Set<PubSubListenerData>>(); public static final String PUBSUB_SHARED_MAP_NAME = "PubSubTopic"; public static final String PUBSUB_TOPIC_ID_COUNTER = "PUBSUB_TOPIC_ID_COUNTER"; public static final long PUBSUB_EVENT_ID_COUNTER = 0; private static final long PUBSUB_MAX_TOPIC_ID = Long.MAX_VALUE >> 10; protected static NginxSharedHashMap<Object, Object> sharedBox; static final LoggerService logger = NginxClojureRT.getLog(); private static void initSharedBox() { do { sharedBox = NginxSharedHashMap.build(PUBSUB_SHARED_MAP_NAME); if (sharedBox == null) { NginxClojureRT.getLog().error("can not find shared map '"+PUBSUB_SHARED_MAP_NAME+"' without which NginxPubSubTopic can't work!"); break; } sharedBox.putIntIfAbsent(PUBSUB_EVENT_ID_COUNTER, 1); NginxClojureRT.getAppEventListenerManager().addListener(new Listener() { @Override public void onEvent(PostedEvent event) throws IOException { if (event.tag == MiniConstants.POST_EVENT_TYPE_PUB) { long id = (Long) event.data; long rid = id | 0x0100000000L; String message = (String) sharedBox.get(id); long topicId = sharedBox.getLong(rid) >> 10; long counter = sharedBox.atomicAddLong(rid, -1); if (logger.isDebugEnabled()) { logger.debug("handle pub post event, id=%x, rid=%x, message=%s, topicId=%x, counter=%x,%x", id, rid, message, topicId, counter, counter & 0x3ff); } if ((counter & 0x3ff) == 1) { // the message has been post to all nginx worker processes // so we can remove it from shared map now. sharedBox.delete(rid); sharedBox.delete(id); } Set<PubSubListenerData> pds = topicSubs.get(topicId); if (pds == null) { NginxClojureRT.getLog().debug("no sub found about topic %d", topicId); }else { for (PubSubListenerData pd : pds) { try { pd.listener.onMessage(message, pd.data); }catch(Throwable e) { NginxClojureRT.getLog().warn("error on pubsub event, message=%s", e); } } } } } }); }while(false); } public NginxPubSubTopic(String topic) { if (sharedBox == null) { synchronized (topicSubs) { initSharedBox(); } } this.topic = topic; if (sharedBox == null) { throw new RuntimeException("can not find shared map '"+PUBSUB_SHARED_MAP_NAME+"' without which NginxPubSubTopic can't work!"); } Object topicIdObj = sharedBox.get(topic); if (topicIdObj == null) { if (sharedBox.putIfAbsent(PUBSUB_TOPIC_ID_COUNTER, 2L) == null) { topicId = 1L; }else { topicId = sharedBox.atomicAddLong(PUBSUB_TOPIC_ID_COUNTER, 1); if (topicId > PUBSUB_MAX_TOPIC_ID) { throw new RuntimeException("too many topics! nginx-clojure can not support > " + PUBSUB_MAX_TOPIC_ID + " topics"); } } topicIdObj = sharedBox.putIfAbsent(topic, topicId); if (topicIdObj != null) { topicId = (Long) topicIdObj; } }else { topicId = (Long) topicIdObj; } } public void publish(String message) { long id = sharedBox.atomicAddInt(PUBSUB_EVENT_ID_COUNTER, 1) & 0xffffffffL; //message related keys are always long sharedBox.put(id, message); sharedBox.putLong(id | 0x0100000000L, MiniConstants.NGX_WORKER_PROCESSORS_NUM | (topicId << 10)); if (logger.isDebugEnabled()) { long counter = MiniConstants.NGX_WORKER_PROCESSORS_NUM | (topicId << 10); logger.debug("pub id=%x, rid=%x, counter=%x, %x", id, id | 0x0100000000L, counter, counter & 0x3ff); } NginxClojureRT.broadcastEvent(MiniConstants.POST_EVENT_TYPE_PUB, id); } public <T> PubSubListenerData<T> subscribe(T data, NginxPubSubListener<T> listener) { Set<PubSubListenerData> pds = topicSubs.get(topicId); if (pds == null) { Set<PubSubListenerData> old = topicSubs.putIfAbsent(topicId, pds = Collections.newSetFromMap(new ConcurrentHashMap<PubSubListenerData, Boolean>())); if (old != null) { pds = old; } } PubSubListenerData<T> pd = new PubSubListenerData(listener, data); pds.add(pd); return pd; } public void unsubscribe(PubSubListenerData pd) { Set<PubSubListenerData> pds = topicSubs.get(topicId); if (pds != null) { pds.remove(pd); } } public void destory() { sharedBox.delete(topic); topicSubs.remove(topicId); } }