package the8472.mldht; import static the8472.bencode.Utils.str2buf; import static the8472.utils.Functional.tap; import static the8472.utils.Functional.unchecked; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicReference; import lbms.plugins.mldht.kad.DHT; import lbms.plugins.mldht.kad.Key; import lbms.plugins.mldht.kad.messages.GetPeersRequest; import lbms.plugins.mldht.kad.messages.MessageBase; import lbms.plugins.mldht.kad.messages.MessageBase.Method; import lbms.plugins.mldht.kad.messages.MessageBase.Type; import lbms.plugins.mldht.utils.NIOConnectionManager; import lbms.plugins.mldht.utils.Selectable; import the8472.utils.ConfigReader; import the8472.utils.XMLUtils; import the8472.utils.concurrent.SerializedTaskExecutor; public class PassiveRedisIndexer implements Component { private Queue<ByteBuffer> writeQueue = new ConcurrentLinkedQueue<>(); private volatile boolean running = true; ConfigReader config; AtomicReference<SocketHandler> ref = new AtomicReference<>(); NIOConnectionManager conMan; public void start(Collection<DHT> dhts, ConfigReader config) { this.config = config; conMan = new NIOConnectionManager("redis selector"); dhts.forEach((dht) -> { dht.addIncomingMessageListener(this::incomingMessage); }); } SocketHandler ensureOpen() { SocketHandler handler = ref.get(); if(handler == null) { handler = new SocketHandler(); if(ref.compareAndSet(null, handler)) { handler.open(); } } return handler; } public void stop() { running = false; } private static final String TTL = Integer.toString(2*24*3600); private void incomingMessage(DHT dht, MessageBase msg) { if(!running) return; if(msg.getType() == Type.REQ_MSG && msg.getMethod() == Method.GET_PEERS) { GetPeersRequest req = (GetPeersRequest) msg; long now = System.currentTimeMillis(); Key k = req.getTarget(); String ipAddr = req.getOrigin().getAddress().getHostAddress(); String key = k.toString(false); StringBuilder b = new StringBuilder(); // zadd <hash> <timestamp> <ip> b.append("*4\r\n"); b.append("$4\r\n"); b.append("ZADD\r\n"); b.append("$40\r\n"); b.append(key).append("\r\n"); String intAsString = Long.toString(now); b.append('$').append(intAsString.length()).append("\r\n"); b.append(intAsString).append("\r\n"); b.append('$').append(ipAddr.length()).append("\r\n"); b.append(ipAddr).append("\r\n"); // expire <hash> <ttl> b.append("*3\r\n"); b.append("$6\r\n"); b.append("EXPIRE\r\n"); b.append("$40\r\n"); b.append(key).append("\r\n"); b.append('$').append(TTL.length()).append("\r\n"); b.append(TTL).append("\r\n"); SocketHandler handler = ensureOpen(); // to avoid OOM we only fill the queue when we have an open connection if(handler.getChannel() != null && handler.getChannel().isConnected()) { writeQueue.add(str2buf(b.toString())); handler.tryWrite.run(); } } } static private final Map<String,String> namespaces = tap(new HashMap<>(), m -> m.put("xsi","http://www.w3.org/2001/XMLSchema-instance")); private InetAddress getAddress() { return config.get(XMLUtils.buildXPath("//components/component[@xsi:type='mldht:redisIndexerType']/address",namespaces)).flatMap(unchecked(str -> Optional.of(InetAddress.getByName(str)))).get(); } class SocketHandler implements Selectable { SocketChannel chan; void open() { try { chan = SocketChannel.open(); chan.configureBlocking(false); chan.connect(new InetSocketAddress(getAddress(),6379)); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } conMan.register(this); } void close() { writeQueue.clear(); ref.compareAndSet(this, null); try { chan.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public SocketChannel getChannel() { return chan; } @Override public void registrationEvent(NIOConnectionManager manager, SelectionKey key) throws IOException { // TODO Auto-generated method stub } @Override public void selectionEvent(SelectionKey key) throws IOException { if(key.isValid() && key.isConnectable()) { chan.finishConnect(); conMan.interestOpsChanged(this); } if(key.isValid() && key.isReadable()) read(); if(key.isValid() && key.isWritable()) { awaitingWriteNotification = false; tryWrite.run(); conMan.interestOpsChanged(this); } } volatile boolean awaitingWriteNotification = true; ByteBuffer toWrite; Runnable tryWrite = SerializedTaskExecutor.onceMore(() -> { while(!awaitingWriteNotification && !writeQueue.isEmpty()) { if(toWrite == null) toWrite = writeQueue.poll(); if(toWrite == null) continue; int written = 0; try { chan.write(toWrite); } catch (IOException e) { e.printStackTrace(); } if(written < 0) { awaitingWriteNotification = true; close(); continue; } if(toWrite.remaining() > 0) { awaitingWriteNotification = true; conMan.interestOpsChanged(this); } else { toWrite = null; } } }); ByteBuffer oblivion = ByteBuffer.allocateDirect(4*1024); void read() throws IOException { while(true) { oblivion.clear(); int read = chan.read(oblivion); if(read < 0) close(); if(read <= 0) break; } } @Override public void doStateChecks(long now) throws IOException { if(!chan.isOpen()) { close(); conMan.deRegister(this); } } @Override public int calcInterestOps() { int ops = SelectionKey.OP_READ; if(chan.isConnectionPending()) ops |= SelectionKey.OP_CONNECT; if(awaitingWriteNotification) ops |= SelectionKey.OP_WRITE; return ops; } } }