Jason Rivard 4 лет назад
Родитель
Сommit
dda7578556
19 измененных файлов с 389 добавлено и 212 удалено
  1. 2 2
      rest-test-service/src/main/java/password/pwm/resttest/RestTestSmsGatewayServlet.java
  2. 2 10
      rest-test-service/src/main/java/password/pwm/resttest/SmsResponse.java
  3. 18 7
      server/src/main/java/password/pwm/http/PwmRequest.java
  4. 45 30
      server/src/main/java/password/pwm/http/PwmSession.java
  5. 13 6
      server/src/main/java/password/pwm/ldap/LdapConnectionService.java
  6. 10 10
      server/src/main/java/password/pwm/svc/report/ReportSummaryData.java
  7. 38 12
      server/src/main/java/password/pwm/svc/stats/StatisticsBundle.java
  8. 1 1
      server/src/main/java/password/pwm/svc/wordlist/AbstractWordlist.java
  9. 9 1
      server/src/main/java/password/pwm/svc/wordlist/SharedHistoryManager.java
  10. 40 13
      server/src/main/java/password/pwm/util/EventRateMeter.java
  11. 32 13
      server/src/main/java/password/pwm/util/java/AverageTracker.java
  12. 43 16
      server/src/main/java/password/pwm/util/java/ConcurrentClosableIteratorWrapper.java
  13. 6 6
      server/src/main/java/password/pwm/util/java/JsonUtil.java
  14. 24 12
      server/src/main/java/password/pwm/util/java/MovingAverage.java
  15. 0 1
      server/src/main/java/password/pwm/util/java/StatisticCounterBundle.java
  16. 60 49
      server/src/main/java/password/pwm/util/localdb/LocalDBFactory.java
  17. 2 2
      server/src/main/java/password/pwm/util/localdb/LocalDBStoredQueue.java
  18. 31 19
      server/src/main/java/password/pwm/util/localdb/WorkQueueProcessor.java
  19. 13 2
      server/src/main/java/password/pwm/util/secure/SecureEngine.java

+ 2 - 2
rest-test-service/src/main/java/password/pwm/resttest/RestTestSmsGatewayServlet.java

