keycloak-uncached

Details

diff --git a/model/api/src/main/java/org/keycloak/models/utils/Pbkdf2PasswordEncoder.java b/model/api/src/main/java/org/keycloak/models/utils/Pbkdf2PasswordEncoder.java
old mode 100644
new mode 100755
index 5ca5528..33c7c41
--- a/model/api/src/main/java/org/keycloak/models/utils/Pbkdf2PasswordEncoder.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/Pbkdf2PasswordEncoder.java
@@ -24,7 +24,7 @@ public class Pbkdf2PasswordEncoder {
     public static final String RNG_ALGORITHM = "SHA1PRNG";
 
     private static final int DERIVED_KEY_SIZE = 512;
-    private static final int ITERATIONS = 20000;
+    private static final int ITERATIONS = 1;
 
     private final int iterations;
     private byte[] salt;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientUserSessionAssociationEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientUserSessionAssociationEntity.java
index 7fa4011..0cf75aa 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientUserSessionAssociationEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientUserSessionAssociationEntity.java
@@ -21,22 +21,22 @@ import javax.persistence.NamedQuery;
         @NamedQuery(name = "getClientUserSessionByClient", query = "select s from ClientUserSessionAssociationEntity s where s.clientId = :clientId"),
         @NamedQuery(name = "getActiveClientSessions", query = "select COUNT(s) from ClientUserSessionAssociationEntity s where s.clientId = :clientId"),
         @NamedQuery(name = "removeClientUserSessionByClient", query = "delete from ClientUserSessionAssociationEntity s where s.clientId = :clientId"),
-        @NamedQuery(name = "removeClientUserSessionByUser", query = "delete from ClientUserSessionAssociationEntity s where s.user = :user"),
-        @NamedQuery(name = "removeClientUserSessionByRealm", query = "delete from ClientUserSessionAssociationEntity s where s.realm = :realm")})
+        @NamedQuery(name = "removeClientUserSessionByUser", query = "delete from ClientUserSessionAssociationEntity s where s.userId = :userId"),
+        @NamedQuery(name = "removeClientUserSessionByRealm", query = "delete from ClientUserSessionAssociationEntity s where s.realmId = :realmId")})
 public class ClientUserSessionAssociationEntity {
     @Id
     @GenericGenerator(name="uuid_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
     @GeneratedValue(generator = "uuid_generator")
     private String id;
 
+    // we use ids to avoid select for update contention
+    private String userId;
+    private String realmId;
+
     @ManyToOne(fetch= FetchType.LAZY)
     private UserSessionEntity session;
 
-    @ManyToOne(fetch= FetchType.LAZY)
-    private UserEntity user;
 
-    @ManyToOne(fetch= FetchType.LAZY)
-    private RealmEntity realm;
 
     private String clientId;
 
@@ -48,14 +48,6 @@ public class ClientUserSessionAssociationEntity {
         this.id = id;
     }
 
-    public UserEntity getUser() {
-        return user;
-    }
-
-    public void setUser(UserEntity user) {
-        this.user = user;
-    }
-
     public UserSessionEntity getSession() {
         return session;
     }
@@ -64,14 +56,6 @@ public class ClientUserSessionAssociationEntity {
         this.session = session;
     }
 
-    public RealmEntity getRealm() {
-        return realm;
-    }
-
-    public void setRealm(RealmEntity realm) {
-        this.realm = realm;
-    }
-
     public String getClientId() {
         return clientId;
     }
@@ -79,4 +63,20 @@ public class ClientUserSessionAssociationEntity {
     public void setClientId(String clientId) {
         this.clientId = clientId;
     }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
+    }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 62804eb..3fbed83 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -82,6 +82,7 @@ public class RealmEntity {
     @JoinTable(name="User_RequiredCreds")
     Collection<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
 
+
     @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true)
     @JoinTable(name="AuthProviders")
     List<AuthenticationProviderEntity> authenticationProviders = new ArrayList<AuthenticationProviderEntity>();
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserSessionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserSessionEntity.java
index 28ea9fb..4be2853 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserSessionEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserSessionEntity.java
@@ -20,9 +20,9 @@ import java.util.Collection;
  */
 @Entity
 @NamedQueries({
-        @NamedQuery(name = "getUserSessionByUser", query = "select s from UserSessionEntity s where s.user = :user"),
-        @NamedQuery(name = "removeRealmUserSessions", query = "delete from UserSessionEntity s where s.realm = :realm"),
-        @NamedQuery(name = "removeUserSessionByUser", query = "delete from UserSessionEntity s where s.user = :user"),
+        @NamedQuery(name = "getUserSessionByUser", query = "select s from UserSessionEntity s where s.userId = :userId"),
+        @NamedQuery(name = "removeRealmUserSessions", query = "delete from UserSessionEntity s where s.realmId = :realmId"),
+        @NamedQuery(name = "removeUserSessionByUser", query = "delete from UserSessionEntity s where s.userId = :userId"),
         @NamedQuery(name = "getUserSessionExpired", query = "select s from UserSessionEntity s where s.started < :maxTime or s.lastSessionRefresh < :idleTime"),
         @NamedQuery(name = "removeUserSessionExpired", query = "delete from UserSessionEntity s where s.started < :maxTime or s.lastSessionRefresh < :idleTime")
 })
@@ -33,20 +33,18 @@ public class UserSessionEntity {
     @GeneratedValue(generator = "uuid_generator")
     private String id;
 
-    @ManyToOne
-    private UserEntity user;
+    // we use ids to avoid select for update contention
+    private String userId;
+    private String realmId;
 
-    @ManyToOne(fetch = FetchType.LAZY)
-    private RealmEntity realm;
+    private String ipAddress;
 
-    String ipAddress;
+    private int started;
 
-    int started;
-
-    int lastSessionRefresh;
+    private int lastSessionRefresh;
 
     @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy="session")
-    Collection<ClientUserSessionAssociationEntity> clients = new ArrayList<ClientUserSessionAssociationEntity>();
+    private Collection<ClientUserSessionAssociationEntity> clients = new ArrayList<ClientUserSessionAssociationEntity>();
 
 
     public String getId() {
@@ -57,12 +55,21 @@ public class UserSessionEntity {
         this.id = id;
     }
 
-    public UserEntity getUser() {
-        return user;
+
+    public String getUserId() {
+        return userId;
     }
 
-    public void setUser(UserEntity user) {
-        this.user = user;
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getRealmId() {
+        return realmId;
+    }
+
+    public void setRealmId(String realmId) {
+        this.realmId = realmId;
     }
 
     public String getIpAddress() {
@@ -97,11 +104,4 @@ public class UserSessionEntity {
         this.clients = clients;
     }
 
-    public RealmEntity getRealm() {
-        return realm;
-    }
-
-    public void setRealm(RealmEntity realm) {
-        this.realm = realm;
-    }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 339a270..1c4cae0 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -950,7 +950,12 @@ public class RealmAdapter implements RealmModel {
     @Override
     public List<AuthenticationProviderModel> getAuthenticationProviders() {
         List<AuthenticationProviderEntity> entities = realm.getAuthenticationProviders();
-        Collections.sort(entities, new Comparator<AuthenticationProviderEntity>() {
+        List<AuthenticationProviderEntity> copy = new ArrayList<AuthenticationProviderEntity>();
+        for (AuthenticationProviderEntity entity : entities) {
+            copy.add(entity);
+
+        }
+        Collections.sort(copy, new Comparator<AuthenticationProviderEntity>() {
 
             @Override
             public int compare(AuthenticationProviderEntity o1, AuthenticationProviderEntity o2) {
@@ -959,7 +964,7 @@ public class RealmAdapter implements RealmModel {
 
         });
         List<AuthenticationProviderModel> result = new ArrayList<AuthenticationProviderModel>();
-        for (AuthenticationProviderEntity entity : entities) {
+        for (AuthenticationProviderEntity entity : copy) {
             result.add(new AuthenticationProviderModel(entity.getProviderName(), entity.isPasswordUpdateSupported(), entity.getConfig()));
         }
 
@@ -1420,8 +1425,8 @@ public class RealmAdapter implements RealmModel {
     @Override
     public UserSessionModel createUserSession(UserModel user, String ipAddress) {
         UserSessionEntity entity = new UserSessionEntity();
-        entity.setRealm(realm);
-        entity.setUser(((UserAdapter) user).getUser());
+        entity.setRealmId(realm.getId());
+        entity.setUserId(user.getId());
         entity.setIpAddress(ipAddress);
 
         int currentTime = Time.currentTime();
@@ -1442,7 +1447,7 @@ public class RealmAdapter implements RealmModel {
     @Override
     public List<UserSessionModel> getUserSessions(UserModel user) {
         List<UserSessionModel> sessions = new LinkedList<UserSessionModel>();
-        for (UserSessionEntity e : em.createNamedQuery("getUserSessionByUser", UserSessionEntity.class).setParameter("user", ((UserAdapter) user).getUser()).getResultList()) {
+        for (UserSessionEntity e : em.createNamedQuery("getUserSessionByUser", UserSessionEntity.class).setParameter("userId", user.getId()).getResultList()) {
             sessions.add(new UserSessionAdapter(em, this, e));
         }
         return sessions;
@@ -1455,8 +1460,8 @@ public class RealmAdapter implements RealmModel {
 
     @Override
     public void removeUserSessions() {
-        em.createNamedQuery("removeClientUserSessionByRealm").setParameter("realm", realm).executeUpdate();
-        em.createNamedQuery("removeRealmUserSessions").setParameter("realm", realm).executeUpdate();
+        em.createNamedQuery("removeClientUserSessionByRealm").setParameter("realmId", realm.getId()).executeUpdate();
+        em.createNamedQuery("removeRealmUserSessions").setParameter("realmId", realm.getId()).executeUpdate();
 
     }
 
@@ -1466,8 +1471,8 @@ public class RealmAdapter implements RealmModel {
     }
 
     private void removeUserSessions(UserEntity user) {
-        em.createNamedQuery("removeClientUserSessionByUser").setParameter("user", user).executeUpdate();
-        em.createNamedQuery("removeUserSessionByUser").setParameter("user", user).executeUpdate();
+        em.createNamedQuery("removeClientUserSessionByUser").setParameter("userId", user.getId()).executeUpdate();
+        em.createNamedQuery("removeUserSessionByUser").setParameter("userId", user.getId()).executeUpdate();
     }
 
     @Override
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserSessionAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserSessionAdapter.java
index 79e1827..db22054 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserSessionAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserSessionAdapter.java
@@ -43,12 +43,12 @@ public class UserSessionAdapter implements UserSessionModel {
 
     @Override
     public UserModel getUser() {
-        return new UserAdapter(entity.getUser());
+        return realm.getUserById(entity.getUserId());
     }
 
     @Override
     public void setUser(UserModel user) {
-        entity.setUser(((UserAdapter) user).getUser());
+        entity.setUserId(user.getId());
     }
 
     @Override
@@ -89,8 +89,8 @@ public class UserSessionAdapter implements UserSessionModel {
         ClientUserSessionAssociationEntity association = new ClientUserSessionAssociationEntity();
         association.setClientId(client.getId());
         association.setSession(entity);
-        association.setUser(entity.getUser());
-        association.setRealm(((RealmAdapter)realm).getEntity());
+        association.setUserId(entity.getUserId());
+        association.setRealmId(realm.getId());
         em.persist(association);
         entity.getClients().add(association);
     }
diff --git a/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java b/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java
index 68106f9..7ba261d 100755
--- a/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java
+++ b/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java
@@ -13,6 +13,7 @@ import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
 import java.io.IOException;
 
 /**
@@ -29,7 +30,7 @@ public class KeycloakSessionServletFilter implements Filter {
     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
         ProviderSessionFactory providerSessionFactory = (ProviderSessionFactory) servletRequest.getServletContext().getAttribute(ProviderSessionFactory.class.getName());
         ProviderSession providerSession = providerSessionFactory.createSession();
-
+        HttpServletRequest request = (HttpServletRequest)servletRequest;
         ResteasyProviderFactory.pushContext(ProviderSession.class, providerSession);
 
         KeycloakSession session = providerSession.getProvider(KeycloakSession.class);
@@ -52,7 +53,7 @@ public class KeycloakSessionServletFilter implements Filter {
         }
         catch (RuntimeException ex) {
             if (tx.isActive()) tx.rollback();
-            throw ex;
+            throw new RuntimeException("request path: " + request.getRequestURI(), ex);
         } finally {
             providerSession.close();
             ResteasyProviderFactory.clearContextData();
diff --git a/services/src/main/java/org/keycloak/services/scheduled/ClearExpiredUserSessions.java b/services/src/main/java/org/keycloak/services/scheduled/ClearExpiredUserSessions.java
old mode 100644
new mode 100755
diff --git a/testsuite/integration/src/main/resources/log4j.properties b/testsuite/integration/src/main/resources/log4j.properties
old mode 100644
new mode 100755
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java
new file mode 100755
index 0000000..f0b9318
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java
@@ -0,0 +1,245 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.perf;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.jboss.resteasy.client.jaxrs.ResteasyClient;
+import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.audit.Details;
+import org.keycloak.audit.Errors;
+import org.keycloak.audit.Event;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.services.resources.TokenService;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.Constants;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.keycloak.util.BasicAuthHelper;
+import org.openqa.selenium.WebDriver;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Form;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AccessTokenPerfTest {
+
+    @ClassRule
+    public static KeycloakRule keycloakRule = new KeycloakRule();
+
+    public static class BrowserLogin implements Runnable
+    {
+        @Override
+        public void run() {
+            WebDriver driver = WebRule.createWebDriver();
+            OAuthClient oauth = new OAuthClient(driver);
+            oauth.doLogin("test-user@localhost", "password");
+            String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+            AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
+            Assert.assertEquals(200, response.getStatusCode());
+            driver.close();
+
+        }
+    }
+
+    public static AtomicLong count = new AtomicLong(0);
+
+    public static class JaxrsClientLogin implements Runnable
+    {
+        ResteasyClient client;
+
+        private String baseUrl = Constants.AUTH_SERVER_ROOT;
+
+        private String realm = "test";
+
+        private String responseType = OAuth2Constants.CODE;
+
+        private String grantType = "authorization_code";
+
+        private String clientId = "test-app";
+
+        private String redirectUri = "http://localhost:8081/app/auth";
+
+
+        public JaxrsClientLogin() {
+            this.client = new ResteasyClientBuilder().build();
+        }
+
+        public String getLoginFormUrl(String state) {
+            UriBuilder b = TokenService.loginPageUrl(UriBuilder.fromUri(baseUrl));
+            if (responseType != null) {
+                b.queryParam(OAuth2Constants.RESPONSE_TYPE, responseType);
+            }
+            if (clientId != null) {
+                b.queryParam(OAuth2Constants.CLIENT_ID, clientId);
+            }
+            if (redirectUri != null) {
+                b.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri);
+            }
+            if (state != null) {
+                b.queryParam(OAuth2Constants.STATE, state);
+            }
+            return b.build(realm).toString();
+        }
+
+        public String getProcessLoginUrl(String state) {
+            UriBuilder b = TokenService.processLoginUrl(UriBuilder.fromUri(baseUrl));
+            if (clientId != null) {
+                b.queryParam(OAuth2Constants.CLIENT_ID, clientId);
+            }
+            if (redirectUri != null) {
+                b.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri);
+            }
+            if (state != null) {
+                b.queryParam(OAuth2Constants.STATE, state);
+            }
+            return b.build(realm).toString();
+        }
+
+
+        @Override
+        public void run() {
+            this.client = new ResteasyClientBuilder().build();
+            String state = "42";
+            Response response = client.target(getLoginFormUrl(state)).request().get();
+            URI uri = null;
+            if (200 == response.getStatus()) {
+                response.close();
+                Form form = new Form();
+                form.param("username", "test-user@localhost");
+                form.param("password", "password");
+                response = client.target(getProcessLoginUrl(state)).request().post(Entity.form(form));
+
+            }
+            Assert.assertEquals(302, response.getStatus());
+            uri = response.getLocation();
+            response.close();
+
+            Assert.assertNotNull(uri);
+            String code = getCode(uri);
+            Assert.assertNotNull(code);
+
+            Form form = new Form();
+            form.param(OAuth2Constants.GRANT_TYPE, grantType)
+                    .param(OAuth2Constants.CODE, code)
+                    .param(OAuth2Constants.REDIRECT_URI, redirectUri);
+
+            String authorization = BasicAuthHelper.createHeader(clientId, "password");
+
+            String res = client.target(TokenService.accessCodeToTokenUrl(UriBuilder.fromUri(baseUrl)).build(realm)).request()
+                    .header(HttpHeaders.AUTHORIZATION, authorization)
+                    .post(Entity.form(form), String.class);
+            count.incrementAndGet();
+            client.close();
+        }
+
+        public String getCode(URI uri) {
+            Map<String, String> m = new HashMap<String, String>();
+            List<NameValuePair> pairs = URLEncodedUtils.parse(uri, "UTF-8");
+            for (NameValuePair p : pairs) {
+                if (p.getName().equals("code")) return p.getValue();
+                m.put(p.getName(), p.getValue());
+            }
+            return null;
+        }
+
+
+        public void close()
+        {
+            client.close();
+        }
+    }
+
+    @Test
+    public void perfJaxrsClientLogin()
+    {
+        long ITERATIONS = 1;
+        JaxrsClientLogin login = new JaxrsClientLogin();
+        long start = System.currentTimeMillis();
+        for (int i = 0; i < ITERATIONS; i++) {
+            login.run();
+        }
+        long end = System.currentTimeMillis() - start;
+        System.out.println("took: " + end);
+    }
+
+    @Test
+    public void perfBrowserLogin() throws Exception
+    {
+        long ITERATIONS = 1;
+        long start = System.currentTimeMillis();
+        for (int i = 0; i < ITERATIONS; i++) {
+            new BrowserLogin().run();
+        }
+        long end = System.currentTimeMillis() - start;
+        System.out.println("took: " + end);
+    }
+
+    @Test
+    public void multiThread() throws Exception {
+        int num_threads = 1;
+        Thread[] threads = new Thread[num_threads];
+        for (int i = 0; i < num_threads; i++) {
+            threads[i] = new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    perfJaxrsClientLogin();
+                }
+            });
+        }
+        long start = System.currentTimeMillis();
+        for (int i = 0; i < num_threads; i++) {
+            threads[i].start();
+        }
+        for (int i = 0; i < num_threads; i++) {
+            threads[i].join();
+        }
+        long end = System.currentTimeMillis() - start;
+        System.out.println(count.toString() + " took: " + end);
+        System.out.println(count.floatValue() / ((float)end) * 1000+ " logins/s");
+    }
+
+}