package com.cyou.cpush.apns.core; import io.netty.channel.EventLoop; import io.netty.channel.EventLoopGroup; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; import java.util.ArrayList; import java.util.Enumeration; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import com.cyou.cpush.apns.conf.Credentials; import com.cyou.cpush.apns.notification.ErrorPacket; import com.cyou.cpush.apns.notification.Notification; public class PushBuffer { private static final InternalLogger log = InternalLoggerFactory .getInstance(PushBuffer.class); private EventLoopGroup eventLoopGroup; private DefaultApnsConnection connection; private ConcurrentLinkedQueue<NotificationPromise> bufferQueue = new ConcurrentLinkedQueue<NotificationPromise>(); private static final int MAX_PER_THREAD = 10000; private static ConcurrentHashMap<Credentials, PushBuffer> bufferGroup = new ConcurrentHashMap<Credentials, PushBuffer>(); private static Thread monitor; private static int INTERVAL_MONITOR = 3000; static { monitor = new Thread() { @Override public void run() { while (true) { try { Enumeration<PushBuffer> buffers = bufferGroup.elements(); for (; buffers.hasMoreElements();) { PushBuffer buffer = buffers.nextElement(); if (!buffer.isEmpty()) { buffer.triggerPush(); } } Thread.sleep(INTERVAL_MONITOR); } catch (Exception e) { log.error(e.getMessage(), e); } } } }; monitor.start(); } public static PushBuffer newInstance(DefaultApnsConnection connection) { Credentials cred = connection.credential(); PushBuffer buffer = bufferGroup.get(cred); if (buffer == null) { buffer = new PushBuffer(connection); bufferGroup.putIfAbsent(cred, buffer); } return buffer; } private PushBuffer(DefaultApnsConnection connection) { this.connection = connection; this.eventLoopGroup = connection.group(); } public NotificationPromise push(Notification notification) { NotificationPromise promise = new NotificationPromise(notification); bufferQueue.offer(promise); return promise; } private void triggerPush() { final EventLoop eventLoop = eventLoopGroup.next(); eventLoop.execute(new Runnable() { @Override public void run() { while (!bufferQueue.isEmpty()) { NotificationPromise tempPromise; ArrayList<NotificationPromise> tempPromises = new ArrayList<NotificationPromise>(); for (int i = 0; i < MAX_PER_THREAD && (tempPromise = bufferQueue.poll()) != null; i++) { tempPromise.executor(eventLoop); tempPromises.add(tempPromise); } if (!tempPromises.isEmpty()) { final NotificationPromise[] notifications = new NotificationPromise[tempPromises .size()]; tempPromises.toArray(notifications); Future<Iterable<ErrorPacket>> future = connection .push(notifications); future.addListener(new FutureListener<Iterable<ErrorPacket>>() { @Override public void operationComplete(Future<Iterable<ErrorPacket>> future) throws Exception { if (future.isSuccess()) { /* notify the failure event to the listeners */ Iterable<ErrorPacket> it = future.get(); if (it != null) { for (ErrorPacket ep : it) { try { notifications[ep.getIdentifier()].setFailure(ep); } catch (Exception e) { log.warn(e.getMessage(), e); } } } /* notify the success event to the listeners */ for (NotificationPromise n : notifications) { if (!n.isDone()) { n.setSuccess(null); } } } else { /* * if handshake fail and greater than max handshake retry, * future will be failed. Now simply notify the each listener * failure. TODO: This will be improved in next version. */ for (NotificationPromise n : notifications) { if (!n.isDone()) { n.setFailure(n.cause()); } } } } }); } } } }); } private boolean isEmpty() { return this.bufferQueue.isEmpty(); } }