package jelectrum.db.level; import java.net.Socket; import java.nio.ByteBuffer; import java.io.DataInputStream; import java.util.Random; import java.text.DecimalFormat; import java.util.Map; import java.util.TreeMap; import java.util.LinkedList; import java.util.concurrent.Semaphore; import java.util.concurrent.LinkedBlockingQueue; import jelectrum.db.DBTooManyResultsException; import jelectrum.EventLog; import jelectrum.Config; import com.google.protobuf.ByteString; public class LevelNetClient { public static final int RESULT_GOOD = 1273252631; public static final int RESULT_BAD = 9999; public static final int RESULT_TOO_MANY = 28365921; public static final int RESULT_NOTFOUND = 133133; public static final int SOCKET_TIMEOUT = 45000; public static final byte OP_MAX_RESULTS=1; private EventLog log; private Config config; private String host; private int port; private LinkedBlockingQueue<LevelConnection> conns; private Semaphore open_sem; public boolean throw_on_error=false; public LevelNetClient(EventLog log, Config config) throws Exception { this.log = log; this.config = config; config.require("leveldb_host"); config.require("leveldb_port"); config.require("leveldb_conns"); this.host = config.get("leveldb_host"); this.port = config.getInt("leveldb_port"); open_sem = new Semaphore(config.getInt("leveldb_conns")); conns = new LinkedBlockingQueue<LevelConnection>(); new MaintThread().start(); } private LevelConnection getConnection() { LevelConnection conn = conns.poll(); if (conn != null) return conn; if (open_sem.tryAcquire()) { while(true) { try { conn = new LevelConnection(); return conn; } catch(java.io.IOException e) { log.alarm("LevelDB connection failure: " + e); if (throw_on_error) throw new RuntimeException(e); try{ Thread.sleep(2500); } catch(Throwable t){} } } } try { return conns.take(); } catch(InterruptedException e) { throw new RuntimeException(e); } } private void returnConnection(LevelConnection conn) { if (conn == null) return; try { conns.put(conn); } catch(InterruptedException e) { throw new RuntimeException(e); } } private void trashConnection(LevelConnection conn) { if (conn != null) { open_sem.release(1); conn.close(); } } public ByteString get(String key) { //System.out.println("leveldb loading: " + key); while(true) { LevelConnection conn = null; try { conn = getConnection(); return conn.get(key); } catch(java.io.IOException e) { trashConnection(conn); conn=null; log.log("LevelDB error: " + e); if (throw_on_error) throw new RuntimeException(e); try{ Thread.sleep(2500); } catch(Throwable t){} } finally { returnConnection(conn); } } } public void put(String key, ByteString value) { while(true) { LevelConnection conn = null; try { conn = getConnection(); conn.put(key, value); return; } catch(java.io.IOException e) { trashConnection(conn); conn=null; log.log("LevelDB error: " + e); if (throw_on_error) throw new RuntimeException(e); try{ Thread.sleep(2500); } catch(Throwable t){} } finally { returnConnection(conn); } } } public void putAll(Map<String, ByteString> m) { while(true) { LevelConnection conn = null; try { conn = getConnection(); conn.putAll(m); return; } catch(java.io.IOException e) { trashConnection(conn); conn=null; log.log("LevelDB error: " + e); if (throw_on_error) throw new RuntimeException(e); try{ Thread.sleep(2500); } catch(Throwable t){} } finally { returnConnection(conn); } } } public Map<String, ByteString> getByPrefix(String prefix, int max_results) { while(true) { LevelConnection conn = null; try { conn = getConnection(); return conn.getByPrefix(prefix, max_results); } catch(java.io.IOException e) { trashConnection(conn); conn=null; log.log("LevelDB error: " + e); if (throw_on_error) throw new RuntimeException(e); try{ Thread.sleep(2500); } catch(Throwable t){} } finally { returnConnection(conn); } } } public class LevelConnection { public LevelConnection() throws java.io.IOException { try { sock = new Socket(host, port); sock.setTcpNoDelay(true); sock.setSoTimeout(SOCKET_TIMEOUT); d_in = new DataInputStream(sock.getInputStream()); } catch(java.lang.SecurityException e) { throw new RuntimeException(e); } } private Socket sock; private DataInputStream d_in; public void close() { try { sock.close(); } catch(Throwable t){} } public ByteString get(String key) throws java.io.IOException { byte cmd[]=new byte[2]; cmd[0]=1; cmd[1]=0; sock.getOutputStream().write(cmd, 0, 2); writeString(key); sock.getOutputStream().flush(); int status = readInt(); if (status == RESULT_NOTFOUND) return null; else if (status != RESULT_GOOD) throw new java.io.IOException("Bad result: " + status); return readBytes(); } public void ping() throws java.io.IOException { byte cmd[]=new byte[2]; cmd[0]=5; cmd[1]=0; sock.getOutputStream().write(cmd, 0, 2); int status = readInt(); if (status != RESULT_GOOD) throw new java.io.IOException("Bad result: " + status); } public void put(String key, ByteString value) throws java.io.IOException { byte cmd[]=new byte[2]; cmd[0]=2; cmd[1]=0; sock.getOutputStream().write(cmd, 0, 2); writeString(key); writeByteArray(value); sock.getOutputStream().flush(); int status = readInt(); if (status != RESULT_GOOD) throw new java.io.IOException("Bad result: " + status); } public void putAll(Map<String, ByteString> map) throws java.io.IOException { byte cmd[]=new byte[2]; cmd[0]=3; cmd[1]=0; sock.getOutputStream().write(cmd, 0, 2); writeInt(map.size()); for(Map.Entry<String, ByteString> me : map.entrySet()) { writeString(me.getKey()); writeByteArray(me.getValue()); } int status = readInt(); if (status != RESULT_GOOD) throw new java.io.IOException("Bad result: " + status); } public Map<String, ByteString> getByPrefix(String prefix,int max_results) throws java.io.IOException { byte cmd[]=new byte[2]; cmd[0]=4; cmd[1]=OP_MAX_RESULTS; sock.getOutputStream().write(cmd, 0, 2); writeInt(max_results); writeString(prefix); int status = readInt(); if (status == RESULT_TOO_MANY) { throw new DBTooManyResultsException(); } int items = readInt(); Map<String,ByteString> m = new TreeMap<>(); for(int i=0; i<items; i++) { String key = readString(); ByteString buff = readBytes(); m.put(key, buff); } return m; } private ByteString readBytes() throws java.io.IOException { int sz = readInt(); //if (sz == 0) return ByteString; byte[] data = new byte[sz]; d_in.readFully(data); return ByteString.copyFrom(data); } private String readString() throws java.io.IOException { return readBytes().toStringUtf8(); } private int readInt() throws java.io.IOException { byte[] d = new byte[4]; d_in.readFully(d); ByteBuffer bb = ByteBuffer.wrap(d); bb.order(java.nio.ByteOrder.BIG_ENDIAN); return bb.getInt(); } private void writeNull() throws java.io.IOException { writeInt(0); } private void writeInt(int val) throws java.io.IOException { byte[] val_bytes=new byte[4]; ByteBuffer bb = ByteBuffer.wrap(val_bytes); bb.order(java.nio.ByteOrder.BIG_ENDIAN); bb.putInt(val); sock.getOutputStream().write(val_bytes); } private void writeString(String s) throws java.io.IOException { if (s == null) { writeNull(); return; } byte[] bytes = s.getBytes(); writeInt(bytes.length); sock.getOutputStream().write(bytes); } private void writeByteArray(ByteString bb) throws java.io.IOException { if (bb == null) { writeNull(); return; } byte[] bytes = bb.toByteArray(); writeInt(bb.size()); bb.writeTo(sock.getOutputStream()); } } public class MaintThread extends Thread { public MaintThread() { setName("LevelNetClient/MaintThread"); setDaemon(true); } public void run() { while(true) { try { Thread.sleep(30000); int open = conns.size(); //log.log("Levelnetclient: checking " + open + " connections"); for(int i=0; i<open; i++) { LevelConnection conn = null; conn = conns.poll(); if (conn != null) { try { conn.ping(); returnConnection(conn); //log.log("Levelnetclient: ping ok"); } catch(java.io.IOException e) { log.log("Levelnetclient: exception: " + e); trashConnection(conn); } } } while(open_sem.tryAcquire()) { try { LevelConnection conn = null; conn = new LevelConnection(); returnConnection(conn); } catch(Throwable t) { log.log("Levelnetclient: " + t); open_sem.release(1); } } } catch(Throwable t) { log.log("LevelNetClient/MaintThread - " + t); } } } } }