diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/Ip2Region.java b/binding/java/src/main/java/org/lionsoul/ip2region/Ip2Region.java index 76dd3df..b5c116c 100644 --- a/binding/java/src/main/java/org/lionsoul/ip2region/Ip2Region.java +++ b/binding/java/src/main/java/org/lionsoul/ip2region/Ip2Region.java @@ -26,16 +26,16 @@ public class Ip2Region { /* v6 pool */ private final SearcherPool v6Pool; - public Ip2Region(Config v4Config, Config v6Config) { + public Ip2Region(Config v4Config, Config v6Config) throws IOException { this.v4Pool = new SearcherPool(v4Config); this.v6Pool = new SearcherPool(v6Config); } - public String search(String ipString) throws InetAddressException, IOException { + public String search(String ipString) throws InetAddressException, IOException, InterruptedException { return search(Util.parseIP(ipString)); } - public String search(byte[] ipBytes) throws InetAddressException, IOException { + public String search(byte[] ipBytes) throws InetAddressException, IOException, InterruptedException { // 1, define the pool with the input ip final SearcherPool pool; if (ipBytes.length == 4) { @@ -47,21 +47,13 @@ public class Ip2Region { } // 2, get a searcher from the pool - final Searcher searcher = pool.getSearcher(); + final Searcher searcher = pool.borrowSearcher(); try { // 3, do the search - final String region = searcher.search(ipBytes); - return region; - } catch (InetAddressException e) { - // for the inet address error and we should return the searcher - throw e; - } catch (IOException e) { - // for the IOException usually means something is wrong with the read operation to the xdb file - // and we choose to keep the searcher and destory it right now - // so we will create a new one for the next search - try {searcher.close();} catch (IOException e1) {} - throw e; + return searcher.search(ipBytes); + } finally { + pool.returnSearcher(searcher); } } diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/SearcherPool.java b/binding/java/src/main/java/org/lionsoul/ip2region/SearcherPool.java index 3256e09..6f362b8 100644 --- a/binding/java/src/main/java/org/lionsoul/ip2region/SearcherPool.java +++ b/binding/java/src/main/java/org/lionsoul/ip2region/SearcherPool.java @@ -5,9 +5,10 @@ package org.lionsoul.ip2region; import java.io.IOException; +import java.util.Iterator; import java.util.LinkedList; import java.util.Queue; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import org.lionsoul.ip2region.xdb.Searcher; @@ -19,43 +20,94 @@ import org.lionsoul.ip2region.xdb.Searcher; * Date 2025/11/21 */ public class SearcherPool { + public static final int WAIT_OVERLOADED = 1; + public static final int NEW_OVERLOADED = 2; + // config instance private final Config config; // searcher pool private final Queue pool; - // searcher lock + // lock & conditions private final ReentrantLock lock; + private final Condition emptyCondition; + private final Condition fullCondition; // searcher numbers that was loaned out - private final AtomicInteger loanCount; + private int loanCount; - public SearcherPool(Config config) { + public SearcherPool(Config config) throws IOException { this(config, false); } - public SearcherPool(Config config, boolean fair) { + public SearcherPool(Config config, boolean fair) throws IOException { + assert config.searchers > 0; this.config = config; this.pool = new LinkedList<>(); this.lock = new ReentrantLock(fair); - this.loanCount = new AtomicInteger(0); - } + this.emptyCondition = this.lock.newCondition(); + this.fullCondition = this.lock.newCondition(); + this.loanCount = 0; - // init the searcher pool - public void init() throws IOException { + // create the searchers for (int i = 0; i < config.searchers; i++) { final Searcher searcher = new Searcher(config.ipVersion, config.xdbPath, config.vIndex, config.cBuffer); pool.add(searcher); } } - public Searcher getSearcher() { - return null; + public Config getConfig() { + return config; + } + + public Searcher borrowSearcher() throws InterruptedException { + lock.lock(); + try { + while (pool.isEmpty()) { + emptyCondition.await(); + } + + loanCount++; + return pool.poll(); + } finally { + lock.unlock(); + } + } + + public void returnSearcher(final Searcher searcher) { + lock.lock(); + try { + pool.add(searcher); + loanCount--; + emptyCondition.signal(); + + // check and signal the full condition. + // pool close + if (loanCount == 0) { + fullCondition.signal(); + } + } finally { + lock.unlock(); + } } // close the searcher pool - public void close() { - this.loanCount.set(config.searchers); + public void close() throws InterruptedException { + lock.lock(); + try { + while (loanCount > 0) { + fullCondition.wait(); + } + + final Iterator it = pool.iterator(); + while (it.hasNext()) { + final Searcher searcher = it.next(); + try {searcher.close();} catch (IOException e) {} + it.remove(); + } + } finally { + lock.unlock(); + } } } \ No newline at end of file diff --git a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Searcher.java b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Searcher.java index dbae4de..5dd22c4 100644 --- a/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Searcher.java +++ b/binding/java/src/main/java/org/lionsoul/ip2region/xdb/Searcher.java @@ -29,6 +29,7 @@ public class Searcher { private final Version version; // random access file handle for file-based search + private final String xdbPath; private final RandomAccessFile handle; private int ioCount = 0; @@ -61,6 +62,7 @@ public class Searcher { public Searcher(Version version, String dbFile, byte[] vectorIndex, LongByteArray cBuff) throws IOException { this.version = version; + this.xdbPath = dbFile; if (cBuff != null) { this.handle = null; this.vectorIndex = null; @@ -173,6 +175,16 @@ public class Searcher { } } + @Override public String toString() { + return String.format( + "%s->{version:%s, xdb:%s, vIndex:%s, cBuffer:%s}", + super.toString(), + version.name, xdbPath, + vectorIndex == null ? "null" : String.valueOf(vectorIndex.length), + contentBuff == null ? "null" : String.valueOf(contentBuff.length()) + ); + } + // --- static util function public static Header loadHeader(RandomAccessFile handle) throws IOException { diff --git a/binding/java/src/test/java/org/lionsoul/ip2region/SearcherPoolTest.java b/binding/java/src/test/java/org/lionsoul/ip2region/SearcherPoolTest.java new file mode 100644 index 0000000..ef51252 --- /dev/null +++ b/binding/java/src/test/java/org/lionsoul/ip2region/SearcherPoolTest.java @@ -0,0 +1,70 @@ +package org.lionsoul.ip2region; + +import java.security.CodeSource; + +import org.junit.Test; +import org.lionsoul.ip2region.xdb.Log; +import org.lionsoul.ip2region.xdb.Searcher; + +public class SearcherPoolTest { + + private static final Log log = Log.getLogger(ConfigTest.class).setLevel(Log.DEBUG); + + public String getDataPath(String xdbFile) { + final CodeSource cs = this.getClass().getProtectionDomain().getCodeSource(); + if (cs != null) { + // log.debugf("code path: %s", cs.getLocation().getPath().concat("../../../../data/")); + return cs.getLocation().getPath().concat("../../../../data/").concat(xdbFile); + } else { + return "../../../../data/".concat(xdbFile); + } + } + + @Test + public void testV4SeacherPool() throws Exception { + final Config v4Config = Config.custom() + .setCachePolicy(Config.VIndexCache) + .setSeachers(5) + .setXdbPath(getDataPath("ip2region_v4.xdb")) + .asV4(); + + + final String ipStr = "58.250.36.41"; + final SearcherPool v4Pool = new SearcherPool(v4Config); + for (int i = 0; i < 20; i++) { + final Searcher searcher = v4Pool.borrowSearcher(); + log.debugf("borrowed searcher %d: %s", i, searcher.toString()); + final String region = searcher.search(ipStr); + log.debugf("search(%s)=%s", ipStr, region); + v4Pool.returnSearcher(searcher); + log.debugf("return searcher %d", i); + } + + v4Pool.close(); + log.debugf("v4 searcher pool closed gracefully"); + } + + @Test + public void testV6SeacherPool() throws Exception { + final Config v6Config = Config.custom() + .setCachePolicy(Config.VIndexCache) + .setSeachers(5) + .setXdbPath(getDataPath("ip2region_v6.xdb")) + .asV6(); + + + final String ipStr = "240e:3b7:3272:d8d0:db09:c067:8d59:539e"; + final SearcherPool v4Pool = new SearcherPool(v6Config); + for (int i = 0; i < 20; i++) { + final Searcher searcher = v4Pool.borrowSearcher(); + log.debugf("borrowed searcher %d: %s", i, searcher.toString()); + final String region = searcher.search(ipStr); + log.debugf("search(%s)=%s", ipStr, region); + v4Pool.returnSearcher(searcher); + log.debugf("return searcher %d", i); + } + + v4Pool.close(); + log.debugf("v6 searcher pool closed gracefully"); + } +}