@@ -45,7 +45,7 @@ public class RestTestSmsGatewayServlet extends HttpServlet
     @Override
     protected void doPost( final HttpServletRequest req, final HttpServletResponse resp ) throws ServletException, IOException
     {
-            final SmsResponse instance = SmsResponse.getInstance();
+            final SmsResponse instance = new SmsResponse();
             final InputStream inputStream = req.getInputStream();
             final String body = IOUtils.toString( inputStream );
 
@@ -69,7 +69,7 @@ public class RestTestSmsGatewayServlet extends HttpServlet
     protected void doGet( final HttpServletRequest req, final HttpServletResponse resp ) throws IOException
     {
             //Check request
-            final SmsResponse instance = SmsResponse.getInstance();
+            final SmsResponse instance = new SmsResponse();
             final String requestUsername = req.getParameter( USERNAME_PARAMETER );
             final SmsGetResponseBody responseBody;
 

+ 2 - 10
rest-test-service/src/main/java/password/pwm/resttest/SmsResponse.java

@@ -26,19 +26,11 @@ import java.util.Map;
 
 public class SmsResponse
 {
-    public static final SmsResponse INSTANCE = new SmsResponse();
-
-    Map<String, ArrayList<SmsPostResponseBody>> recentSmsMessages;
+    private Map<String, ArrayList<SmsPostResponseBody>> recentSmsMessages;
 
     public SmsResponse()
     {
-        this.recentSmsMessages = new HashMap<String, ArrayList<SmsPostResponseBody>>();
-    }
-
-    /** Getters and Setters. */
-    public static synchronized SmsResponse getInstance()
-    {
-        return INSTANCE;
+        this.recentSmsMessages = new HashMap<>();
     }
 
     Map<String, ArrayList<SmsPostResponseBody>> getRecentSmsMessages()

+ 18 - 7
server/src/main/java/password/pwm/http/PwmRequest.java

@@ -69,6 +69,8 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Supplier;
 
 public class PwmRequest extends PwmHttpRequestWrapper
@@ -85,6 +87,7 @@ public class PwmRequest extends PwmHttpRequestWrapper
 
     private final Set<PwmRequestFlag> flags = EnumSet.noneOf( PwmRequestFlag.class );
     private final Instant requestStartTime = Instant.now();
+    private final Lock cspCreationLock = new ReentrantLock();
 
     public static PwmRequest forRequest(
             final HttpServletRequest request,
@@ -495,16 +498,24 @@ public class PwmRequest extends PwmHttpRequestWrapper
         return ssBean.getLogoutURL() == null ? pwmApplication.getConfig().readSettingAsString( PwmSetting.URL_LOGOUT ) : ssBean.getLogoutURL();
     }
 
-    public synchronized String getCspNonce( )
+    public String getCspNonce( )
     {
-        if ( getAttribute( PwmRequestAttribute.CspNonce ) == null )
+        cspCreationLock.lock();
+        try
+        {
+            if ( getAttribute( PwmRequestAttribute.CspNonce ) == null )
+            {
+                final int nonceLength = Integer.parseInt( getConfig().readAppProperty( AppProperty.HTTP_HEADER_CSP_NONCE_BYTES ) );
+                final byte[] cspNonce = pwmApplication.getSecureService().pwmRandom().newBytes( nonceLength );
+                final String cspString = StringUtil.base64Encode( cspNonce );
+                setAttribute( PwmRequestAttribute.CspNonce, cspString );
+            }
+            return ( String ) getAttribute( PwmRequestAttribute.CspNonce );
+        }
+        finally
         {
-            final int nonceLength = Integer.parseInt( getConfig().readAppProperty( AppProperty.HTTP_HEADER_CSP_NONCE_BYTES ) );
-            final byte[] cspNonce = pwmApplication.getSecureService().pwmRandom().newBytes( nonceLength );
-            final String cspString = StringUtil.base64Encode( cspNonce );
-            setAttribute( PwmRequestAttribute.CspNonce, cspString );
+            cspCreationLock.unlock();
         }
-        return ( String ) getAttribute( PwmRequestAttribute.CspNonce );
     }
 
     public <T extends Serializable> T readEncryptedCookie( final String cookieName, final Class<T> returnClass )

+ 45 - 30
server/src/main/java/password/pwm/http/PwmSession.java

@@ -52,6 +52,8 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 /**
  * @author Jason D. Rivard
@@ -73,20 +75,25 @@ public class PwmSession implements Serializable
     private LoginInfoBean loginInfoBean;
     private transient UserInfo userInfo;
 
-    private static final Object CREATION_LOCK = new Object();
+    private static final Lock CREATION_LOCK = new ReentrantLock();
 
+    private final Lock securityKeyLock = new ReentrantLock();
     private final transient SessionManager sessionManager;
 
     public static PwmSession createPwmSession( final PwmApplication pwmApplication )
             throws PwmUnrecoverableException
     {
-        synchronized ( CREATION_LOCK )
+        CREATION_LOCK.lock();
+        try
         {
             return new PwmSession( pwmApplication );
         }
+        finally
+        {
+            CREATION_LOCK.unlock();
+        }
     }
 
-
     private PwmSession( final PwmApplication pwmApplication )
             throws PwmUnrecoverableException
     {
@@ -359,42 +366,50 @@ public class PwmSession implements Serializable
         return ( int ) JavaHelper.sizeof( this );
     }
 
-    synchronized PwmSecurityKey getSecurityKey( final PwmRequest pwmRequest )
+    PwmSecurityKey getSecurityKey( final PwmRequest pwmRequest )
             throws PwmUnrecoverableException
     {
-        final int length = Integer.parseInt( pwmRequest.getConfig().readAppProperty( AppProperty.HTTP_COOKIE_NONCE_LENGTH ) );
-        final String cookieName =  pwmRequest.getConfig().readAppProperty( AppProperty.HTTP_COOKIE_NONCE_NAME );
-
-        String nonce = (String) pwmRequest.getAttribute( PwmRequestAttribute.CookieNonce );
-        if ( nonce == null || nonce.length() < length )
+        securityKeyLock.lock();
+        try
         {
-            nonce = pwmRequest.readCookie( cookieName );
-        }
+            final int length = Integer.parseInt( pwmRequest.getConfig().readAppProperty( AppProperty.HTTP_COOKIE_NONCE_LENGTH ) );
+            final String cookieName = pwmRequest.getConfig().readAppProperty( AppProperty.HTTP_COOKIE_NONCE_NAME );
 
-        boolean newNonce = false;
-        if ( nonce == null || nonce.length() < length )
-        {
-            // random value
-            final String random = pwmRequest.getPwmApplication().getSecureService().pwmRandom().alphaNumericString( length );
+            String nonce = ( String ) pwmRequest.getAttribute( PwmRequestAttribute.CookieNonce );
+            if ( nonce == null || nonce.length() < length )
+            {
+                nonce = pwmRequest.readCookie( cookieName );
+            }
 
-            // timestamp component for uniqueness
-            final String prefix = Long.toString( System.currentTimeMillis(), Character.MAX_RADIX );
+            boolean newNonce = false;
+            if ( nonce == null || nonce.length() < length )
+            {
+                // random value
+                final String random = pwmRequest.getPwmApplication().getSecureService().pwmRandom().alphaNumericString( length );
 
-            nonce = random + prefix;
-            newNonce = true;
-        }
+                // timestamp component for uniqueness
+                final String prefix = Long.toString( System.currentTimeMillis(), Character.MAX_RADIX );
 
-        final PwmSecurityKey securityKey = pwmRequest.getConfig().getSecurityKey();
-        final String concatValue = securityKey.keyHash( pwmRequest.getPwmApplication().getSecureService() ) + nonce;
-        final String hashValue = pwmRequest.getPwmApplication().getSecureService().hash( concatValue );
-        final PwmSecurityKey pwmSecurityKey = new PwmSecurityKey( hashValue );
+                nonce = random + prefix;
+                newNonce = true;
+            }
 
-        if ( newNonce )
+            final PwmSecurityKey securityKey = pwmRequest.getConfig().getSecurityKey();
+            final String concatValue = securityKey.keyHash( pwmRequest.getPwmApplication().getSecureService() ) + nonce;
+            final String hashValue = pwmRequest.getPwmApplication().getSecureService().hash( concatValue );
+            final PwmSecurityKey pwmSecurityKey = new PwmSecurityKey( hashValue );
+
+            if ( newNonce )
+            {
+                pwmRequest.setAttribute( PwmRequestAttribute.CookieNonce, nonce );
+                pwmRequest.getPwmResponse().writeCookie( cookieName, nonce, -1, PwmHttpResponseWrapper.CookiePath.Application );
+            }
+
+            return pwmSecurityKey;
+        }
+        finally
         {
-            pwmRequest.setAttribute( PwmRequestAttribute.CookieNonce, nonce );
-            pwmRequest.getPwmResponse().writeCookie( cookieName, nonce, -1, PwmHttpResponseWrapper.CookiePath.Application );
+            securityKeyLock.unlock();
         }
-
-        return pwmSecurityKey;
     }
 }

+ 13 - 6
server/src/main/java/password/pwm/ldap/LdapConnectionService.java

@@ -44,6 +44,7 @@ import password.pwm.util.java.AtomicLoopIntIncrementer;
 import password.pwm.util.java.ConditionalTaskExecutor;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.StatisticCounterBundle;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
@@ -86,8 +87,14 @@ public class LdapConnectionService implements PwmService
     private volatile STATUS status = STATUS.CLOSED;
 
     private boolean useThreadLocal;
-    private final AtomicLoopIntIncrementer statCreatedProxies = new AtomicLoopIntIncrementer();
-    private final AtomicLoopIntIncrementer statClearedThreadLocals = new AtomicLoopIntIncrementer();
+
+    private final StatisticCounterBundle<StatKey> stats = new StatisticCounterBundle<>( StatKey.class );
+
+    enum StatKey
+    {
+        createdProxies,
+        clearedThreadLocals,
+    }
 
     private enum DebugKey
     {
@@ -276,7 +283,7 @@ public class LdapConnectionService implements PwmService
             LOGGER.trace( () -> "created new system proxy chaiProvider id=" + chaiProvider.toString()
                     + " for ldap profile '" + ldapProfile.getIdentifier() + "'"
                     + " thread=" + Thread.currentThread().getName() );
-            statCreatedProxies.next();
+            stats.increment( StatKey.createdProxies );
             return chaiProvider;
         }
         catch ( final PwmUnrecoverableException e )
@@ -436,8 +443,8 @@ public class LdapConnectionService implements PwmService
         debugInfo.put( DebugKey.Allocated, String.valueOf( allocatedConnections ) );
         debugInfo.put( DebugKey.CurrentActive, String.valueOf( activeConnections ) );
         debugInfo.put( DebugKey.ThreadLocals, String.valueOf( threadLocalConnections.get( ) ) );
-        debugInfo.put( DebugKey.CreatedProviders, String.valueOf( statCreatedProxies.get() ) );
-        debugInfo.put( DebugKey.DiscardedThreadLocals, String.valueOf( statClearedThreadLocals.get() ) );
+        debugInfo.put( DebugKey.CreatedProviders, String.valueOf( stats.get( StatKey.createdProxies ) ) );
+        debugInfo.put( DebugKey.DiscardedThreadLocals, String.valueOf( stats.get( StatKey.clearedThreadLocals ) ) );
         return Collections.unmodifiableMap( JavaHelper.enumMapToStringMap( debugInfo ) );
     }
 
@@ -474,7 +481,7 @@ public class LdapConnectionService implements PwmService
                         {
                             LOGGER.trace( () -> "discarding idled connection id=" + chaiProvider.toString() + " from orphaned threadLocal, age="
                                     + age.asCompactString() + ", thread=" + container.getThreadName() );
-                            statClearedThreadLocals.next();
+                            stats.increment( StatKey.clearedThreadLocals );
                         }
                         container.getProviderMap().clear();
                     }

+ 10 - 10
server/src/main/java/password/pwm/svc/report/ReportSummaryData.java

@@ -185,25 +185,25 @@ public class ReportSummaryData
         if ( userCacheRecord.getResponseStorageMethod() != null )
         {
             final DataStorageMethod method = userCacheRecord.getResponseStorageMethod();
-            responseStorage.computeIfAbsent( method, dataStorageMethod -> new LongAdder() );
-            responseStorage.get( method ).increment();
+            responseStorage
+                    .computeIfAbsent( method, dataStorageMethod -> new LongAdder() )
+                    .increment();
         }
 
         if ( userCacheRecord.getLdapProfile() != null )
         {
             final String userProfile = userCacheRecord.getLdapProfile();
-            if ( !ldapProfile.containsKey( userProfile ) )
-            {
-                ldapProfile.put( userProfile, new LongAdder() );
-            }
-            ldapProfile.get( userProfile ).increment();
+            ldapProfile
+                    .computeIfAbsent( userProfile, type -> new LongAdder() )
+                    .increment();
         }
 
         if ( userCacheRecord.getResponseFormatType() != null )
         {
             final Answer.FormatType type = userCacheRecord.getResponseFormatType();
-            responseFormatType.computeIfAbsent( type, formatType -> new LongAdder() );
-            responseFormatType.get( type ).increment();
+            responseFormatType
+                    .computeIfAbsent( type, formatType -> new LongAdder() )
+                    .increment();
         }
 
         if ( userCacheRecord.isHasOtpSecret() )
@@ -395,7 +395,7 @@ public class ReportSummaryData
                     : LocaleHelper.getLocalizedMessage( locale, labelKey, config, Admin.class, new String[]
                     {
                             replacement,
-                    }
+                            }
             );
             final String pct = valueCount > 0 ? new Percent( valueCount, totalUsers ).pretty( 2 ) : "";
             final PwmNumberFormat numberFormat = PwmNumberFormat.forLocale( locale );

+ 38 - 12
server/src/main/java/password/pwm/svc/stats/StatisticsBundle.java

@@ -30,6 +30,8 @@ import java.util.EnumMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.concurrent.atomic.LongAdder;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 public class StatisticsBundle
 {
@@ -124,32 +126,56 @@ public class StatisticsBundle
 
     private static class AverageBean implements Serializable
     {
-        BigInteger total = BigInteger.ZERO;
-        BigInteger count = BigInteger.ZERO;
+        private BigInteger total = BigInteger.ZERO;
+        private BigInteger count = BigInteger.ZERO;
+        private final Lock lock = new ReentrantLock();
 
         AverageBean( )
         {
         }
 
-        synchronized BigInteger getAverage( )
+        BigInteger getAverage( )
         {
-            if ( BigInteger.ZERO.equals( count ) )
+            lock.lock();
+            try
             {
-                return BigInteger.ZERO;
+                if ( BigInteger.ZERO.equals( count ) )
+                {
+                    return BigInteger.ZERO;
+                }
+                return total.divide( count );
+            }
+            finally
+            {
+                lock.unlock();
             }
-
-            return total.divide( count );
         }
 
-        synchronized void appendValue( final long value )
+        void appendValue( final long value )
         {
-            count = count.add( BigInteger.ONE );
-            total = total.add( BigInteger.valueOf( value ) );
+            lock.lock();
+            try
+            {
+                count = count.add( BigInteger.ONE );
+                total = total.add( BigInteger.valueOf( value ) );
+            }
+            finally
+            {
+                lock.unlock();
+            }
         }
 
-        synchronized boolean isZero()
+        boolean isZero()
         {
-            return total.equals( BigInteger.ZERO );
+            lock.lock();
+            try
+            {
+                return total.equals( BigInteger.ZERO );
+            }
+            finally
+            {
+                lock.unlock();
+            }
         }
     }
 }

+ 1 - 1
server/src/main/java/password/pwm/svc/wordlist/AbstractWordlist.java

@@ -256,7 +256,7 @@ abstract class AbstractWordlist implements Wordlist, PwmService
         return -1;
     }
 
-    public synchronized void close( )
+    public void close( )
     {
         final TimeDuration closeWaitTime = TimeDuration.of( 1, TimeDuration.Unit.MINUTES );
 

+ 9 - 1
server/src/main/java/password/pwm/svc/wordlist/SharedHistoryManager.java

@@ -45,6 +45,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.TimerTask;
 import java.util.concurrent.ExecutorService;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 
 public class SharedHistoryManager implements PwmService
@@ -73,6 +75,7 @@ public class SharedHistoryManager implements PwmService
     private long oldestEntry;
 
     private final Settings settings = new Settings();
+    private final Lock addWordLock = new ReentrantLock();
 
     public SharedHistoryManager( ) throws LocalDBException
     {
@@ -271,7 +274,7 @@ public class SharedHistoryManager implements PwmService
         return word.length() > 0 ? word : null;
     }
 
-    public synchronized void addWord(
+    public void addWord(
             final SessionLabel sessionLabel,
             final String word
     )
@@ -290,6 +293,7 @@ public class SharedHistoryManager implements PwmService
 
         final Instant startTime = Instant.now();
 
+        addWordLock.lock();
         try
         {
             final String hashedWord = hashWord( addWord );
@@ -305,6 +309,10 @@ public class SharedHistoryManager implements PwmService
         {
             LOGGER.warn( sessionLabel, () -> "error adding word to global history list: " + e.getMessage() );
         }
+        finally
+        {
+            addWordLock.unlock();
+        }
     }
 
     private String hashWord( final String word ) throws NoSuchAlgorithmException

+ 40 - 13
server/src/main/java/password/pwm/util/EventRateMeter.java

@@ -25,10 +25,13 @@ import password.pwm.util.java.TimeDuration;
 
 import java.io.Serializable;
 import java.math.BigDecimal;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 public class EventRateMeter implements Serializable
 {
     private final TimeDuration maxDuration;
+    private final Lock lock = new ReentrantLock();
 
     private MovingAverage movingAverage;
     private double remainder;
@@ -43,30 +46,54 @@ public class EventRateMeter implements Serializable
         reset();
     }
 
-    public synchronized void reset( )
+    public void reset( )
     {
-        movingAverage = new MovingAverage( maxDuration.asMillis() );
-        remainder = 0;
+        lock.lock();
+        try
+        {
+            movingAverage = new MovingAverage( maxDuration.asMillis() );
+            remainder = 0;
+        }
+        finally
+        {
+            lock.unlock();
+        }
     }
 
-    public synchronized void markEvents( final int eventCount )
+    public void markEvents( final int eventCount )
     {
-        final long timeSinceLastUpdate = System.currentTimeMillis() - movingAverage.getLastMillis();
-        if ( timeSinceLastUpdate != 0 )
+        lock.lock();
+        try
         {
-            final double eventRate = ( eventCount + remainder ) / timeSinceLastUpdate;
-            movingAverage.update( eventRate * 1000 );
-            remainder = 0;
+            final long timeSinceLastUpdate = System.currentTimeMillis() - movingAverage.getLastMillis();
+            if ( timeSinceLastUpdate != 0 )
+            {
+                final double eventRate = ( eventCount + remainder ) / timeSinceLastUpdate;
+                movingAverage.update( eventRate * 1000 );
+                remainder = 0;
+            }
+            else
+            {
+                remainder += eventCount;
+            }
         }
-        else
+        finally
         {
-            remainder += eventCount;
+            lock.unlock();
         }
     }
 
-    public synchronized BigDecimal readEventRate( )
+    public BigDecimal readEventRate( )
     {
-        return new BigDecimal( this.movingAverage.getAverage() );
+        lock.lock();
+        try
+        {
+            return new BigDecimal( this.movingAverage.getAverage() );
+        }
+        finally
+        {
+            lock.unlock();
+        }
     }
 
 }

+ 32 - 13
server/src/main/java/password/pwm/util/java/AverageTracker.java

@@ -23,38 +23,57 @@ package password.pwm.util.java;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.math.MathContext;
+import java.util.ArrayDeque;
 import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 public class AverageTracker
 {
     private final int maxSamples;
-    private final Queue<BigInteger> samples = new ConcurrentLinkedQueue<>();
+    private final Queue<BigInteger> samples = new ArrayDeque<>();
+    private final ReadWriteLock lock = new ReentrantReadWriteLock();
 
     public AverageTracker( final int maxSamples )
     {
         this.maxSamples = maxSamples;
     }
 
-    public synchronized void addSample( final long input )
+    public void addSample( final long input )
     {
-        samples.add( BigInteger.valueOf( input ) );
-        while ( samples.size() > maxSamples )
+        lock.writeLock().lock();
+        try
         {
-            samples.remove();
+            samples.add( BigInteger.valueOf( input ) );
+            while ( samples.size() > maxSamples )
+            {
+                samples.remove();
+            }
+        }
+        finally
+        {
+            lock.writeLock().unlock();
         }
     }
 
-    public synchronized BigDecimal avg( )
+    public BigDecimal avg( )
     {
-        if ( samples.isEmpty() )
+        lock.readLock().lock();
+        try
         {
-            return BigDecimal.ZERO;
-        }
+            if ( samples.isEmpty() )
+            {
+                return BigDecimal.ZERO;
+            }
 
-        final BigInteger total = samples.stream().reduce( BigInteger::add ).get();
-        final BigDecimal sampleSize = new BigDecimal( samples.size() );
-        return new BigDecimal( total ).divide( sampleSize, MathContext.DECIMAL128 );
+            final BigInteger total = samples.stream().reduce( BigInteger::add ).get();
+            final BigDecimal sampleSize = new BigDecimal( samples.size() );
+            return new BigDecimal( total ).divide( sampleSize, MathContext.DECIMAL128 );
+        }
+        finally
+        {
+            lock.readLock().unlock();
+        }
     }
 
     public long avgAsLong( )

+ 43 - 16
server/src/main/java/password/pwm/util/java/ConcurrentClosableIteratorWrapper.java

@@ -25,12 +25,15 @@ import java.util.NoSuchElementException;
 import java.util.Optional;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Supplier;
 
 public class ConcurrentClosableIteratorWrapper<T> implements Iterator<T>, ClosableIterator<T>
 {
     private final AtomicReference<T> nextReference = new AtomicReference<>();
     private final AtomicBoolean completed = new AtomicBoolean( false );
+    private final Lock lock = new ReentrantLock();
 
     private final Supplier<Optional<T>> nextSupplier;
     private final Runnable closeFunction;
@@ -42,37 +45,61 @@ public class ConcurrentClosableIteratorWrapper<T> implements Iterator<T>, Closab
     }
 
     @Override
-    public synchronized boolean hasNext()
+    public boolean hasNext()
     {
-        if ( completed.get() )
+        lock.lock();
+        try
         {
-            return false;
+            if ( completed.get() )
+            {
+                return false;
+            }
+            work();
+            return nextReference.get() != null;
+        }
+        finally
+        {
+            lock.unlock();
         }
-        work();
-        return nextReference.get() != null;
     }
 
     @Override
-    public synchronized T next()
+    public T next()
     {
-        if ( completed.get() )
+        lock.lock();
+        try
         {
-            throw new NoSuchElementException(  );
+            if ( completed.get() )
+            {
+                throw new NoSuchElementException(  );
+            }
+            work();
+            final T fileSummaryInformation = nextReference.get();
+            if ( fileSummaryInformation == null )
+            {
+                throw new NoSuchElementException(  );
+            }
+            nextReference.set( null );
+            return fileSummaryInformation;
         }
-        work();
-        final T fileSummaryInformation = nextReference.get();
-        if ( fileSummaryInformation == null )
+        finally
         {
-            throw new NoSuchElementException(  );
+            lock.unlock();
         }
-        nextReference.set( null );
-        return fileSummaryInformation;
     }
 
     @Override
-    public synchronized void close()
+    public void close()
     {
-        closeImpl();
+        lock.lock();
+        try
+        {
+            closeImpl();
+        }
+        finally
+        {
+            lock.unlock();
+        }
     }
 
     private void closeImpl()

+ 6 - 6
server/src/main/java/password/pwm/util/java/JsonUtil.java

@@ -158,7 +158,7 @@ public class JsonUtil
         {
         }
 
-        public synchronized JsonElement serialize( final X509Certificate cert, final Type type, final JsonSerializationContext jsonSerializationContext )
+        public JsonElement serialize( final X509Certificate cert, final Type type, final JsonSerializationContext jsonSerializationContext )
         {
             try
             {
@@ -207,12 +207,12 @@ public class JsonUtil
         {
         }
 
-        public synchronized JsonElement serialize( final Date date, final Type type, final JsonSerializationContext jsonSerializationContext )
+        public JsonElement serialize( final Date date, final Type type, final JsonSerializationContext jsonSerializationContext )
         {
             return new JsonPrimitive( ISO_DATE_FORMAT.format( date.toInstant() ) );
         }
 
-        public synchronized Date deserialize( final JsonElement jsonElement, final Type type, final JsonDeserializationContext jsonDeserializationContext )
+        public Date deserialize( final JsonElement jsonElement, final Type type, final JsonDeserializationContext jsonDeserializationContext )
         {
             try
             {
@@ -245,12 +245,12 @@ public class JsonUtil
         {
         }
 
-        public synchronized JsonElement serialize( final Instant instant, final Type type, final JsonSerializationContext jsonSerializationContext )
+        public JsonElement serialize( final Instant instant, final Type type, final JsonSerializationContext jsonSerializationContext )
         {
             return new JsonPrimitive( JavaHelper.toIsoDate( instant ) );
         }
 
-        public synchronized Instant deserialize( final JsonElement jsonElement, final Type type, final JsonDeserializationContext jsonDeserializationContext )
+        public Instant deserialize( final JsonElement jsonElement, final Type type, final JsonDeserializationContext jsonDeserializationContext )
         {
             try
             {
@@ -270,7 +270,7 @@ public class JsonUtil
         {
         }
 
-        public synchronized ChallengeSet deserialize( final JsonElement jsonElement, final Type type, final JsonDeserializationContext jsonDeserializationContext )
+        public ChallengeSet deserialize( final JsonElement jsonElement, final Type type, final JsonDeserializationContext jsonDeserializationContext )
         {
             try
             {

+ 24 - 12
server/src/main/java/password/pwm/util/java/MovingAverage.java

@@ -21,6 +21,8 @@
 package password.pwm.util.java;
 
 import java.io.Serializable;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 /**
  * <p>MovingAverage.java</p>
@@ -50,6 +52,8 @@ public class MovingAverage implements Serializable
     private long lastMillis;
     private double average;
 
+    private final Lock lock = new ReentrantLock();
+
     /**
      * Construct a {@link MovingAverage}, providing the time window
      * we want the average over. For example, providing a value of
@@ -73,22 +77,30 @@ public class MovingAverage implements Serializable
      *
      * @param sample the latest measurement in the rolling average
      */
-    public synchronized void update( final double sample )
+    public void update( final double sample )
     {
-        final long now = System.currentTimeMillis();
-
-        if ( lastMillis == 0 )
+        lock.lock();
+        try
         {
-            // first sample
-            average = sample;
+            final long now = System.currentTimeMillis();
+
+            if ( lastMillis == 0 )
+            {
+                // first sample
+                average = sample;
+                lastMillis = now;
+                return;
+            }
+            final long deltaTime = now - lastMillis;
+            final double coeff = Math.exp( -1.0 * ( ( double ) deltaTime / windowMillis ) );
+            average = ( 1.0 - coeff ) * sample + coeff * average;
+
             lastMillis = now;
-            return;
         }
-        final long deltaTime = now - lastMillis;
-        final double coeff = Math.exp( -1.0 * ( ( double ) deltaTime / windowMillis ) );
-        average = ( 1.0 - coeff ) * sample + coeff * average;
-
-        lastMillis = now;
+        finally
+        {
+            lock.unlock();
+        }
     }
 
     /**

+ 0 - 1
server/src/main/java/password/pwm/util/java/StatisticCounterBundle.java

@@ -74,5 +74,4 @@ public class StatisticCounterBundle<K extends Enum<K>>
     {
         return new LongAccumulator( Long::sum, 0L );
     }
-
 }

+ 60 - 49
server/src/main/java/password/pwm/util/localdb/LocalDBFactory.java

@@ -35,6 +35,8 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 /**
  * @author Jason D. Rivard
@@ -42,8 +44,9 @@ import java.util.Map;
 public class LocalDBFactory
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( LocalDBFactory.class );
+    private static final Lock CREATION_LOCK = new ReentrantLock();
 
-    public static synchronized LocalDB getInstance(
+    public static LocalDB getInstance(
             final File dbDirectory,
             final boolean readonly,
             final PwmEnvironment pwmEnvironment,
@@ -51,66 +54,74 @@ public class LocalDBFactory
     )
             throws Exception
     {
-        final Configuration config = ( configuration == null && pwmEnvironment != null )
-                ? pwmEnvironment.getConfig()
-                : configuration;
-
-        final long startTime = System.currentTimeMillis();
-
-        final String className;
-        final Map<String, String> initParameters;
-        if ( config == null )
-        {
-            className = AppProperty.LOCALDB_IMPLEMENTATION.getDefaultValue();
-            final String initStrings = AppProperty.LOCALDB_INIT_STRING.getDefaultValue();
-            initParameters = StringUtil.convertStringListToNameValuePair( Arrays.asList( initStrings.split( ";;;" ) ), "=" );
-        }
-        else
+        CREATION_LOCK.lock();
+        try
         {
-            className = config.readAppProperty( AppProperty.LOCALDB_IMPLEMENTATION );
-            final String initStrings = config.readAppProperty( AppProperty.LOCALDB_INIT_STRING );
-            initParameters = StringUtil.convertStringListToNameValuePair( Arrays.asList( initStrings.split( ";;;" ) ), "=" );
-        }
+            final Configuration config = ( configuration == null && pwmEnvironment != null )
+                    ? pwmEnvironment.getConfig()
+                    : configuration;
 
-        final Map<LocalDBProvider.Parameter, String> parameters = pwmEnvironment == null
-                ? Collections.emptyMap()
-                : makeParameterMap( pwmEnvironment.getConfig(), readonly );
-        final LocalDBProvider dbProvider = createInstance( className );
-        LOGGER.debug( () -> "initializing " + className + " localDBProvider instance" );
+            final long startTime = System.currentTimeMillis();
 
-        final LocalDB localDB = new LocalDBAdaptor( dbProvider );
+            final String className;
+            final Map<String, String> initParameters;
+            if ( config == null )
+            {
+                className = AppProperty.LOCALDB_IMPLEMENTATION.getDefaultValue();
+                final String initStrings = AppProperty.LOCALDB_INIT_STRING.getDefaultValue();
+                initParameters = StringUtil.convertStringListToNameValuePair( Arrays.asList( initStrings.split( ";;;" ) ), "=" );
+            }
+            else
+            {
+                className = config.readAppProperty( AppProperty.LOCALDB_IMPLEMENTATION );
+                final String initStrings = config.readAppProperty( AppProperty.LOCALDB_INIT_STRING );
+                initParameters = StringUtil.convertStringListToNameValuePair( Arrays.asList( initStrings.split( ";;;" ) ), "=" );
+            }
 
-        initInstance( dbProvider, dbDirectory, initParameters, className, parameters );
-        final TimeDuration openTime = TimeDuration.of( System.currentTimeMillis() - startTime, TimeDuration.Unit.MILLISECONDS );
+            final Map<LocalDBProvider.Parameter, String> parameters = pwmEnvironment == null
+                    ? Collections.emptyMap()
+                    : makeParameterMap( pwmEnvironment.getConfig(), readonly );
+            final LocalDBProvider dbProvider = createInstance( className );
+            LOGGER.debug( () -> "initializing " + className + " localDBProvider instance" );
 
-        if ( !readonly )
-        {
-            LOGGER.trace( () -> "clearing TEMP db" );
-            localDB.truncate( LocalDB.DB.TEMP );
+            final LocalDB localDB = new LocalDBAdaptor( dbProvider );
+
+            initInstance( dbProvider, dbDirectory, initParameters, className, parameters );
+            final TimeDuration openTime = TimeDuration.of( System.currentTimeMillis() - startTime, TimeDuration.Unit.MILLISECONDS );
 
-            final LocalDBUtility localDBUtility = new LocalDBUtility( localDB );
-            if ( localDBUtility.readImportInprogressFlag() )
+            if ( !readonly )
             {
-                LOGGER.error( () -> "previous database import process did not complete successfully, clearing all data" );
-                localDBUtility.cancelImportProcess();
+                LOGGER.trace( () -> "clearing TEMP db" );
+                localDB.truncate( LocalDB.DB.TEMP );
+
+                final LocalDBUtility localDBUtility = new LocalDBUtility( localDB );
+                if ( localDBUtility.readImportInprogressFlag() )
+                {
+                    LOGGER.error( () -> "previous database import process did not complete successfully, clearing all data" );
+                    localDBUtility.cancelImportProcess();
+                }
             }
-        }
 
-        final StringBuilder debugText = new StringBuilder();
-        debugText.append( "LocalDB open" );
-        if ( localDB.getFileLocation() != null )
-        {
-            debugText.append( ", db size: " ).append( StringUtil.formatDiskSize( FileSystemUtility.getFileDirectorySize( localDB.getFileLocation() ) ) );
-            debugText.append( " at " ).append( dbDirectory.toString() );
-            final long freeSpace = FileSystemUtility.diskSpaceRemaining( localDB.getFileLocation() );
-            if ( freeSpace >= 0 )
+            final StringBuilder debugText = new StringBuilder();
+            debugText.append( "LocalDB open" );
+            if ( localDB.getFileLocation() != null )
             {
-                debugText.append( ", " ).append( StringUtil.formatDiskSize( freeSpace ) ).append( " free" );
+                debugText.append( ", db size: " ).append( StringUtil.formatDiskSize( FileSystemUtility.getFileDirectorySize( localDB.getFileLocation() ) ) );
+                debugText.append( " at " ).append( dbDirectory.toString() );
+                final long freeSpace = FileSystemUtility.diskSpaceRemaining( localDB.getFileLocation() );
+                if ( freeSpace >= 0 )
+                {
+                    debugText.append( ", " ).append( StringUtil.formatDiskSize( freeSpace ) ).append( " free" );
+                }
             }
-        }
-        LOGGER.info( () -> debugText, () -> openTime );
+            LOGGER.info( () -> debugText, () -> openTime );
 
-        return localDB;
+            return localDB;
+        }
+        finally
+        {
+            CREATION_LOCK.unlock();
+        }
     }
 
     private static LocalDBProvider createInstance( final String className )

+ 2 - 2
server/src/main/java/password/pwm/util/localdb/LocalDBStoredQueue.java

@@ -69,7 +69,7 @@ public class LocalDBStoredQueue implements Queue<String>, Deque<String>
         this.internalQueue = new InternalQueue( localDB, db, developerDebug );
     }
 
-    public static synchronized LocalDBStoredQueue createLocalDBStoredQueue(
+    public static LocalDBStoredQueue createLocalDBStoredQueue(
             final PwmApplication pwmApplication,
             final LocalDB pwmDB,
             final LocalDB.DB db
@@ -90,7 +90,7 @@ public class LocalDBStoredQueue implements Queue<String>, Deque<String>
         return new LocalDBStoredQueue( pwmDB, db, developerDebug );
     }
 
-    public static synchronized LocalDBStoredQueue createLocalDBStoredQueue(
+    public static LocalDBStoredQueue createLocalDBStoredQueue(
             final LocalDB pwmDB,
             final LocalDB.DB db,
             final boolean debugEnabled

+ 31 - 19
server/src/main/java/password/pwm/util/localdb/WorkQueueProcessor.java

@@ -52,7 +52,9 @@ import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.LockSupport;
+import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Supplier;
 
 /**
@@ -72,6 +74,7 @@ public final class WorkQueueProcessor<W extends Serializable>
     private volatile WorkerThread workerThread;
 
     private final AtomicLoopIntIncrementer idGenerator = new AtomicLoopIntIncrementer();
+    private final Lock submitLock = new ReentrantLock();
     private Instant eldestItem = null;
 
     private ThreadPoolExecutor executorService;
@@ -239,34 +242,43 @@ public final class WorkQueueProcessor<W extends Serializable>
         }
     }
 
-    private synchronized void submitToQueue( final ItemWrapper<W> itemWrapper ) throws PwmOperationalException
+    private void submitToQueue( final ItemWrapper<W> itemWrapper )
+            throws PwmOperationalException
     {
-        if ( workerThread == null )
+        submitLock.lock();
+        try
         {
-            final String errorMsg = this.getClass().getName() + " has been closed, unable to submit new item";
-            throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ) );
-        }
+            if ( workerThread == null )
+            {
+                final String errorMsg = this.getClass().getName() + " has been closed, unable to submit new item";
+                throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ) );
+            }
 
-        final String asString = JsonUtil.serialize( itemWrapper );
+            final String asString = JsonUtil.serialize( itemWrapper );
 
-        if ( settings.getMaxEvents() > 0 )
-        {
-            final Instant startTime = Instant.now();
-            while ( !queue.offerLast( asString ) )
+            if ( settings.getMaxEvents() > 0 )
             {
-                if ( TimeDuration.fromCurrent( startTime ).isLongerThan( settings.getMaxSubmitWaitTime() ) )
+                final Instant startTime = Instant.now();
+                while ( !queue.offerLast( asString ) )
                 {
-                    final String errorMsg = "unable to submit item to worker queue after " + settings.getMaxSubmitWaitTime().asCompactString()
-                            + ", item=" + itemProcessor.convertToDebugString( itemWrapper.getWorkItem() );
-                    throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ) );
+                    if ( TimeDuration.fromCurrent( startTime ).isLongerThan( settings.getMaxSubmitWaitTime() ) )
+                    {
+                        final String errorMsg = "unable to submit item to worker queue after " + settings.getMaxSubmitWaitTime().asCompactString()
+                                + ", item=" + itemProcessor.convertToDebugString( itemWrapper.getWorkItem() );
+                        throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ) );
+                    }
+                    SUBMIT_QUEUE_FULL_RETRY_CYCLE_INTERVAL.pause();
                 }
-                SUBMIT_QUEUE_FULL_RETRY_CYCLE_INTERVAL.pause();
-            }
 
-            eldestItem = itemWrapper.getDate();
-            workerThread.notifyWorkPending();
+                eldestItem = itemWrapper.getDate();
+                workerThread.notifyWorkPending();
 
-            logger.trace( () -> "item submitted: " + makeDebugText( itemWrapper ) );
+                logger.trace( () -> "item submitted: " + makeDebugText( itemWrapper ) );
+            }
+        }
+        finally
+        {
+            submitLock.unlock();
         }
     }
 

+ 13 - 2
server/src/main/java/password/pwm/util/secure/SecureEngine.java

@@ -48,6 +48,8 @@ import java.security.NoSuchAlgorithmException;
 import java.time.Instant;
 import java.util.Arrays;
 import java.util.Random;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 
 /**
@@ -460,6 +462,7 @@ public class SecureEngine
         private final byte[] value;
 
         private final int fixedComponentLength;
+        private final Lock lock = new ReentrantLock();
 
         NonceGenerator( final int fixedComponentLength, final int counterComponentLength )
         {
@@ -470,8 +473,16 @@ public class SecureEngine
 
         public synchronized byte[] nextValue( )
         {
-            increment( value.length - 1 );
-            return Arrays.copyOf( value, value.length );
+            lock.lock();
+            try
+            {
+                increment( value.length - 1 );
+                return Arrays.copyOf( value, value.length );
+            }
+            finally
+            {
+                lock.unlock();
+            }
         }
 
         private void increment( final int index )