------=_Part_3887_1760018525.1205731478084 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 7bit
I did more testing and found there is more to the bug, page size is not the only factor. The number of results in the next search also matters. However, in all cases, if JNDI connection pooling is disabled, then it always works.
Test description:
create 10000 account: user-1 to user-10000 search size limit = 5000
test 1: step 1. search with filter (uid=*user*): expect SizeLimitExceededException step 2. search with filter (uid=*user-9999*): expect 1 entry
test 2: step 1. search with filter (uid=*user*): expect SizeLimitExceededException step 2. search with filter (uid=*user-10*): expect 112 entries
With Connection Pooling ======================= (A) page size = factor of 5000, e.g. 1000, 2500 test 1: fails intermittently, step 2 got SizeLimitExceededException while 1 entry is expected test 2: fails intermittently, step 2 got SizeLimitExceededException while 112 entry is expected
(B) page size = not factor of 5000, e.g. 999, 2000 test 1: always works test 2: fails intermittently, step 2 got SizeLimitExceededException while 112 entry is expected
Without Connection Pooling ========================== test 1, test 2 always work, regardless of page size.
Apparently the page size and result size of test 1 in (B) somehow mask the real bug, which has to do with connection reuse.
Updated test java program attached.
Thanks,
phoebe
------=_Part_3887_1760018525.1205731478084 Content-Type: application/octet_stream; name=TestPagedControl.java Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename=TestPagedControl.java
package com.zimbra.sandbox;
import java.io.IOException; import java.util.ArrayList; import java.util.Hashtable; import java.util.List;
import javax.naming.Context; import javax.naming.NameNotFoundException; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.SizeLimitExceededException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.DirContext; import javax.naming.directory.InvalidSearchFilterException; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.naming.ldap.Control; import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapContext; import javax.naming.ldap.PagedResultsControl; import javax.naming.ldap.PagedResultsResponseControl;
/* README:
Intermittently if the max entries requested is exact multiple of page size, after a paged search hits SizeLimitExceededException, the next search also always throws a SizeLimitExceededException, even when it should not. e.g. if max requested is 5000, then page size of: 1000 => bad 2500 => bad 999 => good 2000 => good
- create 10100 user entries in OpenLDAP: with uid user-0 to user-10100
- Perform the following steps in a loop 1. search (uid=*user*) with limit 5000 and paged control, expecting SizeLimitExceededException 2. search (uid=*user-10001*) with limit 5000 and paged control, expecting 1 entry returned
- observed that if the limit is multiple of page size (e.g. 1000, 2500), the test failed at the second iteration when it performs 2 - it throws SizeLimitExceededException when it should find one entry. OpenLDAP returns err=4: conn=262 op=8 SRCH base="" scope=2 deref=3 filter="(uid=*user-10001*)" conn=262 op=8 SRCH attr=uid conn=262 op=8 SEARCH RESULT tag=101 err=4 nentries=0 text=
otherwise (e.g. when page size is 999, or 123, or 2000) the test passes.
Example test runs: ---------------------------------------------------
java TestPagedControl 1000
Testing page size 1000 iter 0 iter 1 javax.naming.SizeLimitExceededException: [LDAP: error code 4 - Sizelimit Exceeded]; remaining name '' at com.sun.jndi.ldap.LdapCtx.mapErrorCode(LdapCtx.java:3037) at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2931) at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2737) at com.sun.jndi.ldap.LdapCtx.searchAux(LdapCtx.java:1808) at com.sun.jndi.ldap.LdapCtx.c_search(LdapCtx.java:1731) at com.sun.jndi.toolkit.ctx.ComponentDirContext.p_search(ComponentDirContext.java:368) at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(PartialCompositeDirContext.java:338) at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.search(PartialCompositeDirContext.java:321) at javax.naming.directory.InitialDirContext.search(InitialDirContext.java:248) at TestPagedControl.testPagedControl(TestPagedControl.java:81) at TestPagedControl.doTest(TestPagedControl.java:127) at TestPagedControl.main(TestPagedControl.java:140)
---------------------------------------------------
java TestPagedControl 999
Testing page size 999 iter 0 iter 1 iter 2 iter 3 iter 4 iter 5 iter 6 iter 7 iter 8 iter 9 pass
*/
public class TestPagedControl {
static byte[] getCookie(LdapContext lctxt) throws NamingException { Control[] controls = lctxt.getResponseControls(); if (controls != null) { for (int i = 0; i < controls.length; i++) { if (controls[i] instanceof PagedResultsResponseControl) { PagedResultsResponseControl prrc = (PagedResultsResponseControl)controls[i]; return prrc.getCookie(); } } } return null; }
static List<String> testPagedControl(String query, int maxResults, int pageSize, boolean useConnPool) throws Exception { List<String> results = new ArrayList<String>(); // int total = 0; DirContext ctxt = null; try { Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.PROVIDER_URL, "ldap://localhost:389"); env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.SECURITY_PRINCIPAL, "uid=zimbra,cn=admins,cn=zimbra"); env.put(Context.SECURITY_CREDENTIALS, "zimbra"); env.put(Context.REFERRAL, "follow"); env.put("com.sun.jndi.ldap.connect.timeout", "30000"); env.put("com.sun.jndi.ldap.read.timeout", "30000");
if (useConnPool) env.put("com.sun.jndi.ldap.connect.pool", "true"); else env.put("com.sun.jndi.ldap.connect.pool", "false");
ctxt = new InitialLdapContext(env, null);
String base = ""; String[] returnAttrs = {"uid"};
SearchControls searchControls = new SearchControls(SearchControls.SUBTREE_SCOPE, maxResults, 0, returnAttrs, false, false);
byte[] cookie = null; LdapContext lctxt = (LdapContext)ctxt;
NamingEnumeration ne = null;
try { do { lctxt.setRequestControls(new Control[]{new PagedResultsControl(pageSize, cookie, Control.CRITICAL)});
ne = ctxt.search(base, query, searchControls); while (ne != null && ne.hasMore()) { // total++; SearchResult sr = (SearchResult) ne.nextElement(); String dn = sr.getNameInNamespace(); results.add(dn); } cookie = getCookie(lctxt); } while (cookie != null); } finally { if (ne != null) ne.close(); } } catch (InvalidSearchFilterException e) { throw e; } catch (NameNotFoundException e) { throw e; } catch (SizeLimitExceededException e) { throw e; } catch (NamingException e) { throw e; } catch (IOException e) { throw e; } finally { ctxt.close(); }
return results; }
static void test1(int pageSize, boolean useConnPool) throws Exception { /* * first search: expecting SizeLimitExceededException */ boolean good = false; try { testPagedControl("(uid=*user*)", 5000, pageSize, useConnPool); } catch (SizeLimitExceededException e) { good = true; } if (!good) throw new Exception("expecting SizeLimitExceededException");
List<String> results;
/* * second search: expecting one result */ results = testPagedControl("(uid=*user-9999*)", 5000, pageSize, useConnPool); // for (String s : results) System.out.println(s); if (results.size() != 1) throw new Exception("expecting 1 result, got " + results.size()); }
static void test2(int pageSize, boolean useConnPool) throws Exception { /* * first search: expecting SizeLimitExceededException */ boolean good = false; try { testPagedControl("(uid=*user*)", 5000, pageSize, useConnPool); } catch (SizeLimitExceededException e) { good = true; } if (!good) throw new Exception("expecting SizeLimitExceededException");
List<String> results;
/* * second search: expecting 112 results */ results = testPagedControl("(uid=*user-10*)", 5000, pageSize, useConnPool); // for (String s : results) System.out.println(s); if (results.size() != 112) throw new Exception("expecting 112 result, got " + results.size()); }
private static void usage() { System.out.println("usage: TestPagedControl <page size> <whcih test 1|2> <use conn pool: true|false>"); }
public static void main(String[] args) {
if (args.length != 3) { usage(); System.exit(1); }
int pageSize = Integer.parseInt(args[0]); int test = Integer.parseInt(args[1]); boolean useConnPool = Boolean.parseBoolean(args[2]);
System.out.println("Testing page size " + pageSize);
try { for (int i=0; i<10; i++) { System.out.println(" iter " + i); if (test == 1) test1(pageSize, useConnPool); else if (test == 2) test2(pageSize, useConnPool); else { usage(); System.exit(1); } } System.out.println("pass"); } catch (Exception e) { e.printStackTrace(); } }
}
------=_Part_3887_1760018525.1205731478084--